├── examples
├── getnodebyid.cel
├── nodesbypurltype.cel
├── files.cel
├── packages.cel
├── loadsbom.cel
├── README.md
├── bom-binary.spdx.json
├── compose.cel
└── curl.spdx.json
├── main.go
├── .github
├── dependabot.yaml
└── workflows
│ ├── go-build-and-test.yaml
│ ├── golangci-lint.yaml
│ └── release.yaml
├── .chainguard
└── source.yaml
├── .gitignore
├── tutorial
├── 04.the-workbench.md
├── 06.sbom-composition.md
├── 02.the-bomshell-model.md
├── 01.the-sbom-graph.md
├── 05.the-language.md
└── 03.invoking-bomshell.md
├── pkg
├── loader
│ └── loader.go
├── shell
│ ├── implementation_test.go
│ ├── runner_implementation.go
│ ├── runner.go
│ ├── implementation.go
│ ├── shell.go
│ └── environment.go
├── elements
│ ├── bomshell.go
│ ├── document.go
│ ├── node.go
│ └── nodelist.go
├── functions
│ ├── utility.go
│ ├── functions_test.go
│ ├── utility_test.go
│ └── functions.go
├── ui
│ ├── subshell.go
│ └── interactive.go
└── render
│ └── render.go
├── .bom.yaml
├── release
└── ldflags.sh
├── internal
└── cmd
│ ├── interactive.go
│ ├── options.go
│ ├── root.go
│ ├── run.go
│ └── exec.go
├── Makefile
├── .goreleaser.yaml
├── docs
├── functions.md
└── running.md
├── go.mod
├── README.md
├── .golangci.yaml
├── go.sum
└── LICENSE
/examples/getnodebyid.cel:
--------------------------------------------------------------------------------
1 | sboms[0].NodeByID("Package-curl-8.1.2-r0")
2 |
--------------------------------------------------------------------------------
/examples/nodesbypurltype.cel:
--------------------------------------------------------------------------------
1 | sboms[0].NodesByPurlType("golang").ToDocument()
2 |
--------------------------------------------------------------------------------
/examples/files.cel:
--------------------------------------------------------------------------------
1 | // This bomshell query extracts all files from the SBOM and returns them
2 | sboms[0].files().ToDocument()
3 |
--------------------------------------------------------------------------------
/examples/packages.cel:
--------------------------------------------------------------------------------
1 | // This bomshell query extracts all packages from the SBOM and returns them
2 |
3 | sboms[0].packages().ToDocument()
4 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package main
5 |
6 | import (
7 | "github.com/chainguard-dev/bomshell/internal/cmd"
8 | )
9 |
10 | func main() {
11 | cmd.Execute()
12 | }
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | updates:
4 |
5 | - package-ecosystem: gomod
6 | directory: "/"
7 | schedule:
8 | interval: "daily"
9 | open-pull-requests-limit: 10
10 |
11 | - package-ecosystem: "github-actions"
12 | directory: "/"
13 | schedule:
14 | interval: "daily"
15 | open-pull-requests-limit: 10
16 |
--------------------------------------------------------------------------------
/examples/loadsbom.cel:
--------------------------------------------------------------------------------
1 | // Read an SBOM from disk. This expression evaluates to the SBOM document.
2 | // You can use this construct to feed a document into functions that expect
3 | // them.
4 | //
5 | // To store an SBOM document in a variable use the native bind function
6 | // the CEL runtime:
7 | //
8 | // cel.bind(myvar, bomshell.LoadSBOM("examples/curl.spdx.json"), myvar)
9 | //
10 | bomshell.LoadSBOM("examples/curl.spdx.json")
11 |
--------------------------------------------------------------------------------
/.chainguard/source.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Chainguard, Inc
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | spec:
5 | authorities:
6 | - keyless:
7 | url: https://fulcio.sigstore.dev
8 | identities:
9 | - subjectRegExp: .+@chainguard.dev$
10 | issuer: https://accounts.google.com
11 | ctlog:
12 | url: https://rekor.sigstore.dev
13 | - key:
14 | # Allow commits signed by Github (merge commits)
15 | kms: https://github.com/web-flow.gpg
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 | dist
23 |
--------------------------------------------------------------------------------
/.github/workflows/go-build-and-test.yaml:
--------------------------------------------------------------------------------
1 | name: build-and-test
2 |
3 | on:
4 | pull_request:
5 | branches: [ "main" ]
6 |
7 | jobs:
8 |
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
13 |
14 | - name: Set up Go
15 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
16 | with:
17 | go-version: 1.21
18 | check-latest: true
19 | cache: true
20 |
21 | - name: Test
22 | run: |
23 | go get -d ./...
24 | go test -v ./...
25 |
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yaml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | pull_request:
4 | branches:
5 | - main
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | golangci:
12 | name: lint
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
16 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
17 | with:
18 | go-version: "1.21"
19 | check-latest: true
20 | cache: true
21 | - name: Run golangci-lint
22 | uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
23 |
--------------------------------------------------------------------------------
/tutorial/04.the-workbench.md:
--------------------------------------------------------------------------------
1 | # 4. The Interactive Workbench
2 |
3 | The `bomshell` CLI has an interactive workbench that lets users load SBOMs and
4 | work with the files directly in the terminal.
5 |
6 | This mode is in super-early-alpha-experimental-primitive state as of this
7 | writing so you can skip this chapter of the tutorial. It will be completed
8 | as the workbench is more solid and the UI is more defined.
9 |
10 | To launch the interactive mode run:
11 |
12 | ```
13 | bomshell interactive
14 | ```
15 |
16 | This section of the tutorial is also here to let the reader know that the `Esc`
17 | key is the way to exit the workbench :)
18 |
19 | _this section to be completed_
20 |
--------------------------------------------------------------------------------
/pkg/loader/loader.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package loader
5 |
6 | import (
7 | "fmt"
8 | "io"
9 | "os"
10 |
11 | "github.com/bom-squad/protobom/pkg/reader"
12 | "github.com/bom-squad/protobom/pkg/sbom"
13 | )
14 |
15 | func ReadSBOM(stream io.ReadSeekCloser) (*sbom.Document, error) {
16 | r := reader.New()
17 | doc, err := r.ParseStream(stream)
18 | if err != nil {
19 | return nil, fmt.Errorf("parsing SBOM: %w", err)
20 | }
21 |
22 | return doc, nil
23 | }
24 |
25 | func OpenFile(path string) (*os.File, error) {
26 | f, err := os.Open(path)
27 | if err != nil {
28 | return nil, fmt.Errorf("opening SBOM file: %w", err)
29 | }
30 | return f, nil
31 | }
32 |
--------------------------------------------------------------------------------
/.bom.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | license: Apache-2.0
3 | name: bomshell
4 | creator:
5 | person: Chainguard, Inc
6 | tool: bom
7 |
8 | artifacts:
9 | - type: directory
10 | source: ../
11 | license: Apache-2.0
12 | gomodules: true
13 |
14 | - type: file
15 | source: bomshell-windows-amd64.exe
16 | license: Apache-2.0
17 | gomodules: true
18 |
19 | - type: file
20 | source: bomshell-darwin-arm64
21 | license: Apache-2.0
22 | gomodules: true
23 |
24 | - type: file
25 | source: bomshell-darwin-amd64
26 | license: Apache-2.0
27 | gomodules: true
28 |
29 | - type: file
30 | source: bomshell-linux-amd64
31 | license: Apache-2.0
32 | gomodules: true
33 |
34 | - type: file
35 | source: bomshell-linux-arm64
36 | license: Apache-2.0
37 | gomodules: true
38 |
39 | - type: file
40 | source: bomshell-linux-arm
41 | license: Apache-2.0
42 | gomodules: true
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/pkg/shell/implementation_test.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestReadRecipeFile(t *testing.T) {
11 | for uCase, tc := range map[string]struct {
12 | data []byte
13 | expected string
14 | shouldErr bool
15 | }{
16 | "single line:": {[]byte("sboms[0].files().ToDocument()"), "sboms[0].files().ToDocument()\n", false},
17 | "multiline": {[]byte("// Here are some comments!\nsboms[0].files().ToDocument()"), "// Here are some comments!\nsboms[0].files().ToDocument()\n", false},
18 | "shebang": {[]byte("#!/usr/bin/bomshell\nsboms[0].files().ToDocument()"), "sboms[0].files().ToDocument()\n", false},
19 | "null string": {[]byte(""), "", true},
20 | "just shebang": {[]byte("#!/usr/bin/bomshell\n"), "", true},
21 | } {
22 | i := DefaultBomshellImplementation{}
23 | buf := bytes.NewBuffer(tc.data)
24 | res, err := i.ReadRecipeFile(buf)
25 | if tc.shouldErr {
26 | require.Error(t, err, uCase)
27 | continue
28 | }
29 |
30 | require.Equal(t, tc.expected, res, uCase)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/shell/runner_implementation.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package shell
5 |
6 | import (
7 | "fmt"
8 | "io"
9 |
10 | "github.com/google/cel-go/cel"
11 | )
12 |
13 | type RunnerImplementation interface {
14 | ReadStream(io.Reader) (string, error)
15 | Compile(*cel.Env, string) (*cel.Ast, error)
16 | }
17 |
18 | type defaultRunnerImplementation struct{}
19 |
20 | func (dri *defaultRunnerImplementation) ReadStream(reader io.Reader) (string, error) {
21 | // Read all the stream into a string
22 | contents, err := io.ReadAll(reader)
23 | if err != nil {
24 | return "", fmt.Errorf("reading stram code: %w", err)
25 | }
26 | return string(contents), nil
27 | }
28 |
29 | func (dri *defaultRunnerImplementation) Compile(env *cel.Env, code string) (*cel.Ast, error) {
30 | // Run the compilation step
31 | ast, iss := env.Compile(code)
32 | if iss.Err() != nil {
33 | return nil, fmt.Errorf("compilation error: %w", iss.Err())
34 | }
35 | return ast, nil
36 | }
37 |
38 | // func (dri *defaultRunnerImplementation) Evaluate(env )
39 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Example bomshell programs
2 |
3 | This directory contains a number of example programs that can be used to
4 | understand how bomshell works. Each program is simple enough to demo a
5 | single feature of bomshell.
6 |
7 | The following list has a summary of the files, open each `.cel` file to
8 | read the full documentation of the example and instructions on how to run it.
9 |
10 | ## Example List
11 |
12 | Example SBOMs used to run these examples are also found in this directory.
13 |
14 | | File | Description |
15 | | --- | --- |
16 | | [compose.cel](compose.cel) | Example of SBOM composition using `RelateNodeListAtID()` |
17 | | [files.cel](files.cel) | Generate a new SBOM containing only the files found in an SBOM. |
18 | | [packages.cel](packages.cel) | Generate a new SBOM containing only the packages found in an SBOM. |
19 | | [loadsbom.cel](loadsbom.cel) | Demo of SBOM loading directly from bomshell. |
20 | | [nodesbypurltype.cel](nodesbypurltype.cel) | Example showing how to extract all nodes of a certain purl type. |
21 | | [getnodebyid.cel](getnodebyid.cel) | Demo querying an SBOM for a specific node. |
22 |
23 | If you'd like to see more examples here file an issue, we'de be happy to create
24 | more!
25 |
--------------------------------------------------------------------------------
/release/ldflags.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
4 | # SPDX-License-Identifier: Apache-2.0
5 |
6 | set -o errexit
7 | set -o nounset
8 | set -o pipefail
9 |
10 | # Output LDFlAGS for a given environment. LDFLAGS are applied to all go binary
11 | # builds.
12 | #
13 | # Args: env
14 | function ldflags() {
15 | local GIT_VERSION=$(git describe --tags --always --dirty)
16 | local GIT_COMMIT=$(git rev-parse HEAD)
17 |
18 | local GIT_TREESTATE="clean"
19 | if [[ $(git diff --stat) != '' ]]; then
20 | GIT_TREESTATE="dirty"
21 | fi
22 |
23 | local DATE_FMT="+%Y-%m-%dT%H:%M:%SZ"
24 | local BUILD_DATE=$(date "$DATE_FMT")
25 | local SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
26 | if [ $SOURCE_DATE_EPOCH ]
27 | then
28 | local BUILD_DATE=$(date -u -d "@$SOURCE_DATE_EPOCH" "$DATE_FMT" 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" "$DATE_FMT" 2>/dev/null || date -u "$DATE_FMT")
29 | fi
30 |
31 | echo "-buildid= -X sigs.k8s.io/release-utils/version.gitVersion=$GIT_VERSION \
32 | -X sigs.k8s.io/release-utils/version.gitCommit=$GIT_COMMIT \
33 | -X sigs.k8s.io/release-utils/version.gitTreeState=$GIT_TREESTATE \
34 | -X sigs.k8s.io/release-utils/version.buildDate=$BUILD_DATE"
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/elements/bomshell.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package elements
5 |
6 | import (
7 | "errors"
8 | "reflect"
9 |
10 | "github.com/google/cel-go/cel"
11 | "github.com/google/cel-go/checker/decls"
12 | "github.com/google/cel-go/common/types"
13 | "github.com/google/cel-go/common/types/ref"
14 | "github.com/google/cel-go/common/types/traits"
15 | )
16 |
17 | var (
18 | BomshellObject = decls.NewObjectType("bomshell")
19 | BomshellType = cel.ObjectType("bomshell", traits.ReceiverType)
20 | )
21 |
22 | type Bomshell struct{}
23 |
24 | func (bs Bomshell) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
25 | return bs, errors.New("bomshell cannot be converted to native")
26 | }
27 |
28 | // ConvertToType implements ref.Val.ConvertToType.
29 | func (bs Bomshell) ConvertToType(typeVal ref.Type) ref.Val {
30 | switch typeVal {
31 | case DocumentType:
32 | return bs
33 | case types.TypeType:
34 | return BomshellType
35 | }
36 | return types.NewErr("type conversion error not allowed in bomshell")
37 | }
38 |
39 | // Equal implements ref.Val.Equal.
40 | func (bs Bomshell) Equal(other ref.Val) ref.Val {
41 | return types.NewErr("bomshell objects cannot be compared")
42 | }
43 |
44 | func (bs Bomshell) Type() ref.Type {
45 | return BomshellType
46 | }
47 |
48 | // Value implements ref.Val.Value.
49 | func (bs Bomshell) Value() interface{} {
50 | return bs
51 | }
52 |
--------------------------------------------------------------------------------
/tutorial/06.sbom-composition.md:
--------------------------------------------------------------------------------
1 | # 6. SBOM Composition
2 |
3 | The art of SBOM composition is the act of crafting an SBOM document on the fly,
4 | specifically with data that may come from other documents. Composing data involves
5 | ripping parts of one SBOM to copy to a new document or to enrich Nodes and possibly
6 | their graphs with fragments from the first one.
7 |
8 | SBOM composition is an important SBOM ability needed by the ecosystem as it
9 | bridges the gap created by the various tools that output SBOM data from
10 | specialized processes and analyisis. Examples of data that may need to be
11 | composed includes results from SCA tools, license classifiers, dependency data
12 | from compilers, OS package data, and more. All of these data streams can come
13 | together to create the perfect SuperSBOM.
14 |
15 | SBOM composition will generally involve querying data from a document to extract
16 | a `NodeList``, the protobom construct that captures a self-sustaining graph
17 | fragment. Resilting Nodes and NodeLists can be fed into the various SBOM
18 | composition functions which, in turn, return new Documents or NodeLists as a
19 | result of the composing operation.
20 |
21 | The following examples use a sample `nodelist` variable. This is an example
22 | NodeList that may come from a query or a method such as `sbom.packages()`.
23 |
24 | ## NodeList Set Operations
25 | ### Union
26 | ### Intersection
27 | ### Difference
28 |
29 | ## Combining Node Data
30 | ### Enriching
31 | ### Replacing
32 |
33 |
--------------------------------------------------------------------------------
/examples/bom-binary.spdx.json:
--------------------------------------------------------------------------------
1 | {
2 | "SPDXID": "SPDXRef-DOCUMENT",
3 | "name": "SBOM-SPDX-26cad80e-53bc-4b80-bead-f46651423ab7",
4 | "spdxVersion": "SPDX-2.3",
5 | "creationInfo": {
6 | "created": "2023-08-10T04:08:02Z",
7 | "creators": [
8 | "Tool: bom-devel"
9 | ],
10 | "licenseListVersion": "3.20"
11 | },
12 | "dataLicense": "CC0-1.0",
13 | "documentNamespace": "https://spdx.org/spdxdocs/k8s-releng-bom-ce7e2a06-ff1b-4603-b4ef-408f4d7b7ac0",
14 | "documentDescribes": [
15 | "SPDXRef-File-bom"
16 | ],
17 | "packages": [
18 | {
19 | "SPDXID": "SPDXRef-File-bom",
20 | "name": "bom",
21 | "versionInfo": "v0.5.1",
22 | "filesAnalyzed": false,
23 | "licenseConcluded": "Apache-2.0",
24 | "downloadLocation": "NOASSERTION",
25 | "copyrightText": "Copyright (c) 2009 The Kubernetes Authors",
26 | "checksums": [
27 | {
28 | "algorithm": "SHA1",
29 | "checksumValue": "19b0782fac90e395c48019cdda6e70c0346c05f3"
30 | },
31 | {
32 | "algorithm": "SHA256",
33 | "checksumValue": "94db7be7da0bcf000cae1c1971a0e9b14a48efab46e4980ffc91d3a3d68cd073"
34 | },
35 | {
36 | "algorithm": "SHA512",
37 | "checksumValue": "61d1b223400f17c47bb5723a6e51c3d07705e139ff90beb45469f4a9df7ac83b2e4d066ab147bee8647ca9b8b0d985a22d093153774cd5fbd4aa46caaf3e5a62"
38 | }
39 | ],
40 | "externalRefs": [
41 | {
42 | "referenceCategory": "PACKAGE-MANAGER",
43 | "referenceLocator": "pkg:generic/bom@v0.5.1",
44 | "referenceType": "purl"
45 | }
46 | ]
47 | }
48 | ],
49 | "relationships": []
50 | }
51 |
--------------------------------------------------------------------------------
/internal/cmd/interactive.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package cmd
5 |
6 | import (
7 | "fmt"
8 |
9 | "github.com/chainguard-dev/bomshell/pkg/shell"
10 | "github.com/chainguard-dev/bomshell/pkg/ui"
11 | "github.com/spf13/cobra"
12 | "sigs.k8s.io/release-utils/version"
13 | )
14 |
15 | func interactiveCommand() *cobra.Command {
16 | type interactiveOpts = struct {
17 | commandLineOptions
18 | sboms []string
19 | }
20 | opts := &interactiveOpts{
21 | sboms: []string{},
22 | }
23 | execCmd := &cobra.Command{
24 | PersistentPreRunE: initLogging,
25 | Short: "Launch bomshell interactive workbench (experimental)",
26 | Long: `bomshell interactive sbom.spdx.json → Launch the bomshell interactive workbench
27 |
28 | The interactive subcommand launches the bomshell interactive workbench
29 | `,
30 | Use: "interactive [sbom.spdx.json...] ",
31 | SilenceUsage: true,
32 | SilenceErrors: true,
33 | Version: version.GetVersionInfo().GitVersion,
34 | RunE: func(cmd *cobra.Command, args []string) error {
35 | return launchInteractive(commandLineOpts)
36 | },
37 | }
38 |
39 | commandLineOpts.AddFlags(execCmd)
40 | opts.commandLineOptions = *commandLineOpts
41 |
42 | return execCmd
43 | }
44 |
45 | func launchInteractive(_ *commandLineOptions) error {
46 | i, err := ui.NewInteractive(
47 | shell.Options{
48 | SBOMs: commandLineOpts.sboms,
49 | Format: shell.DefaultFormat,
50 | },
51 | )
52 | if err != nil {
53 | return fmt.Errorf("creating interactive env: %w", err)
54 | }
55 |
56 | // Start the interactive shell
57 | if err := i.Start(); err != nil {
58 | return fmt.Errorf("executing interactive mode: %w", err)
59 | }
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/internal/cmd/options.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package cmd
5 |
6 | import (
7 | "fmt"
8 |
9 | "github.com/bom-squad/protobom/pkg/formats"
10 | "github.com/chainguard-dev/bomshell/pkg/shell"
11 | "github.com/spf13/cobra"
12 | "sigs.k8s.io/release-utils/log"
13 | )
14 |
15 | type commandLineOptions struct {
16 | DocumentFormat string
17 | NodeListFormat string
18 | logLevel string
19 | sboms []string
20 | }
21 |
22 | type execOptions struct {
23 | ExecLine string
24 | }
25 |
26 | var execOpts = &execOptions{}
27 |
28 | var commandLineOpts = &commandLineOptions{
29 | DocumentFormat: string(formats.SPDX23JSON),
30 | NodeListFormat: "application/json",
31 | }
32 |
33 | func (o *commandLineOptions) AddFlags(cmd *cobra.Command) {
34 | cmd.PersistentFlags().StringVar(
35 | &o.DocumentFormat,
36 | "document-format",
37 | string(shell.DefaultFormat),
38 | "format to output generated documents",
39 | )
40 |
41 | cmd.PersistentFlags().StringVar(
42 | &o.NodeListFormat,
43 | "nodelist-format",
44 | commandLineOpts.NodeListFormat,
45 | "format to output nodelsits (SBOM fragments)",
46 | )
47 |
48 | cmd.PersistentFlags().StringArrayVar(
49 | &o.sboms,
50 | "sbom",
51 | commandLineOpts.sboms,
52 | "path to one or more SBOMs to load into the bomshell environment",
53 | )
54 |
55 | cmd.PersistentFlags().StringVar(
56 | &o.logLevel,
57 | "log-level",
58 | "info",
59 | fmt.Sprintf("the logging verbosity, either %s", log.LevelNames()),
60 | )
61 | }
62 |
63 | func (eo *execOptions) AddFlags(cmd *cobra.Command) {
64 | cmd.PersistentFlags().StringVarP(
65 | &eo.ExecLine,
66 | "exec",
67 | "e",
68 | "",
69 | "CEL code to execute (overrides filename)",
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 The OpenVEX Authors
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | name: Release
5 |
6 | on:
7 | push:
8 | tags:
9 | - 'v*'
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 |
15 | permissions:
16 | contents: write # needed to write releases
17 | id-token: write # needed for keyless signing
18 | packages: write # needed for pushing the images to ghcr.io
19 |
20 | env:
21 | GO111MODULE: on
22 | COSIGN_EXPERIMENTAL: "true"
23 |
24 | steps:
25 | - name: Check out code onto GOPATH
26 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
27 |
28 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
29 | with:
30 | go-version: '1.21'
31 | check-latest: true
32 |
33 | - name: Install cosign
34 | uses: sigstore/cosign-installer@9614fae9e5c5eddabb09f90a270fcb487c9f7149 # v3.3.0
35 |
36 | - name: Install bom
37 | uses: puerco/release-actions/setup-bom@6c88cda6495b4415966e61f20798fb96a9081397 # main
38 |
39 | - name: Get TAG
40 | id: get_tag
41 | run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
42 |
43 | - name: Set LDFLAGS
44 | id: ldflags
45 | run: |
46 | source ./release/ldflags.sh
47 | goflags=$(ldflags)
48 | echo "GO_FLAGS="${goflags}"" >> "$GITHUB_ENV"
49 |
50 | - name: Run GoReleaser
51 | id: run-goreleaser
52 | uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
53 | with:
54 | version: latest
55 | args: release --clean
56 | env:
57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58 | LDFLAGS: ${{ env.GO_FLAGS }}
59 |
--------------------------------------------------------------------------------
/pkg/elements/document.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package elements
5 |
6 | import (
7 | "fmt"
8 | "reflect"
9 |
10 | "github.com/bom-squad/protobom/pkg/sbom"
11 | "github.com/google/cel-go/cel"
12 | "github.com/google/cel-go/common/types"
13 | "github.com/google/cel-go/common/types/ref"
14 | )
15 |
16 | var DocumentType = cel.ObjectType("bomsquad.protobom.Document")
17 |
18 | type Document struct {
19 | *sbom.Document
20 | }
21 |
22 | // ConvertToNative implements ref.Val.ConvertToNative.
23 | func (d Document) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
24 | if reflect.TypeOf(d).AssignableTo(typeDesc) {
25 | return d, nil
26 | } else if reflect.TypeOf(d.Document).AssignableTo(typeDesc) {
27 | return d.Document, nil
28 | }
29 |
30 | return nil, fmt.Errorf("type conversion error from 'Document' to '%v'", typeDesc)
31 | }
32 |
33 | // ConvertToType implements ref.Val.ConvertToType.
34 | func (d Document) ConvertToType(typeVal ref.Type) ref.Val {
35 | switch typeVal {
36 | case DocumentType:
37 | return d
38 | // TODO(puerco): Add sbom.Doc type conversion
39 | case types.TypeType:
40 | return DocumentType
41 | }
42 | return types.NewErr("type conversion error from '%s' to '%s'", DocumentType, typeVal)
43 | }
44 |
45 | // Equal implements ref.Val.Equal.
46 | func (d Document) Equal(other ref.Val) ref.Val {
47 | _, ok := other.(Document)
48 | if !ok {
49 | return types.MaybeNoSuchOverloadErr(other)
50 | }
51 |
52 | // TODO(puerco): Moar tests like:
53 | // return types.Bool(d.URL.String() == otherDur.URL.String())
54 | return types.True
55 | }
56 |
57 | func (d Document) Type() ref.Type {
58 | return DocumentType
59 | }
60 |
61 | // Value implements ref.Val.Value.
62 | func (d Document) Value() interface{} {
63 | return d.Document
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/shell/runner.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package shell
5 |
6 | import (
7 | "fmt"
8 |
9 | "github.com/google/cel-go/cel"
10 | "github.com/google/cel-go/common/types/ref"
11 | )
12 |
13 | type Runner struct {
14 | Environment *cel.Env
15 | impl RunnerImplementation
16 | }
17 |
18 | func NewRunner() (*Runner, error) {
19 | return NewRunnerWithOptions(&defaultOptions)
20 | }
21 |
22 | func NewRunnerWithOptions(opts *Options) (*Runner, error) {
23 | env, err := createEnvironment(opts)
24 | if err != nil {
25 | return nil, err
26 | }
27 | runner := Runner{
28 | Environment: env,
29 | impl: &defaultRunnerImplementation{},
30 | }
31 |
32 | return &runner, nil
33 | }
34 |
35 | // Compile reads CEL code from string, compiles it and
36 | // returns the Abstract Syntax Tree (AST). The AST can then be evaluated
37 | // in the environment. As compilation of the AST is expensive, it can
38 | // be cached for better performance.
39 | func (r *Runner) Compile(code string) (*cel.Ast, error) {
40 | // Run the compilation step
41 | ast, err := r.impl.Compile(r.Environment, code)
42 | if err != nil {
43 | return nil, fmt.Errorf("compiling program: %w", err)
44 | }
45 | return ast, nil
46 | }
47 |
48 | // EvaluateAST evaluates a CEL syntax tree on an SBOM. Returns the program
49 | // evaluation result or an error.
50 | func (r *Runner) EvaluateAST(ast *cel.Ast, variables map[string]interface{}) (ref.Val, error) {
51 | program, err := r.Environment.Program(ast, cel.EvalOptions(cel.OptOptimize))
52 | if err != nil {
53 | return nil, fmt.Errorf("generating program from AST: %w", err)
54 | }
55 |
56 | result, _, err := program.Eval(variables)
57 | if err != nil {
58 | return nil, fmt.Errorf("evaluation error: %w", err)
59 | }
60 |
61 | return result, nil
62 | }
63 |
--------------------------------------------------------------------------------
/internal/cmd/root.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package cmd
5 |
6 | import (
7 | "fmt"
8 | "os"
9 |
10 | "github.com/sirupsen/logrus"
11 | "github.com/spf13/cobra"
12 | "sigs.k8s.io/release-utils/log"
13 | "sigs.k8s.io/release-utils/version"
14 | )
15 |
16 | const (
17 | appName = "bomshell"
18 | )
19 |
20 | func rootCommand() *cobra.Command {
21 | rootCmd := &cobra.Command{
22 | Use: appName + " [flags] [\"cel code\"| recipe.cel] [sbom.json]...",
23 | Short: appName + " [flags] [\"cel code\"| recipe.cel] [sbom.json]...",
24 | Long: longHelp,
25 | Example: appName + " recipe.cel sbom.spdx.json sbom.cdx.json",
26 | Version: version.GetVersionInfo().GitVersion,
27 | PersistentPreRunE: initLogging,
28 | RunE: func(cmd *cobra.Command, args []string) error {
29 | return cmd.Help() //nolint
30 | },
31 | // SilenceErrors: false,
32 | SilenceUsage: false,
33 | }
34 |
35 | rootCmd.SetVersionTemplate(fmt.Sprintf("%s v{{.Version}}\n", appName))
36 |
37 | execOpts.AddFlags(rootCmd)
38 | commandLineOpts.AddFlags(rootCmd)
39 |
40 | rootCmd.AddCommand(execCommand())
41 | rootCmd.AddCommand(runCommand())
42 | rootCmd.AddCommand(interactiveCommand())
43 | rootCmd.AddCommand(version.WithFont("starwars"))
44 |
45 | return rootCmd
46 | }
47 |
48 | func Execute() {
49 | if len(os.Args) > 1 {
50 | switch os.Args[1] {
51 | case "completion", "exec", "version", "interactive", "run":
52 | default:
53 | os.Args = append([]string{os.Args[0], "exec"}, os.Args[1:]...)
54 | }
55 | }
56 |
57 | if err := rootCommand().Execute(); err != nil {
58 | logrus.Fatal(err)
59 | }
60 | }
61 |
62 | func initLogging(*cobra.Command, []string) error {
63 | return log.SetupGlobalLogger(commandLineOpts.logLevel) //nolint
64 | }
65 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: Copyright 2022 Chainguard Inc
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
5 | ifeq (,$(shell go env GOBIN))
6 | GOBIN=$(shell go env GOPATH)/bin
7 | else
8 | GOBIN=$(shell go env GOBIN)
9 | endif
10 |
11 | # Set version variables for LDFLAGS
12 | GIT_VERSION ?= $(shell git describe --tags --always --dirty)
13 | GIT_HASH ?= $(shell git rev-parse HEAD)
14 | DATE_FMT = +%Y-%m-%dT%H:%M:%SZ
15 | SOURCE_DATE_EPOCH ?= $(shell git log -1 --pretty=%ct)
16 | ifdef SOURCE_DATE_EPOCH
17 | BUILD_DATE ?= $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u "$(DATE_FMT)")
18 | else
19 | BUILD_DATE ?= $(shell date "$(DATE_FMT)")
20 | endif
21 | GIT_TREESTATE = "clean"
22 | DIFF = $(shell git diff --quiet >/dev/null 2>&1; if [ $$? -eq 1 ]; then echo "1"; fi)
23 | ifeq ($(DIFF), 1)
24 | GIT_TREESTATE = "dirty"
25 | endif
26 |
27 | LDFLAGS=-buildid= -X sigs.k8s.io/release-utils/version.gitVersion=$(GIT_VERSION) \
28 | -X sigs.k8s.io/release-utils/version.gitCommit=$(GIT_HASH) \
29 | -X sigs.k8s.io/release-utils/version.gitTreeState=$(GIT_TREESTATE) \
30 | -X sigs.k8s.io/release-utils/version.buildDate=$(BUILD_DATE)
31 |
32 | ## Build
33 | .PHONY: build
34 | build: # build the binaries
35 | go build -trimpath -ldflags "$(LDFLAGS)" -o bomshell ./main.go
36 |
37 | ## Tests
38 | .PHONY: test
39 | test:
40 | go test -v ./...
41 |
42 | ## Release
43 |
44 | .PHONY: release
45 | release:
46 | LDFLAGS="$(LDFLAGS)" goreleaser release --rm-dist --timeout 120m
47 |
48 | .PHONY: snapshot
49 | snapshot:
50 | LDFLAGS="$(LDFLAGS)" goreleaser release --rm-dist --snapshot --skip-sign --skip-publish --timeout 120m
51 |
52 | .PHONY: sbom
53 | sbom:
54 | cd dist && bom generate -c ../.bom.yaml -o sbom.spdx.json --format=json
55 |
56 |
--------------------------------------------------------------------------------
/tutorial/02.the-bomshell-model.md:
--------------------------------------------------------------------------------
1 | # 2. The `bomshell` Model
2 |
3 | In essence, `bomshell` is a runtime that executes small CEL scripts or programs
4 | we call "recipes". Recipes are statements that perform operations on SBOM data
5 | and evaluate to a result. This result will often be a new SBOM (a Document in
6 | protobom lingo). Expressions in protobom recipes can also evaluate to NodeLists
7 | or even Nodes.
8 |
9 | An invocation of protobom has three main tasks:
10 |
11 | 1. It sets some options, according to defaults and user preferences
12 | 2. Preloads one or more SBOMs into the bomshell environment (not required).
13 | 3. Reads a protobom recipe and executes it.
14 |
15 | The results are output to STDOUT by default. The results format can be configured
16 | by the user or an embedding application using the bomshell format.
17 |
18 | ## The `bomshell` Runtime Environment
19 |
20 | When execution starts, the recipe logic runs in an environment that has
21 | two predefined variables:
22 |
23 | - A global `bomshell` object. This object houses the main functions that are not
24 | methods of the SBOM data elements (Documents, Nodes, etc).
25 | - A collection of SBOMs. Any SBOMs preloaded into the runtime environment are
26 | parsed and exposed in a global `sboms[]` list. The list is a numerical array
27 | and documents are indexed in the order they were defined.
28 |
29 | ## Expression Evaluation
30 |
31 | All `bomshell` functions return an SBOM data elements such as Documents or
32 | NodeLists. En expression is said to `evaluate` as a result of the last function
33 | called. If a function is called and it returns a Document, the CEL expression
34 | evaluates to the Document.
35 |
36 | The result of the evaluation will be output (to STDOUT by default). Documents
37 | can be output as standard SPDX or CycloneDX documents. NodeLists can be extracted
38 | in more data formats designed to work with data applications or converted to new
39 | Documents.
40 |
41 |
42 |
--------------------------------------------------------------------------------
/pkg/functions/utility.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package functions
5 |
6 | import (
7 | "github.com/bom-squad/protobom/pkg/sbom"
8 | "github.com/chainguard-dev/bomshell/pkg/elements"
9 | "github.com/sirupsen/logrus"
10 | )
11 |
12 | // cleanEdges removes all edges that have broken Froms and removes
13 | // any destination IDs from elements not in the NodeList.
14 | func cleanEdges(nl *elements.NodeList) {
15 | // First copy the nodelist edges
16 | newEdges := []*sbom.Edge{}
17 |
18 | // Build a catalog of the elements ids
19 | idDict := map[string]struct{}{}
20 | for i := range nl.Nodes {
21 | idDict[nl.Nodes[i].Id] = struct{}{}
22 | }
23 |
24 | // Now list all edges and rebuild the list
25 | for _, edge := range nl.Edges {
26 | newTos := []string{}
27 | if _, ok := idDict[edge.From]; !ok {
28 | continue
29 | }
30 |
31 | for _, s := range edge.To {
32 | if _, ok := idDict[s]; ok {
33 | newTos = append(newTos, s)
34 | }
35 | }
36 |
37 | if len(newTos) == 0 {
38 | continue
39 | }
40 |
41 | edge.To = newTos
42 | newEdges = append(newEdges, edge)
43 | }
44 |
45 | nl.Edges = newEdges
46 | }
47 |
48 | // reconnectOrphanNodes cleans the graph structure by reconnecting all
49 | // orphaned nodes to the top of the graph
50 | func reconnectOrphanNodes(nl *elements.NodeList) {
51 | edgeIndex := map[string]struct{}{}
52 | rootIndex := map[string]struct{}{}
53 |
54 | for _, e := range nl.NodeList.Edges {
55 | for _, t := range e.To {
56 | edgeIndex[t] = struct{}{}
57 | }
58 | }
59 |
60 | for _, id := range nl.NodeList.RootElements {
61 | rootIndex[id] = struct{}{}
62 | }
63 |
64 | for _, n := range nl.NodeList.Nodes {
65 | if _, ok := edgeIndex[n.Id]; !ok {
66 | if _, ok := rootIndex[n.Id]; !ok {
67 | nl.NodeList.RootElements = append(nl.NodeList.RootElements, n.Id)
68 | logrus.Infof("Added orphan node %s", n.Id)
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | project_name: bomshell
2 |
3 | env:
4 | - GO111MODULE=on
5 | - COSIGN_YES=true
6 |
7 | before:
8 | hooks:
9 | - go mod tidy
10 | - /bin/bash -c 'if [ -n "$(git --no-pager diff --exit-code go.mod go.sum)" ]; then exit 1; fi'
11 |
12 | builds:
13 | - id: binaries
14 | binary: bomshell-{{ .Os }}-{{ .Arch }}
15 | no_unique_dist_dir: true
16 | main: .
17 | flags:
18 | - -trimpath
19 | mod_timestamp: '{{ .CommitTimestamp }}'
20 | goos:
21 | - linux
22 | - darwin
23 | - windows
24 | goarch:
25 | - amd64
26 | - arm64
27 | - arm
28 | goarm:
29 | - '7'
30 | ignore:
31 | - goos: windows
32 | goarch: arm64
33 | - goos: windows
34 | goarch: arm
35 | - goos: windows
36 | goarch: s390x
37 | - goos: windows
38 | goarch: ppc64le
39 | - goos: linux
40 | goarch: ppc64le
41 | - goos: linux
42 | goarch: s390x
43 | ldflags:
44 | - "{{ .Env.LDFLAGS }}"
45 | env:
46 | - CGO_ENABLED=0
47 |
48 | signs:
49 | - id: sbom
50 | signature: "${artifact}.sig"
51 | certificate: "${artifact}.pem"
52 | cmd: cosign
53 | args: ["sign-blob", "--output-signature", "${artifact}.sig", "--output-certificate", "${artifact}.pem", "${artifact}"]
54 | artifacts: sbom
55 |
56 | source:
57 | enabled: true
58 | name_template: '{{ .ProjectName }}-{{ .Version }}'
59 |
60 | sboms:
61 | - documents:
62 | - "{{ .ProjectName }}-{{ .Version }}.spdx.json"
63 | cmd: bom
64 | args: ["generate", "-c", "../.bom.yaml", "-o", "{{ .ProjectName }}-{{ .Version }}.spdx.json", "--format=json"]
65 | artifacts: source
66 |
67 | archives:
68 | - format: binary
69 | name_template: "{{ .Binary }}"
70 | allow_different_binary_count: true
71 |
72 | checksum:
73 | name_template: "SHA256SUMS"
74 |
75 | snapshot:
76 | name_template: SNAPSHOT-{{ .ShortCommit }}
77 |
78 | release:
79 | prerelease: allow
80 | draft: false # allow for manual edits
81 |
--------------------------------------------------------------------------------
/pkg/elements/node.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package elements
5 |
6 | import (
7 | "fmt"
8 | "reflect"
9 |
10 | "github.com/bom-squad/protobom/pkg/sbom"
11 | "github.com/google/cel-go/cel"
12 | "github.com/google/cel-go/checker/decls"
13 | "github.com/google/cel-go/common/types"
14 | "github.com/google/cel-go/common/types/ref"
15 | )
16 |
17 | var (
18 | NodeObject = decls.NewObjectType("bomsquad.protobom.Node")
19 | NodeType = cel.ObjectType("bomsquad.protobom.Node")
20 | )
21 |
22 | type Node struct {
23 | *sbom.Node
24 | }
25 |
26 | // ConvertToNative implements ref.Val.ConvertToNative.
27 | func (n Node) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
28 | if reflect.TypeOf(n).AssignableTo(typeDesc) {
29 | return n, nil
30 | } else if reflect.TypeOf(n.Node).AssignableTo(typeDesc) {
31 | return n.Node, nil
32 | }
33 |
34 | return nil, fmt.Errorf("type conversion error from 'Node' to '%v'", typeDesc)
35 | }
36 |
37 | // ConvertToType implements ref.Val.ConvertToType.
38 | func (n Node) ConvertToType(typeVal ref.Type) ref.Val {
39 | switch typeVal {
40 | case NodeType:
41 | return n
42 | case types.TypeType:
43 | return NodeType
44 | }
45 | return types.NewErr("type conversion error from '%s' to '%s'", NodeType, typeVal)
46 | }
47 |
48 | // Equal implements ref.Val.Equal.
49 | func (n Node) Equal(other ref.Val) ref.Val {
50 | _, ok := other.(Node)
51 | if !ok {
52 | return types.MaybeNoSuchOverloadErr(other)
53 | }
54 |
55 | // TODO: Moar tests like:
56 | // return types.Bool(d.URL.String() == otherDur.URL.String())
57 | return types.True
58 | }
59 |
60 | func (n Node) Type() ref.Type {
61 | return NodeType
62 | }
63 |
64 | // Value implements ref.Val.Value.
65 | func (n Node) Value() interface{} {
66 | return n.Node
67 | }
68 |
69 | // ToNodeList returns a new NodeList with the node as the only member
70 | func (n Node) ToNodeList() *NodeList {
71 | return &NodeList{
72 | NodeList: &sbom.NodeList{
73 | Nodes: []*sbom.Node{n.Node},
74 | Edges: []*sbom.Edge{},
75 | RootElements: []string{n.Id},
76 | },
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/docs/functions.md:
--------------------------------------------------------------------------------
1 | # Functions Inventory
2 |
3 | This is an initial inventory of the functions planned for each of the three
4 | objects currently exposed to the bomshell runtime environment:
5 |
6 | | method | Return Value | Description | SBOM | NodeList | Node |
7 | | --- | --- | --- | --- | --- | --- |
8 | | files() | NodeList | Returns all nodes that are files | ✔️ | TBD | TBD |
9 | | packages() | NodeList | Returns all nodes that are files | ✔️ | TBD | TBD |
10 | |
__Node Querying Functions__ | |
11 | | nodeByID() | Node | Returns the node with the matching identifier | ✔️ | TBD | TBD |
12 | | nodesByName() | NodeList | Returns all elements whose name matches | TBD | TBD | TBD |
13 | | nodesByPurl() | NodeList | Returns all elements with a matching purl | TBD | TBD | TBD |
14 | | nodesByPurlType() | NodeList | Returns all elements whose purl is of a certain type | ✔️ | TBD | TBD |
15 | | nodesByDepth() | NodeList | Returns nodes at X degrees of separation from the root | TBD | TBD | TBD |
16 | | __Graph Fragment Querying Functions__ | |
17 | | graphByID() | NodeList | Returns the graph fragment of a Node that matches | TBD | TBD | TBD |
18 | | graphByName() | NodeList | Returns the graph fragment of elements whose name match the query | TBD | TBD | TBD |
19 | | graphByPurl() | NodeList | Returns the graph of all elements with a matching purl | TBD | TBD | TBD |
20 | | graphByPurlType() | NodeList | Returns all elements whose purl is of a certain type | TBD | TBD | TBD |
21 | | graphByDepth() | NodeList | Returns graph fragments starting at X degrees of separation from the root | TBD | TBD | TBD |
22 | |__Element Transformation__ | |
23 | | toNodeList() | NodeList | Returns a NodeList from the object | TBD | TBD | ✔️ |
24 | |__Composition Functions__ | |
25 | | add() | NodeList | Combines nodelists or nodes into a single nodelist | ✔️ | TBD | TBD |
26 | | union() | NodeList | Returns a new nodelist with elements in common | ✔️ | TBD | TBD |
27 | | union() | NodeList | Returns the nodes from a nodelist not present in the second | ✔️ | TBD | TBD |
28 | | relateAt() | NodeList | Inserts a nodelist or node at a point | TBD | TBD | N/A |
29 |
--------------------------------------------------------------------------------
/pkg/ui/subshell.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "github.com/google/cel-go/cel"
5 | "github.com/google/cel-go/common/types"
6 | "github.com/google/cel-go/common/types/ref"
7 | )
8 |
9 | type InteractiveSubshell struct{}
10 |
11 | // func buildSubshell() InteractiveSubshell {}
12 |
13 | func helpFunc(_ ...ref.Val) ref.Val {
14 | h := "Welcome to bomshell!\n"
15 | h += "--------------------\n"
16 | h += "This is the interactive mode of bomshell. In here you can run bomshell\n"
17 | h += "recipes on any loaded SBOMs. The interactive mode will render the\n"
18 | h += "evaluation results using a special human-readable renderer that outputs\n"
19 | h += "information about the returned types. For example, when a recipe \n"
20 | h += "evaluates to a NodeList, bomshell will show some data about it:\n\n"
21 | h += " protobom NodeList\n"
22 | h += " Root Elements: 1\n"
23 | h += " Number of nodes: 69 (14 packages 55 files)\n"
24 | h += " Package URL types: oci: 2 apk: 12 \n\n"
25 | h += "Each element (document, nodelist, node, etc) has its own information\n"
26 | h += "display. They are supposed to be read nby humans and we expect to\n"
27 | h += "modify them constantly with each release of bomshell. In other words,\n"
28 | h += "don't script based on the human (TTY) display\n\n"
29 | h += "Examples!\n"
30 | h += "If you have SBOMs loaded in the environment, try pasting these examples\n"
31 | h += "in the prompt below:\n\n"
32 | h += "// Print information about the first SBOM:\n"
33 | h += "sboms[0]\n\n"
34 | h += "// Query the SBOM and print data about its packages:\n"
35 | h += "sboms[0].packages()\n\n"
36 | h += "// Query a specific node in the document:\n"
37 | h += `sboms[0].NodeByID("my-node-identifier")` + "\n\n"
38 |
39 | return types.String(h)
40 | }
41 |
42 | func (subshell InteractiveSubshell) LibraryName() string {
43 | return "cel.chainguard.bomshell.interactive"
44 | }
45 |
46 | // CompileOptions
47 | func (subshell InteractiveSubshell) CompileOptions() []cel.EnvOption {
48 | return []cel.EnvOption{
49 | cel.Function(
50 | "help",
51 | cel.Overload(
52 | "help_overload", nil, cel.StringType, cel.FunctionBinding(helpFunc),
53 | ),
54 | ),
55 | }
56 | }
57 |
58 | func (subshell InteractiveSubshell) ProgramOptions() []cel.ProgramOption {
59 | return []cel.ProgramOption{}
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/functions/functions_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package functions
5 |
6 | import (
7 | "fmt"
8 | "testing"
9 |
10 | "github.com/bom-squad/protobom/pkg/sbom"
11 | "github.com/chainguard-dev/bomshell/pkg/elements"
12 | "github.com/google/cel-go/common/types"
13 | "github.com/google/cel-go/common/types/ref"
14 | "github.com/stretchr/testify/require"
15 | )
16 |
17 | // ToNodeList takes a node and returns a new NodeList
18 | // with that nodelist with the node as the only member.
19 | func TestToNodeList(t *testing.T) {
20 | for _, tc := range []struct {
21 | name string
22 | sut ref.Val
23 | }{
24 | {
25 | name: "doc",
26 | sut: &elements.Document{
27 | Document: &sbom.Document{
28 | Metadata: &sbom.Metadata{},
29 | NodeList: &sbom.NodeList{},
30 | },
31 | },
32 | },
33 | {
34 | name: "nodelist",
35 | sut: &elements.NodeList{
36 | NodeList: &sbom.NodeList{},
37 | },
38 | },
39 | {
40 | name: "node",
41 | sut: &elements.Node{
42 | Node: &sbom.Node{},
43 | },
44 | },
45 | } {
46 | tc := tc
47 | t.Run(tc.name, func(t *testing.T) {
48 | res := ToNodeList(tc.sut)
49 | require.NotNil(t, res)
50 | require.Equal(t, "elements.NodeList", fmt.Sprintf("%T", res), res)
51 | })
52 | }
53 | }
54 |
55 | func TestNodeByID(t *testing.T) {
56 | node := &sbom.Node{
57 | Id: "mynode",
58 | }
59 | nl := &sbom.NodeList{
60 | Nodes: []*sbom.Node{node},
61 | Edges: []*sbom.Edge{},
62 | RootElements: []string{},
63 | }
64 |
65 | for _, tc := range []struct {
66 | name string
67 | sut ref.Val
68 | }{
69 | {
70 | name: "doc",
71 | sut: &elements.Document{
72 | Document: &sbom.Document{
73 | NodeList: nl,
74 | },
75 | },
76 | },
77 | {
78 | name: "nodelist",
79 | sut: &elements.NodeList{
80 | NodeList: nl,
81 | },
82 | },
83 | {
84 | name: "node",
85 | sut: &elements.Node{
86 | Node: node,
87 | },
88 | },
89 | } {
90 | t.Run(tc.name, func(t *testing.T) {
91 | res := NodeByID(tc.sut, types.String("mynode"))
92 | require.NotNil(t, res)
93 | require.Equal(t, "elements.Node", fmt.Sprintf("%T", res), res)
94 | require.Equal(t, "mynode", res.Value().(*sbom.Node).Id)
95 | })
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/chainguard-dev/bomshell
2 |
3 | // replace github.com/bom-squad/protobom => ../bom-squad/protobom/
4 |
5 | go 1.21
6 |
7 | toolchain go1.21.1
8 |
9 | require (
10 | github.com/bom-squad/protobom v0.3.0
11 | github.com/charmbracelet/bubbletea v0.25.0
12 | github.com/charmbracelet/lipgloss v0.9.1
13 | github.com/sirupsen/logrus v1.9.3
14 | github.com/spf13/cobra v1.8.0
15 | github.com/stretchr/testify v1.8.4
16 | sigs.k8s.io/release-utils v0.7.7
17 | )
18 |
19 | require github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
20 |
21 | require (
22 | github.com/CycloneDX/cyclonedx-go v0.8.0 // indirect
23 | github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 // indirect
24 | github.com/atotto/clipboard v0.1.4 // indirect
25 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
26 | github.com/charmbracelet/bubbles v0.17.1
27 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
28 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
29 | github.com/davecgh/go-spew v1.1.1 // indirect
30 | github.com/google/cel-go v0.19.0
31 | github.com/google/go-cmp v0.6.0 // indirect
32 | github.com/google/uuid v1.5.0 // indirect
33 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
34 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
35 | github.com/mattn/go-isatty v0.0.18 // indirect
36 | github.com/mattn/go-localereader v0.0.1 // indirect
37 | github.com/mattn/go-runewidth v0.0.15 // indirect
38 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
39 | github.com/muesli/cancelreader v0.2.2 // indirect
40 | github.com/muesli/reflow v0.3.0 // indirect
41 | github.com/muesli/termenv v0.15.2 // indirect
42 | github.com/pmezard/go-difflib v1.0.0 // indirect
43 | github.com/rivo/uniseg v0.2.0 // indirect
44 | github.com/spdx/tools-golang v0.5.3 // indirect
45 | github.com/spf13/pflag v1.0.5 // indirect
46 | github.com/stoewer/go-strcase v1.2.0 // indirect
47 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
48 | golang.org/x/sync v0.1.0 // indirect
49 | golang.org/x/sys v0.12.0 // indirect
50 | golang.org/x/term v0.6.0 // indirect
51 | golang.org/x/text v0.9.0 // indirect
52 | google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect
53 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
54 | google.golang.org/protobuf v1.32.0
55 | gopkg.in/yaml.v3 v3.0.1 // indirect
56 | )
57 |
--------------------------------------------------------------------------------
/examples/compose.cel:
--------------------------------------------------------------------------------
1 | // This is an example of SBOM composition using bomshell.
2 | //
3 | // === BEGIN CODE ===
4 |
5 | sboms[0].RelateNodeListAtID(
6 | sboms[1].NodesByPurlType("golang"),
7 | "File-bom",
8 | "DEPENDS_ON"
9 | )
10 |
11 | // === END CODE ===
12 | //
13 | // There should be two SBOMs that can be used to test this program:
14 | // bom-binary.spdx.json
15 | // bom-github.spdx.json
16 | //
17 | // The first one (bom-binary.spdx.json) is an sbom with a single package
18 | // describing the bom binary for linux/amd64:
19 | //
20 | // 📂 SPDX Document SBOM-SPDX-26cad80e-53bc-4b80-bead-f46651423ab7
21 | // │
22 | // │ 📦 DESCRIBES 1 Packages
23 | // │
24 | // ├ bom@v0.5.1
25 | // │ └ 🔗 0 Relationships
26 | // └ 📄 DESCRIBES 0 Files
27 | //
28 | //
29 | // The second SBOM (bom-github.spdx.json) is an SPDX SBOM downloaded from the
30 | // GitHub self service SBOM feature. It describes the github respoitory housing
31 | // the code that compiled the tool above:
32 | //
33 | // 📂 SPDX Document com.github.kubernetes-sigs/bom
34 | // │
35 | // │ 📦 DESCRIBES 1 Packages
36 | // │
37 | // ├ pkg:github/kubernetes-sigs/bom@2cc9dcc83b2867047edff143905829ff9e3b98ff
38 | // │ │ 🔗 191 Relationships
39 | // │ ├ DEPENDS_ON PACKAGE pkg:npm/%40nodelib/fs.scandir@2.1.3
40 | // │ ├ DEPENDS_ON PACKAGE pkg:npm/%40nodelib/fs.stat@2.0.3
41 | // │ ├ DEPENDS_ON PACKAGE pkg:npm/%40nodelib/fs.walk@1.2.4
42 | // │ ├ DEPENDS_ON PACKAGE pkg:npm/ansi-regex@5.0.1
43 | // [ ... ]
44 | // │ ├ DEPENDS_ON PACKAGE pkg:golang/github.com/docker/docker@24.0.0+incompatible
45 | // │ ├ DEPENDS_ON PACKAGE pkg:golang/github.com/go-git/go-git/v5@5.8.1
46 | // │ ├ DEPENDS_ON PACKAGE pkg:golang/github.com/google/go-containerregistry@0.16.1
47 | // [ ... ]
48 | // │ ├ DEPENDS_ON PACKAGE pkg:githubactions/actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe
49 | // │ ├ DEPENDS_ON PACKAGE pkg:githubactions/goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7
50 | // │ ├ DEPENDS_ON PACKAGE pkg:githubactions/puerco/release-actions/setup-tejolote@6c88cda6495b4415966e61f20798fb96a9081397
51 | // [ ... ]
52 | // │
53 | // └ 📄 DESCRIBES 0 Files
54 | //
55 | //
56 | // The second SBOM contains three types of dependencies:
57 | // - npm (used in the website)
58 | // - go (the application source)
59 | // - githubactions (from the repository CI)
60 | //
61 | // The following bomshell program extracts the go dependencies from the second
62 | // SBOM and remixes them into the first to enrich its data. He resulting SBOM
63 | // describes the full dependency list of the binary.
64 | //
65 | // To run the composing example, run the following bomshell invocation:
66 | //
67 | // bomshell exec compose.cel bom-binary.spdx.json bom-github.spdx.json
68 | //
69 |
--------------------------------------------------------------------------------
/tutorial/01.the-sbom-graph.md:
--------------------------------------------------------------------------------
1 | # 1. The SBOM Graph
2 |
3 | Inside of protobom, all SBOM data is represented as a directed graph. This model
4 | is a direct inheritance from `protobom` the universal I/O layer to work
5 | with Software Bill of Materials data.
6 |
7 | `bomshell` uses the sbom graph from `protobom`, the graph is modelled to capture
8 | all data from SPDX 2.2+ and CycloneDX 1.4+ losslesly. This means that when
9 | ingesting documents from these formats is preserved regardless of their origin.
10 |
11 | The protobom model has four main elements: NodeLists, Nodes, Edges and a Document.
12 | One important notion from protobom/bomshell that needs to be grasped early is:
13 | When working with SBOM data, there may not always be a Document. Hopefully, by
14 | the end of this tutorial it will become clear why. Let's go through all elements
15 | to understand their roles.
16 |
17 | ## The `protobom` Building Blocks
18 |
19 | ### 1.1 Node
20 |
21 | A node in the protobom model is the basic element of information. A node abstracts
22 | packages and files from SPDX and components from CycloneDX. Nodes have properties
23 | which capture the different bits of SBOM data such as Names, Hashes, Licenses, etc.
24 |
25 | As their name implies, Nodes constitute the Nodes of the SBOM graph model. Ideally
26 | SBOMs will always be conected to other nodes or to the root of the Document.
27 |
28 | ### 1.2 Edges
29 |
30 | Node are bound together by edges. Edges abstract the SPDX relationships and the
31 | implicit links in the various links from the CycloneDX model (components tree,
32 | dependency graph).
33 |
34 | Edges in `protobom` are directed, they have one source and can have many
35 | destinations. They are also typed, the types in protobom are losely based on the
36 | SPDX relationship types. They are also designed to contain data. At the time
37 | of writing, they don't have properties but they will soon.
38 |
39 | ### 1.3 NodeLists
40 |
41 | The SBOM graph is contained within a NodeList. A node list is essentially a
42 | data struct that contains:
43 |
44 | - A list of Nodes
45 | - A list of Edges
46 | - A list of Root Nodes
47 |
48 | The Root Nodes mark the entry point to the graph. NodeLists can exist independent
49 | of a Document and are the mos frequent result of operations performed on the
50 | SBOM graph.
51 |
52 | ### 1.4 The Document
53 |
54 | A document is just a thin container for a NodeList with some added metadata.
55 | The document has two parts a NodeList and a Metadata struct that captures any
56 | data not related to components when a file is read by protobom.
57 |
58 | Any NodeList can be turned into a document. As all Documents have a built-in NodeList,
59 | any operation that can be performed on a NodeList can also be performed on a document.
60 |
61 |
--------------------------------------------------------------------------------
/pkg/functions/utility_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package functions
5 |
6 | import (
7 | "testing"
8 |
9 | "github.com/bom-squad/protobom/pkg/sbom"
10 | "github.com/chainguard-dev/bomshell/pkg/elements"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func TestCleanEdges(t *testing.T) {
15 | for _, tc := range []struct {
16 | sut *elements.NodeList
17 | expected *elements.NodeList
18 | }{
19 | // Edge does not need to be modified
20 | {
21 | sut: &elements.NodeList{
22 | NodeList: &sbom.NodeList{
23 | Nodes: []*sbom.Node{
24 | {Id: "node1"}, {Id: "node2"},
25 | },
26 | Edges: []*sbom.Edge{
27 | {
28 | Type: 0,
29 | From: "node1",
30 | To: []string{"node2"},
31 | },
32 | },
33 | RootElements: []string{"node1"},
34 | },
35 | },
36 | expected: &elements.NodeList{
37 | NodeList: &sbom.NodeList{
38 | Nodes: []*sbom.Node{
39 | {Id: "node1"}, {Id: "node2"},
40 | },
41 | Edges: []*sbom.Edge{
42 | {
43 | Type: 0,
44 | From: "node1",
45 | To: []string{"node2"},
46 | },
47 | },
48 | RootElements: []string{"node1"},
49 | },
50 | },
51 | },
52 | // Edge contains a broken To
53 | {
54 | sut: &elements.NodeList{
55 | NodeList: &sbom.NodeList{
56 | Nodes: []*sbom.Node{
57 | {Id: "node1"}, {Id: "node2"},
58 | },
59 | Edges: []*sbom.Edge{
60 | {
61 | Type: 0,
62 | From: "node1",
63 | To: []string{"node2", "node3"},
64 | },
65 | },
66 | RootElements: []string{"node1"},
67 | },
68 | },
69 | expected: &elements.NodeList{
70 | NodeList: &sbom.NodeList{
71 | Nodes: []*sbom.Node{
72 | {Id: "node1"}, {Id: "node2"},
73 | },
74 | Edges: []*sbom.Edge{
75 | {
76 | Type: 0,
77 | From: "node1",
78 | To: []string{"node2"},
79 | },
80 | },
81 | RootElements: []string{"node1"},
82 | },
83 | },
84 | },
85 | // Edge contains a broken From
86 | {
87 | sut: &elements.NodeList{
88 | NodeList: &sbom.NodeList{
89 | Nodes: []*sbom.Node{
90 | {Id: "node1"}, {Id: "node2"},
91 | },
92 | Edges: []*sbom.Edge{
93 | {
94 | Type: 0,
95 | From: "node3",
96 | To: []string{"node1"},
97 | },
98 | },
99 | RootElements: []string{"node1"},
100 | },
101 | },
102 | expected: &elements.NodeList{
103 | NodeList: &sbom.NodeList{
104 | Nodes: []*sbom.Node{
105 | {Id: "node1"}, {Id: "node2"},
106 | },
107 | Edges: []*sbom.Edge{},
108 | RootElements: []string{"node1"},
109 | },
110 | },
111 | },
112 | } {
113 | cleanEdges(tc.sut)
114 | require.Equal(t, tc.sut, tc.expected)
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/pkg/render/render.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package render
5 |
6 | import (
7 | "fmt"
8 | "strings"
9 |
10 | "github.com/bom-squad/protobom/pkg/sbom"
11 | "github.com/chainguard-dev/bomshell/pkg/elements"
12 | "github.com/google/cel-go/common/types"
13 | )
14 |
15 | type RendererOptions struct {
16 | ListNodes bool
17 | }
18 |
19 | type Renderer interface {
20 | Display(any) string
21 | }
22 |
23 | func NewTTY() *TTY {
24 | return &TTY{
25 | Options: RendererOptions{
26 | ListNodes: false,
27 | },
28 | }
29 | }
30 |
31 | type TTY struct {
32 | Options RendererOptions
33 | }
34 |
35 | func (tty *TTY) Display(result any) string {
36 | switch v := result.(type) {
37 | case nil:
38 | return ""
39 | case types.String:
40 | return v.Value().(string)
41 | case *elements.Document:
42 | return tty.Display(v.Document)
43 | case *elements.NodeList:
44 | return tty.Display(v.NodeList)
45 | case elements.NodeList:
46 | return tty.Display(v.NodeList)
47 | case *elements.Node:
48 | return tty.Display(v.Node)
49 | case elements.Node:
50 | return tty.Display(v.Node)
51 | case *sbom.Document:
52 | ret := "protobom Document\n"
53 | ret += fmt.Sprintf("Document ID: %s", v.Metadata.Id)
54 | ret += "\n" + tty.Display(v.NodeList)
55 | return ret
56 | case *sbom.NodeList:
57 | ret := "protobom NodeList\n"
58 | ret += fmt.Sprintf("Root Elements: %d\n", len(v.GetRootElements()))
59 | ret += fmt.Sprintf("Number of nodes: %d (%d packages %d files)\n", len(v.Nodes), numPackages(v), numFiles(v))
60 | ptypes := purlTypes(v)
61 | ret += "Package URL types: "
62 | if len(ptypes) > 0 {
63 | for t, n := range ptypes {
64 | ret += fmt.Sprintf("%s: %d ", t, n)
65 | }
66 | ret += "\n"
67 | }
68 |
69 | if tty.Options.ListNodes {
70 | for _, n := range v.Nodes {
71 | ret += fmt.Sprintf(" Node %s %s\n", n.Id, n.Purl())
72 | }
73 | }
74 | return ret
75 | case *sbom.Node:
76 | ret := "Node Information:\n"
77 | ret += fmt.Sprintf("ID: %s (%s)\n", v.Id, []string{"file", "package"}[v.Type])
78 | ret += fmt.Sprintf("Package URL: %s\n", v.Purl())
79 | ret += fmt.Sprintf("Name: %s\n", v.Name)
80 | return ret
81 | default:
82 | ret := fmt.Sprintf("type %T renderer not implemented yet!\n", result)
83 | return ret
84 | }
85 | }
86 |
87 | func purlTypes(nl *sbom.NodeList) map[string]int {
88 | purls := map[string]int{}
89 | for _, n := range nl.Nodes {
90 | p := n.Purl()
91 | if p == "" {
92 | continue
93 | }
94 | ps := strings.TrimPrefix(string(p), "pkg:/")
95 | ps = strings.TrimPrefix(ps, "pkg:")
96 |
97 | parts := strings.Split(ps, "/")
98 |
99 | if _, ok := purls[parts[0]]; !ok {
100 | purls[parts[0]] = 0
101 | }
102 | purls[parts[0]]++
103 | }
104 | return purls
105 | }
106 |
107 | func numFiles(nl *sbom.NodeList) int {
108 | c := 0
109 | for _, n := range nl.Nodes {
110 | if n.Type == sbom.Node_FILE {
111 | c++
112 | }
113 | }
114 | return c
115 | }
116 |
117 | func numPackages(nl *sbom.NodeList) int {
118 | c := 0
119 | for _, n := range nl.Nodes {
120 | if n.Type == sbom.Node_PACKAGE {
121 | c++
122 | }
123 | }
124 | return c
125 | }
126 |
--------------------------------------------------------------------------------
/pkg/shell/implementation.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package shell
5 |
6 | import (
7 | "bufio"
8 | "errors"
9 | "fmt"
10 | "io"
11 | "os"
12 | "strings"
13 |
14 | "github.com/bom-squad/protobom/pkg/reader"
15 | "github.com/bom-squad/protobom/pkg/sbom"
16 | "github.com/bom-squad/protobom/pkg/writer"
17 | "github.com/chainguard-dev/bomshell/pkg/elements"
18 | "github.com/google/cel-go/cel"
19 | "github.com/google/cel-go/common/types/ref"
20 | )
21 |
22 | type BomshellImplementation interface {
23 | Compile(*Runner, string) (*cel.Ast, error)
24 | Evaluate(*Runner, *cel.Ast, map[string]interface{}) (ref.Val, error)
25 | LoadSBOM(io.ReadSeekCloser) (*elements.Document, error)
26 | OpenFile(path string) (*os.File, error)
27 | PrintDocumentResult(Options, ref.Val, io.WriteCloser) error
28 | ReadRecipeFile(io.Reader) (string, error)
29 | }
30 |
31 | type DefaultBomshellImplementation struct{}
32 |
33 | func (di *DefaultBomshellImplementation) Compile(runner *Runner, code string) (*cel.Ast, error) {
34 | return runner.Compile(code)
35 | }
36 |
37 | func (di *DefaultBomshellImplementation) Evaluate(runner *Runner, ast *cel.Ast, variables map[string]interface{}) (ref.Val, error) {
38 | return runner.EvaluateAST(ast, variables)
39 | }
40 |
41 | func (di *DefaultBomshellImplementation) LoadSBOM(stream io.ReadSeekCloser) (*elements.Document, error) {
42 | r := reader.New()
43 | doc, err := r.ParseStream(stream)
44 | if err != nil {
45 | return nil, fmt.Errorf("parsing SBOM: %w", err)
46 | }
47 |
48 | return &elements.Document{Document: doc}, nil
49 | }
50 |
51 | func (di *DefaultBomshellImplementation) OpenFile(path string) (*os.File, error) {
52 | f, err := os.Open(path)
53 | if err != nil {
54 | return nil, fmt.Errorf("opening SBOM file: %w", err)
55 | }
56 | return f, nil
57 | }
58 |
59 | // PrintDocumentResult takes a document result from a bomshell query and outputs it
60 | // as an SBOM in the format specified in the options
61 | func (di *DefaultBomshellImplementation) PrintDocumentResult(opts Options, result ref.Val, w io.WriteCloser) error {
62 | protoWriter := writer.New(
63 | writer.WithFormat(opts.Format),
64 | )
65 |
66 | // Check to make sure the result is a document
67 | if result.Type() != elements.DocumentType {
68 | return errors.New("error printing result, value is not a document")
69 | }
70 |
71 | doc, ok := result.Value().(*sbom.Document)
72 | if !ok {
73 | return errors.New("error casting result into protobom document")
74 | }
75 |
76 | if err := protoWriter.WriteStream(doc, w); err != nil {
77 | return fmt.Errorf("writing document to stream: %w", err)
78 | }
79 | return nil
80 | }
81 |
82 | // ReadRecipeFile reads a bomshell recipe file and returns it as a string.
83 | // This function will look for a pind-bag line at the start of the file and
84 | // strip it if needed.
85 | func (di *DefaultBomshellImplementation) ReadRecipeFile(f io.Reader) (string, error) {
86 | fileScanner := bufio.NewScanner(f)
87 | fileData := ""
88 |
89 | fileScanner.Split(bufio.ScanLines)
90 |
91 | for fileScanner.Scan() {
92 | if fileData == "" && strings.HasPrefix(fileScanner.Text(), "#!") {
93 | continue
94 | }
95 | fileData += fileScanner.Text() + "\n"
96 | }
97 |
98 | if fileData == "" {
99 | return fileData, errors.New("file read resulted in zero bytes")
100 | }
101 |
102 | return fileData, nil
103 | }
104 |
--------------------------------------------------------------------------------
/pkg/elements/nodelist.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package elements
5 |
6 | import (
7 | "fmt"
8 | "reflect"
9 |
10 | "github.com/bom-squad/protobom/pkg/sbom"
11 | "github.com/google/cel-go/cel"
12 | "github.com/google/cel-go/checker/decls"
13 | "github.com/google/cel-go/common/types"
14 | "github.com/google/cel-go/common/types/ref"
15 | )
16 |
17 | var (
18 | NodeListObject = decls.NewObjectType("bomsquad.protobom.NodeList")
19 | NodeListType = cel.ObjectType("bomsquad.protobom.NodeList")
20 | )
21 |
22 | type NodeList struct {
23 | *sbom.NodeList
24 | }
25 |
26 | // ConvertToNative implements ref.Val.ConvertToNative.
27 | func (nl NodeList) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
28 | if reflect.TypeOf(nl).AssignableTo(typeDesc) {
29 | return nl, nil
30 | } else if reflect.TypeOf(nl.NodeList).AssignableTo(typeDesc) {
31 | return nl.NodeList, nil
32 | }
33 | return nil, fmt.Errorf("type conversion error from 'NodeList' to '%v'", typeDesc)
34 | }
35 |
36 | // ConvertToType implements ref.Val.ConvertToType.
37 | func (nl NodeList) ConvertToType(typeVal ref.Type) ref.Val {
38 | switch typeVal {
39 | case NodeListType:
40 | return nl
41 | case types.TypeType:
42 | return NodeListType
43 | }
44 | return types.NewErr("type conversion error from '%s' to '%s'", NodeListType, typeVal)
45 | }
46 |
47 | // Equal implements ref.Val.Equal.
48 | func (nl NodeList) Equal(other ref.Val) ref.Val {
49 | // otherDur, ok := other.(NodeList)
50 | _, ok := other.(NodeList)
51 | if !ok {
52 | return types.MaybeNoSuchOverloadErr(other)
53 | }
54 |
55 | // TODO: Moar tests like:
56 | // return types.Bool(d.URL.String() == otherDur.URL.String())
57 | return types.True
58 | }
59 |
60 | // Type implements ref.Val.Type.
61 | func (nl NodeList) Type() ref.Type {
62 | return NodeListType
63 | }
64 |
65 | // Value implements ref.Val.Value.
66 | func (nl NodeList) Value() interface{} {
67 | return nl.NodeList
68 | }
69 |
70 | func (nl NodeList) Add(incoming ref.Val) {
71 | if newNodeList, ok := incoming.(NodeList); ok {
72 | // return types.NewErr("attemtp to convert a non node")
73 | for _, n := range newNodeList.Nodes {
74 | if !nl.HasNodeWithID(n.Id) {
75 | nl.Nodes = append(nl.Nodes, n)
76 | }
77 | }
78 |
79 | for _, e := range newNodeList.Edges {
80 | nl.AddEdge(e.From, e.Type, e.To)
81 | }
82 | }
83 | }
84 |
85 | // AddEsge adds edge data to
86 | func (nl NodeList) AddEdge(from string, t sbom.Edge_Type, to []string) {
87 | for i := range nl.Edges {
88 | // If there is already an edge with the same data, just add
89 | if nl.Edges[i].From == from && nl.Edges[i].Type == t {
90 | for _, newTo := range to {
91 | add := true
92 | for _, existingTo := range nl.Edges[i].To {
93 | if existingTo == newTo {
94 | add = false
95 | break
96 | }
97 | }
98 | if !add {
99 | continue
100 | }
101 | nl.Edges[i].To = append(nl.Edges[i].To, newTo)
102 | }
103 | return
104 | }
105 | }
106 | // .. otherwise add a new edge
107 | nl.Edges = append(nl.Edges, &sbom.Edge{
108 | Type: t,
109 | From: from,
110 | To: to,
111 | })
112 | }
113 |
114 | // HasNodeWithID Returns true if the NodeList already has a node with the specified ID
115 | func (nl NodeList) HasNodeWithID(nodeID string) bool {
116 | for _, n := range nl.Nodes {
117 | if n.Id == nodeID {
118 | return true
119 | }
120 | }
121 | return false
122 | }
123 |
--------------------------------------------------------------------------------
/tutorial/05.the-language.md:
--------------------------------------------------------------------------------
1 | # 5. The language
2 |
3 | The first chapter, The SBOM graph intruduced some key concepts of the bomshell
4 | model. This section will put those concepts to work in more concrete examples.
5 |
6 | Working in bomshell means interacting with the basic protobom constructs: Nodes,
7 | Edges, Documents, etc. The CEL runtime in bomshell exposes all the public
8 | properties of the objects, this means that they are accesible from the recipe
9 | code. This expression evaluates to the document ID:
10 |
11 | ```
12 | sbom.metadata.id
13 | ```
14 |
15 | ## Methods
16 |
17 | The runtime environment defines a few global functions, the real work usually
18 | happens on methods exposed by the `protobom` building blocks. For example, the
19 | following recipe returns one node that has the identifier "MY-ID":
20 |
21 | ```
22 | sbom.GetNodeByID("MY-ID")
23 | ```
24 |
25 | `bomshell` operates on the assumption that functions return the basic protobom
26 | constructs and they can be chained after another to query and remix SBOM data:
27 |
28 | ```
29 | sbom.method1().method2().method3()
30 | ```
31 |
32 | As mentioned in the _The bomshell Model_ chapter, expressions gnerally evaluate
33 | to one of the protobom building blocks and their final result is output by
34 | protobom. Let's look at an example finding packages and files.
35 |
36 | ## Finding Packages and Files
37 |
38 | Both documents and NodeLists expose simple `.packages()` and `.files()` methods
39 | that traverse their graphs and return a new `NodeList`` built exclusively with
40 | Nodes that match packages and files respectively. For example, the following
41 | expression evaluates to a NodeList comprised of all packages in an SBOM:
42 |
43 | ```
44 | sbom.packages()
45 | ```
46 |
47 | The equivalent `.files()` method returns a NodeList with only those nodes of type
48 | file:
49 |
50 | ```
51 | sbom.files()
52 | ```
53 |
54 | Depending on the `--nodelist-format` flag, the bomshell run will output the
55 | resulting nodelist in a variety of formats.
56 |
57 | ### Converting NodeLists to Documents
58 |
59 | At any point in the bomshell recipe, NodeLists can be transformed into a new Document:
60 |
61 | ```
62 | sbom.packages().ToDocument()
63 | ```
64 |
65 | In this case, the expression evaluates to a document that is created in the fly.
66 | The output will depend on the `--document-format` setting: you can output a new
67 | SPDX or CycloneDX document. Any format supported by the protobom backend can be
68 | specified.
69 |
70 | ## More SBOM Queries
71 |
72 | ## Preloaded SBOMs
73 |
74 | A note on preloaded SBOMs.
75 |
76 | All files configured to be ingested are parsed and loaded into the magic
77 | `sboms[]` collection. This numeric array holds all preloaded SBOMs in the order
78 | they were specified. `sbom[0]` is the first one, `sbom[1]` is the second one and
79 | so on.
80 |
81 | The astute reader will have noticed that examples use the variable `sbom` instead
82 | of `sbom[0]`. This is a convenience variable preloaded with the first specified
83 | sbom. The contents of `sbom` and `sbom[0]` are pointers to the same protobom Document.
84 |
85 | ## The bomshell Variable
86 |
87 | Global functions that are not methods of the protobom Documents and Nodes are
88 | methods of the magic `bomshell` object. This global object is always created
89 | when the recipe is executed.
90 |
91 | At the time of writing the bomshell object only has one function: LoadSBOM().
92 | This function reads a file and loads an SBOM into memory at runtime.
93 |
94 | More details about the bomshell object will be filled into this chapter as
95 | the runtime matures.
96 |
--------------------------------------------------------------------------------
/docs/running.md:
--------------------------------------------------------------------------------
1 | # Running `bomshell` Recipes
2 |
3 | The plain bomshell runtime offers three ways of running recipes. In its default
4 | mode, `bomshell` tries to understand what you are trying to run by checking
5 | the values of the invocation. The default bomshell invocation is meant to be run
6 | from the command line. For a more predictable, programatic way of running `bomshell`
7 | programs see `bomshell run` below.
8 |
9 | ## The Plain `bomshell` Command
10 |
11 | Typing `bomshell` into your terminal invokes the _smart_ bomshell exec mode.
12 | In this smart mode, bomshell will try to figure out what to run. It will try
13 | to find a bomshell recipe in three ways:
14 |
15 | 1. __Recipe file.__ `bomshell` will look at the value of the first positional
16 | argument to check if it points to a file. If it does, it will load it and
17 | avaluate its contents as a bomshell program.
18 |
19 | 1. __Inline bomshell code.__ If the first positional argument is not a file,
20 | bomshell will try to execute its contents as CEL bomshell code.
21 |
22 | 1. __The `-e|--execute` Flag.__ If the `--execute` flag is defined, bomshell will
23 | read its contents and use it as the recipe to run. All positional arguments will
24 | be interpreted as paths of SBOMs to run into the runtime environment.
25 |
26 | ## `bomshell run`
27 |
28 | When using bomshell in scripts or other automated environments, the recommended
29 | subcomand is `bomshell run`. This subcomand will not try to interpret arguments
30 | or flags. Positional arguments will always be interpreted in the same fashion:
31 |
32 | * The first positional argument is the `bomshell` file to run.
33 | * Any other arguments will be interpreted as SBOMs to preload and expose in the
34 | runtime environment.
35 |
36 | ### Exmaple:
37 |
38 | ```
39 | bomshell run myrecipe.cel sbom1.spdx.json sbom2.cdx.json
40 | ```
41 |
42 | ## Reading and Piping SBOMs into `bomshell`
43 |
44 | The CLI supports three ways of preloading SBOMs, plus a fourth one at run time.
45 |
46 | 1. First by passing the paths of the documents in positional arguments:
47 |
48 | ```
49 | bomshell 'sbom.files()' sbom1.cdx.json
50 | ```
51 |
52 | 2. Next, using the `--sbom` flag. The following command is equivalent to the previous
53 | example:
54 |
55 | ```
56 | bomshell 'sbom.files()' --sbom=sbom1.cdx.json
57 | ```
58 |
59 | 3. Finally, piping the SBOM to `bomshell`'s STDIN. Both the `run` subcommand and
60 | the default `bomshell` mode support piping SBOMs to the runtime. Here is an example:
61 |
62 | ```
63 | cat sbom.spdx.json | bomshell 'sbom.packages().ToDocument()'
64 | ```
65 |
66 | For convenience, a global `sbom` variable is always available. It stores the
67 | first SBOM loaded into the runtime.
68 |
69 | ## Loading SBOMs at Runtime
70 |
71 | The global `bomshell` object exposed in the envrionment has a `LoadSBOM()` method
72 | which can be used to load more documents at runtime. SBOMs loaded using `LoadSBOM()`
73 | can be stored in the `sboms` collection or bound to new variables. Here is an example:
74 |
75 | ```
76 | bomshell.LoadSBOM("myfile.spdx.json").Files()s
77 | ```
78 |
79 | ## The SBOM Collection: Ordering
80 |
81 | Al SBOMs preloaded at runtime are loaded and available at runtime in the global
82 | `sboms` collection. This global variable is a map, integer based where all SBOMs
83 | are stored in the order they were defined.
84 |
85 | The order SBOMs are stored in the `sboms` array is the following:
86 |
87 | 1. SBOMs piped to `bomshell`'s STDIN. If a file was piped to the bomshell process,
88 | it will always be `sboms[0]`.
89 | 1. SBOMs from positional arguments. The next entries in the SBOM collection will
90 | be any SBOMs defined as positional arguments.
91 | 1. Files defined using `--sbom`. Finally any SBOMs defined using the `--sbom` flag
92 | will be the last ones appended to the global SBOM collection.
93 |
94 | Documents loaded can also be reflected in variables outside of the collection.
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 💣🐚 bomshell
2 |
3 | An SBOM query language and associated utilities to work with data in any format.
4 |
5 | `bomshell` is a runtime environment designed to evaluate expressions, called
6 | _recipes_, that operate on the SBOM graph. bomshell recipes can extract,
7 | rearrange and remix data from SBOMs in any format, making SBOM composition a
8 | reality.
9 |
10 | ### __⚠️ Experimental Notice ⚠️__
11 |
12 | `bomshell` is evolving rapidly but it should still be considered pre-release software. The language
13 | is still incomplete and changing constantly.
14 |
15 | ## SBOM Querying and Remixing Examples
16 |
17 | In essence, a bomshell invocation parses a set of SBOMs and executes a recipe.
18 | At runrime, the preloaded SBOMs are accesible to the running program from the
19 | bomshell environment. For more details be sure to check out the
20 | [`bomshell` tutorial](tutorial/) and the
21 | [examples directory](examples/).
22 |
23 | ### Extract Files and Packages from an SBOM
24 |
25 | This example reads an SBOM, extracts its files and returns a new document
26 | with no packages, only those files:
27 |
28 | ```
29 | bomshell -e 'sbom.files().ToDocument()' mysbom.spdx.json
30 | ```
31 |
32 | This recipe the same but with nodes that are package data:
33 |
34 | ```
35 | bomshell -e 'sbom.packages().ToDocument()' mysbom.spdx.json
36 | ```
37 |
38 | ### Multiformat Support
39 |
40 | `bomshell` can read any SBOM format (that `protobom` supports). By default,
41 | output is written as SPDX 2.3 but it can also be rendered to any format:
42 |
43 | ```
44 | bomshell --document-format="application/vnd.cyclonedx+json;version=1.4" \
45 | --execute 'sbom.packages().ToDocument()' mysbom.spdx.json
46 | ```
47 |
48 | Reading an SBOM into bomshell and writing it to another format essentially
49 | converts it into another format:
50 |
51 | ```
52 | bomshell --document-format="application/vnd.cyclonedx+json;version=1.4" \
53 | --execute 'sbom' mysbom.spdx.json
54 | ```
55 |
56 | ### Querying SBOM Data
57 |
58 | bomshell is still very young 👶🏽 but it already offers a few functions and methods
59 | to query SBOM data. The following example extracts all go packages from an SBOM:
60 |
61 | ```
62 | bomshell -e 'sbom.NodesByPurlType("golang")' mysbom.spdx.json
63 | ```
64 |
65 | Specific nodes can be looked up by ID too:
66 |
67 | ```
68 | bomshell -e 'sbom.NodeByID("com.github.kubernetes-kubectl")' mysbom.spdx.json
69 | ```
70 |
71 | ### SBOM Composition
72 |
73 | Loaded SBOMs are accessible through the `sbom[]` array. Nodes in
74 | a document can be augmented or replaced. New graph sections can
75 | be remixed into a point in a document graph.
76 |
77 | The following recipe extracts the npm packages from one SBOM and
78 | remixes them as dependencies of a binary in the other:
79 |
80 | ```
81 | bomshell -e 'sbom[0].RelateNodeListAtID(sbom[1].NodesByPurlType("npm"), "my-binary", "DEPENDS_ON)' \
82 | --sbom=sbom1.spdx.json \
83 | --sbom=sbom2.cdx.json
84 | ```
85 |
86 | Note in the previous example that each SBOM is in a different format. Remixing
87 | from different makes `bomshell` a powerful tool to work with any SBOM, tools can specialize in what they do best and bomshell
88 | can compose documents assembled from multiple sources of
89 | data.
90 |
91 | ## The `bomshell` Core
92 |
93 | bomshell recipes are written in CEL
94 | ([Common Expression Language](https://github.com/google/cel-spec))
95 | making the runtime small and embeddable in other applications.
96 |
97 | The backing library of Bomshell is
98 | [`protobom` the universal Software Bill of Materials I/O library ](https://github.com/bom-squad/protobom).
99 | The bomshell runtime reads SBOMs and exposes the protobom
100 | data graph to the CEL environment, emulating some methods and adding
101 | some of its own.
102 |
103 | Just as its core components, bomshell is open source, released under the
104 | Apache 2.0 license.
105 |
--------------------------------------------------------------------------------
/internal/cmd/run.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package cmd
5 |
6 | import (
7 | "errors"
8 | "fmt"
9 | "io"
10 | "os"
11 |
12 | "github.com/bom-squad/protobom/pkg/formats"
13 | "github.com/chainguard-dev/bomshell/pkg/shell"
14 | "github.com/sirupsen/logrus"
15 | "github.com/spf13/cobra"
16 | "sigs.k8s.io/release-utils/version"
17 | )
18 |
19 | func runCommand() *cobra.Command {
20 | runCmd := &cobra.Command{
21 | PersistentPreRunE: initLogging,
22 | Short: "Run bomshell recipe files",
23 | Example: "bomshell run program.cel [sbom.spdx.json]...",
24 | Long: appName + ` run recipe.cel sbom.spdx.json → Execute a bomshell program
25 |
26 | The exec subcommand executes a cell program from a file and outputs the result.
27 |
28 | bomshell expects the program in the first positional argument. The rest of
29 | arguments hold paths to SBOMs which will be preloaded and made available in
30 | the runtime environment (see the --sbom flag too).
31 | `,
32 | Use: "run",
33 | Version: version.GetVersionInfo().GitVersion,
34 | SilenceUsage: false,
35 | SilenceErrors: true,
36 | RunE: func(cmd *cobra.Command, args []string) error {
37 | if len(args) == 0 {
38 | cmd.Help() //nolint:errcheck
39 | return errors.New("no cel program specified")
40 | }
41 |
42 | sbomPaths := []string{}
43 | if len(args) > 1 {
44 | sbomPaths = append(sbomPaths, args[1:]...)
45 | }
46 |
47 | return runFile(
48 | commandLineOpts, args[0], append(sbomPaths, commandLineOpts.sboms...),
49 | )
50 | },
51 | }
52 | execOpts.AddFlags(runCmd)
53 | commandLineOpts.AddFlags(runCmd)
54 |
55 | return runCmd
56 | }
57 |
58 | // buildShell creates the bomshell environment, preconfigured with the defined
59 | // options. All SBOMs in the sbomList variable will be read and exposed in the
60 | // runtime environment.
61 | func buildShell(opts *commandLineOptions, sbomList []string) (*shell.Bomshell, error) {
62 | bomshell, err := shell.NewWithOptions(shell.Options{
63 | SBOMs: sbomList,
64 | Format: formats.Format(opts.DocumentFormat),
65 | })
66 | if err != nil {
67 | return nil, fmt.Errorf("creating bomshell: %w", err)
68 | }
69 | return bomshell, nil
70 | }
71 |
72 | // runFile creates and configures a bomshell instance to run a recipe from a file
73 | func runFile(opts *commandLineOptions, recipePath string, sbomList []string) error {
74 | bomshell, err := buildShell(opts, sbomList)
75 | if err != nil {
76 | return err
77 | }
78 |
79 | result, err := bomshell.RunFile(recipePath)
80 | if err != nil {
81 | return fmt.Errorf("executing program: %w", err)
82 | }
83 |
84 | return bomshell.PrintResult(result, os.Stdout) //nolint
85 | }
86 |
87 | // runCode creates and configures a bomshell instance to run a recipe from a string
88 | func runCode(opts *commandLineOptions, celCode string, sbomList []string) error {
89 | bomshell, err := buildShell(opts, sbomList)
90 | if err != nil {
91 | return err
92 | }
93 | result, err := bomshell.Run(celCode)
94 | if err != nil {
95 | return fmt.Errorf("executing program: %w", err)
96 | }
97 |
98 | return bomshell.PrintResult(result, os.Stdout) //nolint
99 | }
100 |
101 | // testStdin check to see if STDIN can be opened for reading. If it can, then
102 | // this function will read all the input to a file and return the path
103 | func testStdin() (string, error) {
104 | fi, err := os.Stdin.Stat()
105 | if err != nil {
106 | return "", fmt.Errorf("checking stdin for data: %w", err)
107 | }
108 | if (fi.Mode() & os.ModeCharDevice) != 0 {
109 | return "", nil
110 | }
111 |
112 | f, err := os.CreateTemp("", "protobom-input-*")
113 | if err != nil {
114 | return "", fmt.Errorf("opening temporary file: %w", err)
115 | }
116 |
117 | if _, err := io.Copy(f, os.Stdin); err != nil {
118 | os.Remove(f.Name())
119 | return "", fmt.Errorf("copying STDIN to temporary file: %w", err)
120 | }
121 |
122 | logrus.Infof("buffered STDIN to %s", f.Name())
123 |
124 | return f.Name(), nil
125 | }
126 |
--------------------------------------------------------------------------------
/pkg/shell/shell.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package shell
5 |
6 | import (
7 | "fmt"
8 | "io"
9 |
10 | "github.com/bom-squad/protobom/pkg/formats"
11 | "github.com/chainguard-dev/bomshell/pkg/elements"
12 | "github.com/google/cel-go/cel"
13 | "github.com/google/cel-go/common/types/ref"
14 | "github.com/sirupsen/logrus"
15 | )
16 |
17 | const (
18 | protoDocumentType = "bomsquad.protobom.Document"
19 | DefaultFormat = formats.SPDX23JSON
20 | )
21 |
22 | type Options struct {
23 | SBOMs []string
24 | Format formats.Format
25 | EnvOptions []cel.EnvOption
26 | }
27 |
28 | var defaultOptions = Options{
29 | Format: DefaultFormat,
30 | }
31 |
32 | type Bomshell struct {
33 | Options Options
34 | runner *Runner
35 | impl BomshellImplementation
36 | }
37 |
38 | func New() (*Bomshell, error) {
39 | return NewWithOptions(defaultOptions)
40 | }
41 |
42 | func NewWithOptions(opts Options) (*Bomshell, error) {
43 | runner, err := NewRunnerWithOptions(&opts)
44 | if err != nil {
45 | return nil, fmt.Errorf("creating runner: %w", err)
46 | }
47 | return &Bomshell{
48 | Options: opts,
49 | runner: runner,
50 | impl: &DefaultBomshellImplementation{},
51 | }, nil
52 | }
53 |
54 | // RunFile runs a bomshell recipe from a file
55 | func (bs *Bomshell) RunFile(path string) (ref.Val, error) {
56 | f, err := bs.impl.OpenFile(path)
57 | if err != nil {
58 | return nil, fmt.Errorf("opening file: %w", err)
59 | }
60 |
61 | defer f.Close()
62 |
63 | data, err := bs.impl.ReadRecipeFile(f)
64 | if err != nil {
65 | return nil, fmt.Errorf("reading program data: %w", err)
66 | }
67 |
68 | return bs.Run(data)
69 | }
70 |
71 | func (bs *Bomshell) Run(code string) (ref.Val, error) {
72 | // Variables that wil be made available in the CEL env
73 | vars := map[string]interface{}{}
74 | sbomList := []*elements.Document{}
75 |
76 | // Load defined SBOMs into the sboms array
77 | if len(bs.Options.SBOMs) > 0 {
78 | for _, sbomSpec := range bs.Options.SBOMs {
79 | // TODO(puerco): Split for varname
80 | f, err := bs.impl.OpenFile(sbomSpec)
81 | if err != nil {
82 | return nil, fmt.Errorf("opening SBOM file: %w", err)
83 | }
84 |
85 | doc, err := bs.impl.LoadSBOM(f)
86 | if err != nil {
87 | return nil, fmt.Errorf("loading SBOM: %w", err)
88 | }
89 | logrus.Debugf("Loaded %s", sbomSpec)
90 |
91 | sbomList = append(sbomList, doc)
92 | }
93 | }
94 |
95 | // Add the SBOM list to the runtim environment
96 | vars["sboms"] = sbomList
97 | if len(sbomList) > 0 {
98 | vars["sbom"] = sbomList[0]
99 | }
100 | // Add the default bomshell object
101 | vars["bomshell"] = elements.Bomshell{}
102 |
103 | ast, err := bs.impl.Compile(bs.runner, code)
104 | if err != nil {
105 | return nil, fmt.Errorf("compiling program: %w", err)
106 | }
107 |
108 | result, err := bs.impl.Evaluate(bs.runner, ast, vars)
109 | if err != nil {
110 | return nil, fmt.Errorf("evaluating: %w", err)
111 | }
112 |
113 | return result, nil
114 | }
115 |
116 | func (bs *Bomshell) LoadSBOM(path string) (*elements.Document, error) {
117 | f, err := bs.impl.OpenFile(path)
118 | if err != nil {
119 | return nil, fmt.Errorf("opening SBOM file: %w", err)
120 | }
121 |
122 | doc, err := bs.impl.LoadSBOM(f)
123 | if err != nil {
124 | return nil, fmt.Errorf("loading SBOM: %w", err)
125 | }
126 |
127 | return doc, nil
128 | }
129 |
130 | // PrintResult writes result into writer w according to the format
131 | // configured in the options
132 | func (bs *Bomshell) PrintResult(result ref.Val, w io.WriteCloser) error {
133 | // TODO(puerco): Check if result is an error
134 | if result == nil {
135 | fmt.Fprint(w, "")
136 | }
137 |
138 | switch result.Type() {
139 | case elements.DocumentType:
140 | if err := bs.impl.PrintDocumentResult(bs.Options, result, w); err != nil {
141 | return fmt.Errorf("printing results: %w", err)
142 | }
143 | return nil
144 | default:
145 | fmt.Printf("TMPRENDER:\nvalue: %v (%T)\n", result.Value(), result)
146 | return nil
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/internal/cmd/exec.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package cmd
5 |
6 | import (
7 | "errors"
8 | "fmt"
9 | "os"
10 |
11 | "github.com/spf13/cobra"
12 | "sigs.k8s.io/release-utils/version"
13 | )
14 |
15 | var longHelp = `💣🐚 bomshell: An SBOM Programming Interface
16 |
17 | bomshell is a programmable shell to work with SBOM (Software Bill of Materials)
18 | data using CEL expressions (Common Expression Language). bomshell can query and
19 | remix SBOM data, split data into new documents and more.
20 |
21 | The main bomshell command can run bomshell scripts (called recipes) from files,
22 | or from the command line positional arguments.
23 |
24 | # Execute recipe.cel, preloading SBOMs
25 | bomshell recipe.cel sbom1.json sbom2.json ....
26 |
27 | # Execute a bomshell recipe from the command line
28 | bomshell 'sbom.Packages()' sbom.json
29 |
30 | # Pipe an SBOM through bomshell, extract its file:
31 | cat sbom.json | bomshell 'sbom.Files()'
32 |
33 | The root command tries to be smart when looking at arguments. It will look for
34 | the CEL code to execute in the value of the -e|--execute flag. If it is not a file,
35 | it will check the first positional argument:
36 |
37 | - If arg[0] is a file, bomshell will try to run it as a recipe.
38 | - If it is not a file, bomshell will treat the value as a CEL recipe and run it.
39 |
40 | If a recipe is defined with -e|--execute, bomshell will treat all positional
41 | arguments as sboms to be read and preloaded into the runtime environment:
42 |
43 | # Run a program with --execute:
44 | bomshell --execute="sbom.GetNodeByID("my-package")" sbom.json
45 |
46 | For a more predictable runner, check the bomshell run subcommand.
47 |
48 | `
49 |
50 | func execCommand() *cobra.Command {
51 | execCmd := &cobra.Command{
52 | Short: "Default execution mode (hidden)",
53 | Long: longHelp,
54 | Version: version.GetVersionInfo().GitVersion,
55 | Use: "exec",
56 | SilenceUsage: true,
57 | SilenceErrors: true,
58 | Hidden: true,
59 | RunE: func(cmd *cobra.Command, args []string) error {
60 | // List of SBOMs to prelaod
61 | sbomPaths := []string{}
62 |
63 | // If there is an SBOM piped through STDIN, it will always
64 | // be SBOM #0 in our list
65 | stdinSBOM, err := testStdin()
66 | if err != nil {
67 | return fmt.Errorf("checking STDIN for a piped SBOM")
68 | }
69 |
70 | if stdinSBOM != "" {
71 | sbomPaths = append(sbomPaths, stdinSBOM)
72 | defer os.Remove(stdinSBOM)
73 | }
74 |
75 | // If no file was piped and no args, then print help and exit
76 | if len(args) == 0 && execOpts.ExecLine == "" {
77 | return cmd.Help() //nolint
78 | }
79 |
80 | // Case 1: Run snippet from the --execute flag
81 | if execOpts.ExecLine != "" {
82 | sbomPaths = append(sbomPaths, args...)
83 | sbomPaths = append(sbomPaths, commandLineOpts.sboms...)
84 | if err := runCode(commandLineOpts, execOpts.ExecLine, sbomPaths); err != nil {
85 | return fmt.Errorf("running code snippet: %w", err)
86 | }
87 | return nil
88 | }
89 |
90 | // The next two cases take SBOMs from arg[1] on
91 | if len(args) > 1 {
92 | sbomPaths = append(sbomPaths, args[1:]...)
93 | }
94 |
95 | // Case 2: If the first argument is not a file, then we assume it is code
96 | if _, err := os.Stat(args[0]); errors.Is(err, os.ErrNotExist) {
97 | // TODO(puerco): Implemnent code to check if args[0] is code :D
98 | if err := runCode(commandLineOpts, args[0], append(sbomPaths, commandLineOpts.sboms...)); err != nil {
99 | return fmt.Errorf("running code snippet: %w", err)
100 | }
101 | return nil
102 | }
103 |
104 | // Case 3: First argument is the recipe file
105 | if err := runFile(commandLineOpts, args[0], append(sbomPaths, commandLineOpts.sboms...)); err != nil {
106 | return fmt.Errorf("executing recipe: %w", err)
107 | }
108 | return nil
109 | },
110 | }
111 |
112 | execOpts.AddFlags(execCmd)
113 | commandLineOpts.AddFlags(execCmd)
114 | return execCmd
115 | }
116 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | run:
3 | concurrency: 6
4 | deadline: 5m
5 | issues:
6 | exclude-rules:
7 | # counterfeiter fakes are usually named 'fake_.go'
8 | - path: fake_.*\.go
9 | linters:
10 | - gocritic
11 | - golint
12 | - dupl
13 |
14 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50.
15 | max-issues-per-linter: 0
16 |
17 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
18 | max-same-issues: 0
19 | linters:
20 | disable-all: true
21 | enable:
22 | - asciicheck
23 | - bodyclose
24 | # - depguard
25 | - dogsled
26 | - dupl
27 | - durationcheck
28 | - errcheck
29 | # - goconst Disabled temporarily as we need to const away all the relationships
30 | - gocritic
31 | - gocyclo # Disables until some functions are optimized
32 | # - godox
33 | - gofmt
34 | - gofumpt
35 | - goheader
36 | - goimports
37 | # - gomoddirectives
38 | - gomodguard
39 | - goprintffuncname
40 | - gosec
41 | - gosimple
42 | - govet
43 | - importas
44 | - ineffassign
45 | - makezero
46 | - misspell
47 | - nakedret
48 | - nolintlint
49 | - prealloc
50 | - predeclared
51 | - promlinter
52 | # - revive Disabled for now as we have lots of uppercase consts
53 | - staticcheck
54 | - stylecheck
55 | - typecheck
56 | - unconvert
57 | - unparam
58 | - unused
59 | - whitespace
60 | # - cyclop
61 | # - errorlint
62 | # - exhaustive
63 | # - exhaustivestruct
64 | # - exportloopref
65 | # - forbidigo
66 | # - forcetypeassert
67 | # - funlen
68 | # - gci
69 | # - gochecknoglobals
70 | # - gochecknoinits
71 | # - gocognit
72 | # - godot
73 | # - goerr113
74 | # - gomnd
75 | # - ifshort
76 | # - lll
77 | # - nestif
78 | # - nilerr
79 | # - nlreturn
80 | # - noctx
81 | # - paralleltest
82 | # - scopelint
83 | # - tagliatelle
84 | # - testpackage
85 | # - thelper
86 | # - tparallel
87 | # - wastedassign
88 | - wrapcheck
89 | # - wsl
90 | gci:
91 | no-inline-comments: false
92 | no-prefix-comments: true
93 | sections:
94 | - standard
95 | - default
96 | - prefix(github.com/bom-squad/protobom)
97 |
98 | section-separators:
99 | - newLine
100 | godox:
101 | keywords:
102 | - BUG
103 | - FIXME
104 | - HACK
105 | errcheck:
106 | check-type-assertions: true
107 | check-blank: true
108 | gocritic:
109 | enabled-checks:
110 | # Diagnostic
111 | - appendAssign
112 | - argOrder
113 | - badCond
114 | - caseOrder
115 | - codegenComment
116 | - commentedOutCode
117 | - deprecatedComment
118 | - dupArg
119 | - dupBranchBody
120 | - dupCase
121 | - dupSubExpr
122 | - exitAfterDefer
123 | - flagDeref
124 | - flagName
125 | - nilValReturn
126 | - offBy1
127 | - sloppyReassign
128 | - weakCond
129 | - octalLiteral
130 |
131 | # Performance
132 | - appendCombine
133 | - equalFold
134 | - hugeParam
135 | - indexAlloc
136 | - rangeExprCopy
137 | - rangeValCopy
138 |
139 | # Style
140 | - assignOp
141 | - boolExprSimplify
142 | - captLocal
143 | - commentFormatting
144 | - commentedOutImport
145 | - defaultCaseOrder
146 | - docStub
147 | - elseif
148 | - emptyFallthrough
149 | - emptyStringTest
150 | - hexLiteral
151 | - methodExprCall
152 | - regexpMust
153 | - singleCaseSwitch
154 | - sloppyLen
155 | - stringXbytes
156 | - switchTrue
157 | - typeAssertChain
158 | - typeSwitchVar
159 | - underef
160 | - unlabelStmt
161 | - unlambda
162 | - unslice
163 | - valSwap
164 | - wrapperFunc
165 | - yodaStyleExpr
166 | # - ifElseChain
167 |
168 | # Opinionated
169 | - builtinShadow
170 | - importShadow
171 | - initClause
172 | - nestingReduce
173 | - paramTypeCombine
174 | - ptrToRefParam
175 | - typeUnparen
176 | - unnamedResult
177 | - unnecessaryBlock
178 | nolintlint:
179 | # Enable to ensure that nolint directives are all used. Default is true.
180 | allow-unused: false
181 | # Disable to ensure that nolint directives don't have a leading space. Default is true.
182 | # TODO(lint): Enforce machine-readable `nolint` directives
183 | allow-leading-space: true
184 | # Exclude following linters from requiring an explanation. Default is [].
185 | allow-no-explanation: []
186 | # Enable to require an explanation of nonzero length after each nolint directive. Default is false.
187 | # TODO(lint): Enforce explanations for `nolint` directives
188 | require-explanation: false
189 | # Enable to require nolint directives to mention the specific linter being suppressed. Default is false.
190 | require-specific: true
191 |
--------------------------------------------------------------------------------
/tutorial/03.invoking-bomshell.md:
--------------------------------------------------------------------------------
1 | # 3. Invoking `bomshell`
2 |
3 | There are three main ways to invoke bomshell. The regular `bomshell` command,
4 | the stricter `bomshell run` subcommand, and as the interpreter of standalone
5 | scripts.
6 |
7 | To test the following commands, download protobom from our
8 | [releases page](https://github.com/chainguard-dev/bomshell/releases) or
9 | build it from source for the most cutting edge experience :)
10 |
11 | ## The `bomshell` Command
12 |
13 | There are two main ingredients of a bomshell invocation: the recipe and one or
14 | more SBOMs to work on. When running `bomshell` from the command line, the tool
15 | will try to make some smart assumptions to find the ingredients it needs.
16 |
17 | ### Finding The Recipe
18 |
19 | In its normal mode, bomshell will try to find the recipe from a couple of places.
20 | There is only one recipe in a bomshell invocation.
21 |
22 | #### First Positional Argument
23 |
24 | Forst there is the positional arguments it receives:
25 |
26 | ```
27 | bomshell recipe.cel sbom1.spdx.json
28 | ```
29 |
30 | When receiving a list of arguments, bomshell will check the first to see if it
31 | is a file with CEL code in it. If it is not a file, it will try to parse the
32 | contents of the first argument not as a file name but as the recipe code. This
33 | lets you specify the recipe code in your terminal directly:
34 |
35 | ```
36 | bomshell 'sbom.packages()' sbom1.cdx.json
37 | ```
38 |
39 | There is one exception designed for convenience and UI consistency that override
40 | this behavior.
41 |
42 | #### The `--execute` Flag
43 |
44 | To avoid guess work parsing the first argument, the recipe code can be specified
45 | in the command line using the `-e`|`--execute` flag:
46 |
47 | ```
48 | bomshell --execute 'sbom.packages()' sbom1.spdx.json
49 | ```
50 |
51 | When `-e` is set, all positional arguments will be interpreted as SBOMs to parse.
52 |
53 | ### Defining SBOMs to be Preloaded
54 |
55 | The second ingredient of the `bomshell` invocation are the SBOMs that are parsed
56 | and loaded into the runtime environment. There are three ways to tell bomshell
57 | where to find SBOMs to play with.
58 |
59 | Regardless of how they were specified, the SBOMs follow the same journey into the
60 | bomshell runtime environment: The files are opened, their content is read and
61 | the format is automatically detected. The data is parsed into a `protobom` document
62 | and loaded into the SBOMs array in the runtime. Any SBOM that fails to load because
63 | of I/O errors or parsing problems will cause the whole bomshell run to fail before
64 | the recipe code is evaluated.
65 |
66 | #### Positional Arguments
67 |
68 | After the recipe code is found, any positional arguments remaining will be
69 | interpreted as path names to SBOM files. As mentioned before the format is
70 | automatically detected by the `protobom` core so no need to specify if files are
71 | SPDX or CycloneDX documents or to have special file name conventions.
72 |
73 | ```
74 | bomshell recipe.cel sbom1.spdx.json sbom2.cdx.json
75 | ```
76 |
77 | To understand how positional arguments are interpreted, check the previous section
78 | on finding the recipe.
79 |
80 | #### The `--sbom` Flag
81 |
82 | To ensure a consistent behavior that does not guess your intentions, you can
83 | define the SBOMs to be loaded. Any number of SBOMs can be defined. The following
84 | command is an equivalent to the previous example:
85 |
86 | ```
87 | bomshell recipe.cel --sbom=sbom1.spdx.json --sbom=sbom2.cdx.json
88 | ```
89 |
90 | #### Piping an SBOM through STDIN
91 |
92 | bomshell also supports piping an SBOM through STDIN. This is useful to chaing
93 | `bomshell` to the result of another program that writes SBOMs such as generators
94 | or downloading documents with curl:
95 |
96 | ```
97 | cat mysbom.spdx.json | bomshell 'sbom.files()'
98 | ```
99 |
100 | #### SBOM Order
101 |
102 | All loaded SBOMs are loaded into a magic array that is exposed at runtime. The
103 | array is numeric and SBOMs are loaded in the way they were specified:
104 |
105 | 1. If an SBOM was loaded from STDIN it will always be `sbom[0]`
106 | 2. Next, positional arguments take precedent
107 | 3. Finally any sboms specified using the `--sbom` flag.
108 |
109 | ## The `bomshell run` Subcommand.
110 |
111 | `bomshell` offers a more predictable `run` subcommand designed t be run in CI and
112 | other automated environments. The run subcommand works almost like the default
113 | `bomshell` invocation but it will not try to parse arguments to figure out if
114 | a string is a file name or code to execute. This lets you have a consistent behavior
115 | when it runs unsupervised.
116 |
117 | ## Standalone Scripts
118 |
119 | The bomshell interpreter has an internal extension to the CEL parser to support
120 | scripts with a shebang line. This allows for scripts to be run directly, relying
121 | on the OS to run the bomshell interpreter.
122 |
123 | #### Example: `file-printer`
124 |
125 | ```shell
126 | #!/usr/bin/env bash
127 | sbom.files()
128 | ```
129 |
130 | #### Execution:
131 |
132 | ```
133 | file-printer
134 | [... output trimmed ...]
135 | ```
136 |
137 |
--------------------------------------------------------------------------------
/pkg/ui/interactive.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package ui
5 |
6 | import (
7 | "fmt"
8 | "strings"
9 |
10 | "github.com/chainguard-dev/bomshell/pkg/render"
11 | "github.com/chainguard-dev/bomshell/pkg/shell"
12 | "github.com/charmbracelet/bubbles/textarea"
13 | "github.com/charmbracelet/bubbles/viewport"
14 | tea "github.com/charmbracelet/bubbletea"
15 | "github.com/charmbracelet/lipgloss"
16 | "github.com/google/cel-go/cel"
17 | )
18 |
19 | const Prompt = "🐚❯ "
20 |
21 | var titleStyle = func() lipgloss.Style {
22 | b := lipgloss.RoundedBorder()
23 | b.Right = "├"
24 | return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
25 | }()
26 |
27 | /*
28 | infoStyle = func() lipgloss.Style {
29 | b := lipgloss.RoundedBorder()
30 | b.Left = "┤"
31 | return titleStyle.Copy().BorderStyle(b)
32 | }()
33 | */type historyEntry struct {
34 | expression string
35 | result string
36 | isError bool
37 | }
38 |
39 | type History []historyEntry
40 |
41 | func (h *History) Append(entry historyEntry) {
42 | *h = append(*h, entry)
43 | }
44 |
45 | type model struct {
46 | ready bool
47 | bomshell *shell.Bomshell
48 | renderer render.Renderer
49 | history History
50 | viewport viewport.Model
51 | textarea textarea.Model
52 | }
53 |
54 | func (m model) Init() tea.Cmd {
55 | return textarea.Blink
56 | }
57 |
58 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
59 | var (
60 | textareaCmd tea.Cmd
61 | cmd tea.Cmd
62 | cmds []tea.Cmd
63 | )
64 |
65 | if m.ready {
66 | m.textarea, textareaCmd = m.textarea.Update(msg)
67 | cmds = append(cmds, textareaCmd)
68 | }
69 |
70 | switch msg := msg.(type) {
71 | case tea.KeyMsg:
72 | switch msg.Type {
73 | case tea.KeyCtrlC, tea.KeyEsc:
74 | return m, tea.Quit
75 | case tea.KeyEnter: // tea.KeyUp
76 | // Execute the expression:
77 | result, err := m.bomshell.Run(m.textarea.Value())
78 | if err == nil {
79 | m.history = append(m.history, historyEntry{
80 | m.textarea.Value(),
81 | m.renderer.Display(result),
82 | false,
83 | })
84 | } else {
85 | m.history = append(m.history, historyEntry{
86 | m.textarea.Value(),
87 | err.Error(),
88 | true,
89 | })
90 | }
91 |
92 | // content := fmt.Sprintf("Va pues (len es %d):\n", len(m.history))
93 | content := ""
94 | for _, e := range m.history {
95 | content = content + "> " + e.expression + "\n"
96 | content = content + "- " + e.result + "\n\n"
97 | }
98 | m.viewport.SetContent(content)
99 | m.textarea.Reset()
100 | m.viewport.GotoBottom()
101 | cmds = append(cmds, viewport.Sync(m.viewport))
102 | }
103 |
104 | case tea.WindowSizeMsg:
105 | headerHeight := lipgloss.Height(m.headerView())
106 | footerHeight := lipgloss.Height(m.footerView())
107 | verticalMarginHeight := headerHeight + footerHeight
108 | m.textarea.SetWidth(msg.Width)
109 |
110 | if !m.ready {
111 | m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight)
112 | m.viewport.YPosition = headerHeight
113 | m.viewport.HighPerformanceRendering = true
114 | m.viewport.KeyMap = viewport.KeyMap{}
115 | m.ready = true
116 | m.textarea.Focus()
117 | m.viewport.YPosition = headerHeight + 1
118 | } else {
119 | m.viewport.Width = msg.Width
120 | m.viewport.Height = msg.Height - verticalMarginHeight
121 | }
122 |
123 | cmds = append(cmds, viewport.Sync(m.viewport))
124 | }
125 |
126 | m.viewport, cmd = m.viewport.Update(msg)
127 | cmds = append(cmds, cmd)
128 |
129 | return m, tea.Batch(cmds...)
130 | }
131 |
132 | func (m model) View() string {
133 | if !m.ready {
134 | return "\n Initializing..."
135 | }
136 | return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView())
137 | }
138 |
139 | func (m model) headerView() string {
140 | title := titleStyle.Render("💣🐚 BOMSHELL v0.0.1")
141 | line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)))
142 | return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
143 | }
144 |
145 | func (m model) footerView() string {
146 | return m.textarea.View()
147 | /*
148 | info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
149 | line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info)))
150 | return lipgloss.JoinHorizontal(lipgloss.Center, line, info)
151 | */
152 | }
153 |
154 | func max(a, b int) int {
155 | if a > b {
156 | return a
157 | }
158 | return b
159 | }
160 |
161 | func initModel(bomshell *shell.Bomshell) model {
162 | ta := textarea.New()
163 | ta.Placeholder = "type a bomshell expression..."
164 | ta.Focus()
165 |
166 | ta.Prompt = Prompt
167 | ta.SetHeight(1)
168 | ta.FocusedStyle.CursorLine = lipgloss.NewStyle()
169 | ta.ShowLineNumbers = false
170 | ta.KeyMap.InsertNewline.SetEnabled(false)
171 |
172 | return model{
173 | bomshell: bomshell,
174 | history: []historyEntry{},
175 | renderer: render.NewTTY(),
176 | textarea: ta,
177 | }
178 | }
179 |
180 | type Interactive struct {
181 | ui *tea.Program
182 | bomshell *shell.Bomshell
183 | }
184 |
185 | func (i *Interactive) Start() error {
186 | if _, err := i.ui.Run(); err != nil {
187 | return fmt.Errorf("starting UI: %w", err)
188 | }
189 | return nil
190 | }
191 |
192 | func NewInteractive(opts shell.Options) (*Interactive, error) {
193 | opts.EnvOptions = append(opts.EnvOptions, cel.Lib(InteractiveSubshell{}))
194 | bomshell, err := shell.NewWithOptions(opts)
195 | if err != nil {
196 | return nil, fmt.Errorf("creating bomshell environment: %w", err)
197 | }
198 | /*
199 | if err := bomshell.RunFile(program); err != nil {
200 | logrus.Fatal(err)
201 | }
202 | */
203 | return &Interactive{
204 | bomshell: bomshell,
205 | ui: tea.NewProgram(
206 | initModel(bomshell),
207 | tea.WithAltScreen(),
208 | // tea.WithMouseCellMotion(),
209 | ),
210 | }, nil
211 | }
212 |
--------------------------------------------------------------------------------
/pkg/shell/environment.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package shell
5 |
6 | import (
7 | "fmt"
8 |
9 | "github.com/chainguard-dev/bomshell/pkg/elements"
10 | "github.com/chainguard-dev/bomshell/pkg/functions"
11 | "github.com/google/cel-go/cel"
12 | "github.com/google/cel-go/common/types"
13 | "github.com/google/cel-go/common/types/ref"
14 | "github.com/google/cel-go/ext"
15 | // "github.com/google/cel-go/common/operators"
16 | // "github.com/google/cel-go/common/types/traits"
17 | // celfuncs "github.com/google/cel-go/interpreter/functions"
18 | )
19 |
20 | type shellLibrary struct{}
21 |
22 | // createEnvironment creates the CEL execution environment that the runner will
23 | // use to compile and evaluate programs on the SBOM
24 | func (shellLibrary) CompileOptions() []cel.EnvOption {
25 | return []cel.EnvOption{
26 | cel.Variable("sboms", cel.MapType(cel.IntType, elements.DocumentType)),
27 | cel.Variable("sbom", elements.DocumentType),
28 | cel.Variable("bomshell", elements.BomshellType),
29 |
30 | cel.Function(
31 | "files",
32 | cel.MemberOverload(
33 | "sbom_files_binding", []*cel.Type{cel.ObjectType(protoDocumentType)}, elements.NodeListType,
34 | cel.UnaryBinding(functions.Files),
35 | ),
36 | cel.MemberOverload(
37 | "nodelist_files_binding", []*cel.Type{elements.NodeListType}, elements.NodeListType,
38 | cel.UnaryBinding(functions.Files),
39 | ),
40 | cel.MemberOverload(
41 | "node_files_binding", []*cel.Type{elements.NodeType}, elements.NodeListType,
42 | cel.UnaryBinding(functions.Files),
43 | ),
44 | ),
45 |
46 | cel.Function(
47 | "packages",
48 | cel.MemberOverload(
49 | "sbom_packages_binding", []*cel.Type{cel.ObjectType(protoDocumentType)}, elements.NodeListType,
50 | cel.UnaryBinding(functions.Packages),
51 | ),
52 | cel.MemberOverload(
53 | "nodeslist_packages_binding", []*cel.Type{elements.NodeListType}, elements.NodeListType,
54 | cel.UnaryBinding(functions.Packages),
55 | ),
56 | cel.MemberOverload(
57 | "node_packages_binding", []*cel.Type{elements.NodeType}, elements.NodeListType,
58 | cel.UnaryBinding(functions.Packages),
59 | ),
60 | ),
61 |
62 | cel.Function(
63 | "add",
64 | cel.MemberOverload(
65 | "add_nodelists",
66 | []*cel.Type{elements.NodeListType, elements.NodeListType},
67 | elements.NodeListType,
68 | cel.BinaryBinding(functions.Addition),
69 | // cel.OverloadOperandTrait(traits.AdderType),
70 | ),
71 | ),
72 |
73 | cel.Function(
74 | "ToNodeList",
75 | cel.MemberOverload(
76 | "document_tonodelist_binding",
77 | []*cel.Type{cel.ObjectType(protoDocumentType)}, elements.NodeListType,
78 | cel.UnaryBinding(functions.ToNodeList),
79 | ),
80 | cel.MemberOverload(
81 | "nodelist_tonodelist_binding",
82 | []*cel.Type{elements.NodeListType}, elements.NodeListType,
83 | cel.UnaryBinding(functions.ToNodeList),
84 | ),
85 | cel.MemberOverload(
86 | "node_tonodelist_binding",
87 | []*cel.Type{elements.NodeType}, elements.NodeListType,
88 | cel.UnaryBinding(functions.ToNodeList),
89 | ),
90 | ),
91 |
92 | /*
93 | cel.Function(
94 | operators.Add,
95 | cel.MemberOverload(
96 | "add_nodelists",
97 | []*cel.Type{elements.NodeListType, elements.NodeListType},
98 | elements.NodeListType,
99 | cel.BinaryBinding(functions.Addition),
100 |
101 | cel.OverloadOperandTrait(traits.AdderType),
102 | ),
103 | ),
104 | */
105 | cel.Function(
106 | "NodeByID",
107 | cel.MemberOverload(
108 | "sbom_nodebyid_binding", []*cel.Type{elements.DocumentType, cel.StringType}, elements.NodeType,
109 | cel.BinaryBinding(functions.NodeByID),
110 | ),
111 | cel.MemberOverload(
112 | "nodelist_nodebyid_binding", []*cel.Type{elements.NodeListType, cel.StringType}, elements.NodeType,
113 | cel.BinaryBinding(functions.NodeByID),
114 | ),
115 | ),
116 |
117 | cel.Function(
118 | "NodesByPurlType",
119 | cel.MemberOverload(
120 | "sbom_nodesbypurltype_binding", []*cel.Type{elements.DocumentType, cel.StringType}, elements.NodeListType,
121 | cel.BinaryBinding(functions.NodesByPurlType),
122 | ),
123 | cel.MemberOverload(
124 | "nodelist_nodesbypurltype_binding", []*cel.Type{elements.NodeListType, cel.StringType}, elements.NodeListType,
125 | cel.BinaryBinding(functions.NodesByPurlType),
126 | ),
127 | ),
128 |
129 | cel.Function(
130 | "ToDocument",
131 | cel.MemberOverload(
132 | "document_todocument_binding",
133 | []*cel.Type{elements.DocumentType}, elements.DocumentType,
134 | cel.UnaryBinding(functions.ToDocument),
135 | ),
136 | cel.MemberOverload(
137 | "nodelist_todocument_binding",
138 | []*cel.Type{elements.NodeListType}, elements.DocumentType,
139 | cel.UnaryBinding(functions.ToDocument),
140 | ),
141 | cel.MemberOverload(
142 | "node_todocument_binding",
143 | []*cel.Type{elements.NodeType}, elements.DocumentType,
144 | cel.UnaryBinding(functions.ToDocument),
145 | ),
146 | ),
147 |
148 | cel.Function(
149 | "LoadSBOM",
150 | cel.MemberOverload(
151 | "bomshell_loadsbom_binding",
152 | []*cel.Type{elements.BomshellType, cel.StringType}, elements.DocumentType,
153 | cel.BinaryBinding(functions.LoadSBOM),
154 | ),
155 | ),
156 |
157 | cel.Function(
158 | "RelateNodeListAtID",
159 | cel.MemberOverload(
160 | "sbom_relatenodesatid_binding",
161 | []*cel.Type{elements.DocumentType, elements.NodeListType, cel.StringType, cel.StringType},
162 | elements.DocumentType, // result
163 | cel.FunctionBinding(functions.RelateNodeListAtID),
164 | ),
165 | cel.MemberOverload(
166 | "nodelist_relatenodesatid_binding",
167 | []*cel.Type{elements.NodeListType, elements.NodeListType, cel.StringType, cel.StringType},
168 | elements.DocumentType, // result
169 | cel.FunctionBinding(functions.RelateNodeListAtID),
170 | ),
171 | ),
172 | /*
173 | cel.Macros(
174 | // cel.bind(var, , )
175 | cel.NewReceiverMacro("LoadSBOM", 1, celBind),
176 | ),
177 | */
178 | }
179 | }
180 |
181 | func (shellLibrary) LibraryName() string {
182 | return "cel.chainguard.bomshell"
183 | }
184 |
185 | func (shellLibrary) ProgramOptions() []cel.ProgramOption {
186 | /*
187 | return []cel.ProgramOption{
188 | // cel.Functions(functions.StandardOverloads()...),
189 |
190 | cel.Functions(
191 | &celfuncs.Overload{
192 | Operator: "++", /// Placegholder while I figure out how to overload operators.Add
193 | OperandTrait: traits.AdderType,
194 | Binary: functions.Addition,
195 | // Function: functions.AdditionOp,
196 | //NonStrict: false,
197 | },
198 | ),
199 | }
200 | */
201 | return []cel.ProgramOption{}
202 | }
203 |
204 | func Library() cel.EnvOption {
205 | return cel.Lib(shellLibrary{})
206 | }
207 |
208 | type customTypeAdapter struct{}
209 |
210 | func (customTypeAdapter) NativeToValue(value interface{}) ref.Val {
211 | val, ok := value.(elements.Bomshell)
212 | if ok {
213 | return val
214 | } else {
215 | // let the default adapter handle other cases
216 | return types.DefaultTypeAdapter.NativeToValue(value)
217 | }
218 | }
219 |
220 | func createEnvironment(opts *Options) (*cel.Env, error) {
221 | envOpts := []cel.EnvOption{
222 | cel.CustomTypeAdapter(&customTypeAdapter{}),
223 | Library(),
224 | ext.Bindings(),
225 | ext.Strings(),
226 | ext.Encoders(),
227 | }
228 |
229 | // Add any additional environment options passed in the construcutor
230 | envOpts = append(envOpts, opts.EnvOptions...)
231 | env, err := cel.NewEnv(
232 | envOpts...,
233 | )
234 | if err != nil {
235 | return nil, (fmt.Errorf("creating CEL environment: %w", err))
236 | }
237 |
238 | return env, nil
239 | }
240 |
--------------------------------------------------------------------------------
/pkg/functions/functions.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // SPDX-FileCopyrightText: Copyright 2023 Chainguard Inc
3 |
4 | package functions
5 |
6 | import (
7 | "fmt"
8 | "os"
9 |
10 | "github.com/bom-squad/protobom/pkg/sbom"
11 | "github.com/chainguard-dev/bomshell/pkg/elements"
12 | "github.com/chainguard-dev/bomshell/pkg/loader"
13 | "github.com/google/cel-go/common/types"
14 | "github.com/google/cel-go/common/types/ref"
15 | "google.golang.org/protobuf/types/known/timestamppb"
16 | "sigs.k8s.io/release-utils/version"
17 | )
18 |
19 | // ToNodeList takes a node and returns a new NodeList
20 | // with that nodelist with the node as the only member.
21 | var ToNodeList = func(lhs ref.Val) ref.Val {
22 | switch v := lhs.Value().(type) {
23 | case *sbom.Document:
24 | return elements.NodeList{
25 | NodeList: v.NodeList,
26 | }
27 | case *elements.Document:
28 | return elements.NodeList{
29 | NodeList: v.Document.NodeList,
30 | }
31 | case *sbom.NodeList:
32 | return elements.NodeList{
33 | NodeList: v,
34 | }
35 | case *elements.NodeList:
36 | return v
37 | case *elements.Node:
38 | nl := v.ToNodeList()
39 | return *nl
40 | case *sbom.Node:
41 | nl := elements.Node{
42 | Node: v,
43 | }.ToNodeList()
44 | return *nl
45 | default:
46 | return types.NewErr("type does not support conversion to NodeList" + fmt.Sprintf("%T", v))
47 | }
48 | }
49 |
50 | var Addition = func(lhs, rhs ref.Val) ref.Val {
51 | return elements.NodeList{
52 | NodeList: &sbom.NodeList{},
53 | }
54 | }
55 |
56 | var AdditionOp = func(vals ...ref.Val) ref.Val {
57 | return elements.NodeList{
58 | NodeList: &sbom.NodeList{},
59 | }
60 | }
61 |
62 | // NodeByID returns a Node matching the specified ID
63 | var NodeByID = func(lhs, rawID ref.Val) ref.Val {
64 | queryID, ok := rawID.Value().(string)
65 | if !ok {
66 | return types.NewErr("argument to element by id has to be a string")
67 | }
68 | var node *sbom.Node
69 | switch v := lhs.Value().(type) {
70 | case *sbom.Document:
71 | node = v.NodeList.GetNodeByID(queryID)
72 | case *sbom.NodeList:
73 | node = v.GetNodeByID(queryID)
74 | case *sbom.Node:
75 | if v.Id == queryID {
76 | node = v
77 | }
78 | default:
79 | return types.NewErr("method unsupported on type %T", lhs.Value())
80 | }
81 |
82 | if node == nil {
83 | return nil
84 | }
85 |
86 | return elements.Node{
87 | Node: node,
88 | }
89 | }
90 |
91 | // Files returns all the Nodes marked as type file from an element. The function
92 | // supports documents, nodelists, and nodes. If the node is a file, it will return
93 | // a NodeList with it as the single node or empty if it is a package.
94 | //
95 | // If the passed type is not supported, the return value will be an error.
96 | var Files = func(lhs ref.Val) ref.Val {
97 | nodeList, err := getTypedNodes(lhs, sbom.Node_FILE)
98 | if err != nil {
99 | return types.NewErr(err.Error())
100 | }
101 | return nodeList
102 | }
103 |
104 | // Packages returns a NodeList with any packages in the lhs element. It supports
105 | // Documents, NodeLists and Nodes. If a node is provided it will return a NodeList
106 | // with the single node it is a package, otherwise it will be empty.
107 | //
108 | // If lhs is an unsupprted type, Packages will return an error.
109 | var Packages = func(lhs ref.Val) ref.Val {
110 | nodeList, err := getTypedNodes(lhs, sbom.Node_PACKAGE)
111 | if err != nil {
112 | return types.NewErr(err.Error())
113 | }
114 | return nodeList
115 | }
116 |
117 | // getTypedNodes takes an element and returns a nodelist containing all nodes
118 | // of the specified type (package or file). If an unsupported types is provided,
119 | // the function return an error
120 | func getTypedNodes(element ref.Val, t sbom.Node_NodeType) (elements.NodeList, error) {
121 | var sourceNodeList *sbom.NodeList
122 |
123 | switch v := element.Value().(type) {
124 | case *sbom.Document:
125 | sourceNodeList = v.NodeList
126 | case *elements.Document:
127 | sourceNodeList = v.Document.NodeList
128 | case *sbom.NodeList:
129 | sourceNodeList = v
130 | case *elements.NodeList:
131 | sourceNodeList = v.NodeList
132 | case *elements.Node:
133 | sourceNodeList = &sbom.NodeList{
134 | RootElements: []string{},
135 | }
136 |
137 | if v.Node.Type == t {
138 | sourceNodeList.AddNode(v.Node)
139 | sourceNodeList.RootElements = append(sourceNodeList.RootElements, v.Id)
140 | }
141 |
142 | return elements.NodeList{
143 | NodeList: sourceNodeList,
144 | }, nil
145 |
146 | default:
147 | return elements.NodeList{}, fmt.Errorf("unable to list packages (unsupported type?) %T", element.Value())
148 | }
149 | resultNodeList := elements.NodeList{
150 | NodeList: &sbom.NodeList{
151 | RootElements: []string{},
152 | Edges: sourceNodeList.Edges,
153 | },
154 | }
155 |
156 | for _, n := range sourceNodeList.Nodes {
157 | if n.Type == t {
158 | resultNodeList.AddNode(n)
159 | }
160 | }
161 |
162 | cleanEdges(&resultNodeList)
163 | reconnectOrphanNodes(&resultNodeList)
164 | return resultNodeList, nil
165 | }
166 |
167 | // ToDocument converts an element into a fill document. This is useful when
168 | // bomshell needs to convert its results to a document to output them as an SBOM
169 | var ToDocument = func(lhs ref.Val) ref.Val {
170 | var nodelist *elements.NodeList
171 | switch v := lhs.Value().(type) {
172 | case *sbom.NodeList:
173 | nodelist = &elements.NodeList{NodeList: v}
174 | case *elements.NodeList:
175 | nodelist = v
176 | case *elements.Node:
177 | nodelist = v.ToNodeList()
178 | case *sbom.Node:
179 | nodelist = elements.Node{Node: v}.ToNodeList()
180 | default:
181 | return types.NewErr("unable to convert element to document")
182 | }
183 |
184 | // Here we reconnect all orphaned nodelists to the root of the
185 | // nodelist. The produced document will describe all elements of
186 | // the nodelist except for those which are already related to other
187 | // nodes in the graph.
188 | reconnectOrphanNodes(nodelist)
189 |
190 | doc := elements.Document{
191 | Document: &sbom.Document{
192 | Metadata: &sbom.Metadata{
193 | Id: "",
194 | Version: "1",
195 | Name: "bomshell generated document",
196 | Date: timestamppb.Now(),
197 | Tools: []*sbom.Tool{
198 | {
199 | Name: "bomshell",
200 | Version: version.GetVersionInfo().GitVersion,
201 | Vendor: "Chainguard Labs",
202 | },
203 | },
204 | Authors: []*sbom.Person{},
205 | Comment: "This document was generated by bomshell from a protobom nodelist",
206 | },
207 | NodeList: nodelist.NodeList,
208 | },
209 | }
210 |
211 | return doc
212 | }
213 |
214 | var LoadSBOM = func(_, pathVal ref.Val) ref.Val {
215 | path, ok := pathVal.Value().(string)
216 | if !ok {
217 | return types.NewErr("argument to element by id has to be a string")
218 | }
219 |
220 | f, err := os.Open(path)
221 | if err != nil {
222 | return types.NewErr("opening SBOM file: %w", err)
223 | }
224 |
225 | doc, err := loader.ReadSBOM(f)
226 | if err != nil {
227 | return types.NewErr("loading document: %w", err)
228 | }
229 | return elements.Document{
230 | Document: doc,
231 | }
232 | }
233 |
234 | var NodesByPurlType = func(lhs, rhs ref.Val) ref.Val {
235 | purlType, ok := rhs.Value().(string)
236 | if !ok {
237 | return types.NewErr("argument to GetNodesByPurlType must be a string")
238 | }
239 |
240 | var nl *sbom.NodeList
241 | switch v := lhs.Value().(type) {
242 | case *sbom.Document:
243 | nl = v.NodeList.GetNodesByPurlType(purlType)
244 | case *sbom.NodeList:
245 | nl = v.GetNodesByPurlType(purlType)
246 | default:
247 | return types.NewErr("method unsupported on type %T", lhs.Value())
248 | }
249 |
250 | return elements.NodeList{
251 | NodeList: nl,
252 | }
253 | }
254 |
255 | // RelateNodeListAtID relates a nodelist at the specified ID
256 | var RelateNodeListAtID = func(vals ...ref.Val) ref.Val {
257 | if len(vals) != 4 {
258 | return types.NewErr("invalid number of arguments for RealteAtNodeListAtID")
259 | }
260 | id, ok := vals[2].Value().(string)
261 | if !ok {
262 | return types.NewErr("node id has to be a string")
263 | }
264 | // relType
265 | _, ok = vals[3].Value().(string)
266 | if !ok {
267 | return types.NewErr("relationship type has has to be a string")
268 | }
269 |
270 | nodelist, ok := vals[1].(elements.NodeList)
271 | if !ok {
272 | return types.NewErr("could not cast nodelist")
273 | }
274 |
275 | switch v := vals[0].Value().(type) {
276 | case *sbom.Document:
277 | // FIXME: Lookup reltype
278 | if err := v.NodeList.RelateNodeListAtID(nodelist.Value().(*sbom.NodeList), id, sbom.Edge_dependsOn); err != nil {
279 | return types.NewErr(err.Error())
280 | }
281 | return elements.Document{
282 | Document: v,
283 | }
284 | case *sbom.NodeList:
285 | if err := v.RelateNodeListAtID(nodelist.Value().(*sbom.NodeList), id, sbom.Edge_dependsOn); err != nil {
286 | return types.NewErr(err.Error())
287 | }
288 | return elements.NodeList{
289 | NodeList: v,
290 | }
291 | default:
292 | return types.NewErr("method unsupported on type %T", vals[0].Value())
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M=
2 | github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk=
3 | github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
4 | github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 h1:6COpXWpHbhWM1wgcQN95TdsmrLTba8KQfPgImBXzkjA=
5 | github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
6 | github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
7 | github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
8 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
9 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
10 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
12 | github.com/bom-squad/protobom v0.3.0 h1:1kdKbTmnhigxCQK3f0BJZWeevE+Z0bSGy0o76CFm0Ak=
13 | github.com/bom-squad/protobom v0.3.0/go.mod h1:IhdpGUnU5LTI5E7blHlGY9SDwJewK0xZd/YdW+ESKY0=
14 | github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
15 | github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
16 | github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
17 | github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
18 | github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
19 | github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
20 | github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
21 | github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
22 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
23 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
24 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
25 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
26 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
27 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
28 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
29 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
30 | github.com/google/cel-go v0.19.0 h1:vVgaZoHPBDd1lXCYGQOh5A06L4EtuIfmqQ/qnSXSKiU=
31 | github.com/google/cel-go v0.19.0/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
32 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
33 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
34 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
35 | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
36 | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
37 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
38 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
39 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
40 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
41 | github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
42 | github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
43 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
44 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
45 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
46 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
47 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
48 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
49 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
50 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
51 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
52 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
53 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
54 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
55 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
58 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
59 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
60 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
61 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
62 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
63 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
64 | github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
65 | github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY=
66 | github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
67 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
68 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
69 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
70 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
71 | github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
72 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
73 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
74 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
75 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
76 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
77 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
78 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
79 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
80 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
81 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
82 | github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
83 | github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
84 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
85 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
86 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
87 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
88 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
89 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
90 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
91 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
92 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
93 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
94 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
95 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
96 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
97 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
98 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
99 | golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
100 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
101 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
102 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
103 | google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44=
104 | google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q=
105 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg=
106 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I=
107 | google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
108 | google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
109 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
110 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
111 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
112 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
113 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
114 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
115 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
116 | sigs.k8s.io/release-utils v0.7.7 h1:JKDOvhCk6zW8ipEOkpTGDH/mW3TI+XqtPp16aaQ79FU=
117 | sigs.k8s.io/release-utils v0.7.7/go.mod h1:iU7DGVNi3umZJ8q6aHyUFzsDUIaYwNnNKGHo3YE5E3s=
118 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
119 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/examples/curl.spdx.json:
--------------------------------------------------------------------------------
1 | {
2 | "SPDXID": "SPDXRef-DOCUMENT",
3 | "name": "sbom-sha256:c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
4 | "spdxVersion": "SPDX-2.3",
5 | "creationInfo": {
6 | "created": "2023-05-30T10:45:35Z",
7 | "creators": [
8 | "Tool: apko (v0.8.0-53-gfaa1b37)",
9 | "Organization: Chainguard, Inc"
10 | ],
11 | "licenseListVersion": "3.16"
12 | },
13 | "dataLicense": "CC0-1.0",
14 | "documentNamespace": "https://spdx.org/spdxdocs/apko/",
15 | "documentDescribes": [
16 | "SPDXRef-Package-sha256-47fed8868b46b060efb8699dc40e981a0c785650223e03602d8c4493fc75b68c"
17 | ],
18 | "files": [
19 | {
20 | "SPDXID": "SPDXRef-File--etc-ssl-certs-ca-certificates.crt",
21 | "fileName": "/etc/ssl/certs/ca-certificates.crt",
22 | "licenseConcluded": "NOASSERTION",
23 | "checksums": [
24 | {
25 | "algorithm": "SHA1",
26 | "checksumValue": "b132b312a42c8be5d632069aecc6797b629f1264"
27 | },
28 | {
29 | "algorithm": "SHA256",
30 | "checksumValue": "824cefcee69de918c76b7b92776f304c3a4b7f6281539118bc1d41a9dd8476d9"
31 | },
32 | {
33 | "algorithm": "SHA512",
34 | "checksumValue": "18d8c151a80c14db8a2b419503d589495ea2377e8f28bbe6f087bcc13d4c9d429616bfc57d3d7fcd40b3406760a036f8737a34ea29be53e3edf7c55e05809108"
35 | }
36 | ]
37 | },
38 | {
39 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95ADDRESS",
40 | "fileName": "/usr/lib/locale/C.utf8/LC_ADDRESS",
41 | "licenseConcluded": "NOASSERTION",
42 | "checksums": [
43 | {
44 | "algorithm": "SHA1",
45 | "checksumValue": "12d0e0600557e0dcb3c64e56894b81230e2eaa72"
46 | },
47 | {
48 | "algorithm": "SHA256",
49 | "checksumValue": "26e2800affab801cb36d4ff9625a95c3abceeda2b6553a7aecd0cfcf34c98099"
50 | },
51 | {
52 | "algorithm": "SHA512",
53 | "checksumValue": "d38b225e8204e1e85e6c631481f46d0b8fca8cf8d8dfc290f00adb15b605959f91f0d55dc830fdd82c22f916140090928e44f1b5123facac135705cc81df00b0"
54 | }
55 | ]
56 | },
57 | {
58 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95COLLATE",
59 | "fileName": "/usr/lib/locale/C.utf8/LC_COLLATE",
60 | "licenseConcluded": "NOASSERTION",
61 | "checksums": [
62 | {
63 | "algorithm": "SHA1",
64 | "checksumValue": "f245e3207984879d0b736c9aa42f4268e27221b9"
65 | },
66 | {
67 | "algorithm": "SHA256",
68 | "checksumValue": "47a5f5359a8f324abc39d69a7f6241a2ac0e2fbbeae5b9c3a756e682b75d087b"
69 | },
70 | {
71 | "algorithm": "SHA512",
72 | "checksumValue": "3220445f9f137f3ff4b02c7b0c4a2bb963e495440a174ff5f15143bbd13cdc1c1f5055f5beaf807554c70bb134e842e963bd2411e0e81ae4fcb0613327fa16de"
73 | }
74 | ]
75 | },
76 | {
77 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95CTYPE",
78 | "fileName": "/usr/lib/locale/C.utf8/LC_CTYPE",
79 | "licenseConcluded": "NOASSERTION",
80 | "checksums": [
81 | {
82 | "algorithm": "SHA1",
83 | "checksumValue": "9b237153cdbb14eed476d372b0c5b37141ce3e73"
84 | },
85 | {
86 | "algorithm": "SHA256",
87 | "checksumValue": "4af23bb40c8f2e80a26c95369b442986213c50a7308d8d73b85c4911dde0a358"
88 | },
89 | {
90 | "algorithm": "SHA512",
91 | "checksumValue": "83777337c2a8bfe6c7545a78ccd13f17cd3fb96f817ea62d810d87bd073c33f273cbb1746d3f6ae980679b53b88d00c1a0cbeb7cb2f573f363fe16abc007b4ae"
92 | }
93 | ]
94 | },
95 | {
96 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95IDENTIFICATION",
97 | "fileName": "/usr/lib/locale/C.utf8/LC_IDENTIFICATION",
98 | "licenseConcluded": "NOASSERTION",
99 | "checksums": [
100 | {
101 | "algorithm": "SHA1",
102 | "checksumValue": "1eeec3b2cb259530d76ef717e24af0fd34d94624"
103 | },
104 | {
105 | "algorithm": "SHA256",
106 | "checksumValue": "38a1d8e5271c86f48910d9c684f64271955335736e71cec35eeac942f90eb091"
107 | },
108 | {
109 | "algorithm": "SHA512",
110 | "checksumValue": "680812c5bc70c90bd7b82a0b42ec8acddbb88dc186388f0c4a0b16bc4a08f49a05f3dc4086d1b9ab497b2617f136fc93eca1030de3712d41baa7e25a7e870cec"
111 | }
112 | ]
113 | },
114 | {
115 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MEASUREMENT",
116 | "fileName": "/usr/lib/locale/C.utf8/LC_MEASUREMENT",
117 | "licenseConcluded": "NOASSERTION",
118 | "checksums": [
119 | {
120 | "algorithm": "SHA1",
121 | "checksumValue": "0a7d0d264f9ded94057020e807bfaa13a7573821"
122 | },
123 | {
124 | "algorithm": "SHA256",
125 | "checksumValue": "bb14a6f2cbd5092a755e8f272079822d3e842620dd4542a8dfa1e5e72fc6115b"
126 | },
127 | {
128 | "algorithm": "SHA512",
129 | "checksumValue": "497cea17c3c7cf344e761c9aea4d0a88574d8ab2ff51b76881b1a59e8cf6583841e049cb6b83cb6c5e958c72b6d9fb8ea241728dfe76981da153302de28b00c8"
130 | }
131 | ]
132 | },
133 | {
134 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MESSAGES-SYSC95LCC95MESSAGES",
135 | "fileName": "/usr/lib/locale/C.utf8/LC_MESSAGES/SYS_LC_MESSAGES",
136 | "licenseConcluded": "NOASSERTION",
137 | "checksums": [
138 | {
139 | "algorithm": "SHA1",
140 | "checksumValue": "574d7e92bedf1373ec9506859b0d55ee7babbf20"
141 | },
142 | {
143 | "algorithm": "SHA256",
144 | "checksumValue": "f9ad02f1d8eba721d4cbd50c365b5c681c39aec008f90bfc2be2dc80bfbaddcb"
145 | },
146 | {
147 | "algorithm": "SHA512",
148 | "checksumValue": "51606a077ed7fbc15fb361c355fc6a87438ef7a5324defbba8fa04dd58f8095c3dda3de7bc41b2fb5497c33d5c4faa2e82e96bd770eeecbdac91f95423400e8c"
149 | }
150 | ]
151 | },
152 | {
153 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MONETARY",
154 | "fileName": "/usr/lib/locale/C.utf8/LC_MONETARY",
155 | "licenseConcluded": "NOASSERTION",
156 | "checksums": [
157 | {
158 | "algorithm": "SHA1",
159 | "checksumValue": "110ed47e32d65c61ab8240202faa2114d025a009"
160 | },
161 | {
162 | "algorithm": "SHA256",
163 | "checksumValue": "bfd9e9975443b834582493fe9a8d7aefcd989376789c17470a1e548aee76fd55"
164 | },
165 | {
166 | "algorithm": "SHA512",
167 | "checksumValue": "b247a6adf097154cb1af52199396ec6465986f5067a4a3b2a97423e0327d837579d689d89eb3ff9dda054228a190a8b163b085336df9bb64ddd9c48615cafe1b"
168 | }
169 | ]
170 | },
171 | {
172 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95NAME",
173 | "fileName": "/usr/lib/locale/C.utf8/LC_NAME",
174 | "licenseConcluded": "NOASSERTION",
175 | "checksums": [
176 | {
177 | "algorithm": "SHA1",
178 | "checksumValue": "b5d16f1042c3c1c4bef85766aa2c20c1b0d8cff6"
179 | },
180 | {
181 | "algorithm": "SHA256",
182 | "checksumValue": "14507aad9f806112e464b9ca94c93b2e4d759ddc612b5f87922d7cac7170697d"
183 | },
184 | {
185 | "algorithm": "SHA512",
186 | "checksumValue": "a6f898de0f03959965b7110768c80aff1831398c75f821d0998023bf80594edb02e4b6d82aed6caa0754902b9046ba75334c310bfac1d5cbe2bf19a25733f198"
187 | }
188 | ]
189 | },
190 | {
191 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95NUMERIC",
192 | "fileName": "/usr/lib/locale/C.utf8/LC_NUMERIC",
193 | "licenseConcluded": "NOASSERTION",
194 | "checksums": [
195 | {
196 | "algorithm": "SHA1",
197 | "checksumValue": "1bd2f3db04022b8cfe5cd7a7f90176f191e19425"
198 | },
199 | {
200 | "algorithm": "SHA256",
201 | "checksumValue": "f5976e6b3e6b24dfe03caad6a5b98d894d8110d8bd15507e690fd60fd3e04ab2"
202 | },
203 | {
204 | "algorithm": "SHA512",
205 | "checksumValue": "a97712e287b806a07690c3a5ed3dfa88c53d40d89a32f93cbf891b8fc85e4b393db96444068f75e54d944c7a3466d9d85981f4096775cb10e2e9ef83c091a946"
206 | }
207 | ]
208 | },
209 | {
210 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95PAPER",
211 | "fileName": "/usr/lib/locale/C.utf8/LC_PAPER",
212 | "licenseConcluded": "NOASSERTION",
213 | "checksums": [
214 | {
215 | "algorithm": "SHA1",
216 | "checksumValue": "567aaf639393135b76e22e72aaee1df95764e990"
217 | },
218 | {
219 | "algorithm": "SHA256",
220 | "checksumValue": "cde048b81e2a026517cc707c906aebbd50f5ee3957b6f0c1c04699dffcb7c015"
221 | },
222 | {
223 | "algorithm": "SHA512",
224 | "checksumValue": "f52473579beada206be140f23a18e3f87bbf89b7ba5d4bcda1e9202e7eafb08efaee69205d9b3a8dd8fa6179369a7e93f9601935244cca10eee9de07328a8e47"
225 | }
226 | ]
227 | },
228 | {
229 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95TELEPHONE",
230 | "fileName": "/usr/lib/locale/C.utf8/LC_TELEPHONE",
231 | "licenseConcluded": "NOASSERTION",
232 | "checksums": [
233 | {
234 | "algorithm": "SHA1",
235 | "checksumValue": "3316c99e183186c5cad97a71674ef7431c3da845"
236 | },
237 | {
238 | "algorithm": "SHA256",
239 | "checksumValue": "f4caf0d12844219b65ba42edc7ec2f5ac1b2fc36a3c88c28887457275daca1ee"
240 | },
241 | {
242 | "algorithm": "SHA512",
243 | "checksumValue": "5368d67364357cd64d9f7ed727860b809a20c3b84f6f5b606d630e02903cdab0af4fb9131100918304d42347dbb48e26341deccaae19d635d46ad5c3fa3162d8"
244 | }
245 | ]
246 | },
247 | {
248 | "SPDXID": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95TIME",
249 | "fileName": "/usr/lib/locale/C.utf8/LC_TIME",
250 | "licenseConcluded": "NOASSERTION",
251 | "checksums": [
252 | {
253 | "algorithm": "SHA1",
254 | "checksumValue": "e619a4db877e0b54fa14b8a3992da2b561b3239b"
255 | },
256 | {
257 | "algorithm": "SHA256",
258 | "checksumValue": "0910b595d1d5d4e52cc0f415bbb1ff07c015d6860d34aae02505dd9973a63154"
259 | },
260 | {
261 | "algorithm": "SHA512",
262 | "checksumValue": "69a4e27589f003d5607ed6e495183ff282a3f7556199549534ab58f4d53b1673a5140a01d0e6e0f4201216349751954c80f013214805cf72e33882b48f4209d7"
263 | }
264 | ]
265 | },
266 | {
267 | "SPDXID": "SPDXRef-File--etc-group",
268 | "fileName": "/etc/group",
269 | "licenseConcluded": "NOASSERTION",
270 | "checksums": [
271 | {
272 | "algorithm": "SHA1",
273 | "checksumValue": "ec071ffcbd968b249b10b185b3d6123edfc0c115"
274 | },
275 | {
276 | "algorithm": "SHA256",
277 | "checksumValue": "3b207abe452015c17bb872bdfd5999d15a08769b4d385ac7c1db252382410f88"
278 | },
279 | {
280 | "algorithm": "SHA512",
281 | "checksumValue": "2237f35b600512c2749bd4a83aa1899824c268fde6a093e09f5cf7548155939a003dd6ebe8e33bf44357531abf9abaf0e450f5c329bd8c8fe114601ebb98070c"
282 | }
283 | ]
284 | },
285 | {
286 | "SPDXID": "SPDXRef-File--etc-hosts",
287 | "fileName": "/etc/hosts",
288 | "licenseConcluded": "NOASSERTION",
289 | "checksums": [
290 | {
291 | "algorithm": "SHA1",
292 | "checksumValue": "043eb324a653456caa1a73e2e2d49f77792bb0c5"
293 | },
294 | {
295 | "algorithm": "SHA256",
296 | "checksumValue": "e3998dbe02b51dada33de87ae43d18a93ab6915b9e34f5a751bf2b9b25a55492"
297 | },
298 | {
299 | "algorithm": "SHA512",
300 | "checksumValue": "ac12d0ea9d710cc0122cc3eea5281a489f0c9217ed18fe16b40848f743be1e7e49f8d5b709377ac276559b901356de33b85905426d5e6f5f4b13720629139704"
301 | }
302 | ]
303 | },
304 | {
305 | "SPDXID": "SPDXRef-File--etc-nsswitch.conf",
306 | "fileName": "/etc/nsswitch.conf",
307 | "licenseConcluded": "NOASSERTION",
308 | "checksums": [
309 | {
310 | "algorithm": "SHA1",
311 | "checksumValue": "ef732648b323a542f701fc1133eb65b9c81adf8d"
312 | },
313 | {
314 | "algorithm": "SHA256",
315 | "checksumValue": "b0e81dd0825cba9e39affd4c64f86e3ab983bb731789f19819215c0eadeab7be"
316 | },
317 | {
318 | "algorithm": "SHA512",
319 | "checksumValue": "caf8982ac21dd39020fba730bd7ab7cfc0a6a2a582dd1caf967842d5bd91605491fe17a0c5ff013ef9c14496f4d7ede6999ad44ee1a18e1eeda4d919f84fa4e0"
320 | }
321 | ]
322 | },
323 | {
324 | "SPDXID": "SPDXRef-File--etc-os-release",
325 | "fileName": "/etc/os-release",
326 | "licenseConcluded": "NOASSERTION",
327 | "checksums": [
328 | {
329 | "algorithm": "SHA1",
330 | "checksumValue": "7835684dcf49106d117a45ce5618ee6219eb3638"
331 | },
332 | {
333 | "algorithm": "SHA256",
334 | "checksumValue": "fed8ba7bc11d0242ab089888bcc52c75fee81eeae4382b899ff76537814ee1e8"
335 | },
336 | {
337 | "algorithm": "SHA512",
338 | "checksumValue": "52414b3d7b622a802ef5f5d7730388539fc6c6d132ad6fec9cc014ff5c7a587daf3267c976e466541189932caf1e2259b3fe621c30da5e6a5b0b9f3b4f237dfd"
339 | }
340 | ]
341 | },
342 | {
343 | "SPDXID": "SPDXRef-File--etc-passwd",
344 | "fileName": "/etc/passwd",
345 | "licenseConcluded": "NOASSERTION",
346 | "checksums": [
347 | {
348 | "algorithm": "SHA1",
349 | "checksumValue": "590e103d9271aa287fc7546b954ead3df2852a28"
350 | },
351 | {
352 | "algorithm": "SHA256",
353 | "checksumValue": "dc48a1f79a71702792bdb8d1473a7d3b91b2add4bdad0da8cdf00da51554c155"
354 | },
355 | {
356 | "algorithm": "SHA512",
357 | "checksumValue": "616f13dacc91cc326256787e5c6e78c77e7e212c59d034cbde67d1e7b7916a0ce1eeead458fe972e050805884c3f5680289e1672dbda3b0f68db086afd1eb2c1"
358 | }
359 | ]
360 | },
361 | {
362 | "SPDXID": "SPDXRef-File--etc-profile",
363 | "fileName": "/etc/profile",
364 | "licenseConcluded": "NOASSERTION",
365 | "checksums": [
366 | {
367 | "algorithm": "SHA1",
368 | "checksumValue": "25aeb4d378af5dd1f260588869ac19b0df6481aa"
369 | },
370 | {
371 | "algorithm": "SHA256",
372 | "checksumValue": "8adf547453fe02fdc92e90424bffea4130bf88cc772a492b74912fb50a85c467"
373 | },
374 | {
375 | "algorithm": "SHA512",
376 | "checksumValue": "3328c3596e03c9a3ca1c8b34c48d3ee8475a08d489997ae4a493e81e7b7b5b7668d0079b64548077e84fcf9e1d70a2dccdcbbed94dfbd4941db6808348cf7f6c"
377 | }
378 | ]
379 | },
380 | {
381 | "SPDXID": "SPDXRef-File--etc-profile.d-locale.sh",
382 | "fileName": "/etc/profile.d/locale.sh",
383 | "licenseConcluded": "NOASSERTION",
384 | "checksums": [
385 | {
386 | "algorithm": "SHA1",
387 | "checksumValue": "4bc8fe596ef5996c5f572f32b61a94ec7515a01c"
388 | },
389 | {
390 | "algorithm": "SHA256",
391 | "checksumValue": "84eb9034099d759ff08e6da5a731cacfc63a319547ad0f1dfc1c64853aca93f2"
392 | },
393 | {
394 | "algorithm": "SHA512",
395 | "checksumValue": "b2fc9b72846a43a45ba9a8749e581cef34d1915836833b51b7919dfbf4e275b7d55fec4dea7b23df3796380910971a41331e53e8cf0d304834e3da02cc135e5a"
396 | }
397 | ]
398 | },
399 | {
400 | "SPDXID": "SPDXRef-File--etc-protocols",
401 | "fileName": "/etc/protocols",
402 | "licenseConcluded": "NOASSERTION",
403 | "checksums": [
404 | {
405 | "algorithm": "SHA1",
406 | "checksumValue": "a262a5a77be01aad99a98cf20ff28735da3cac37"
407 | },
408 | {
409 | "algorithm": "SHA256",
410 | "checksumValue": "a90a2be9c2a88be6fbfc1fc73ba76f34698377bb19513e5de503dbb0bfe13be1"
411 | },
412 | {
413 | "algorithm": "SHA512",
414 | "checksumValue": "eadc83e47fcc354ab83fd109bee452bda170886fb684e67faf615930c11480919505f4af60c685b124efc54af0ded9522663132f911eac6622144f8b4c8be695"
415 | }
416 | ]
417 | },
418 | {
419 | "SPDXID": "SPDXRef-File--etc-secfixes.d-wolfi",
420 | "fileName": "/etc/secfixes.d/wolfi",
421 | "licenseConcluded": "NOASSERTION",
422 | "checksums": [
423 | {
424 | "algorithm": "SHA1",
425 | "checksumValue": "5fff5aea306234708b1952c565904638ddb8c477"
426 | },
427 | {
428 | "algorithm": "SHA256",
429 | "checksumValue": "fe0d31329e650f504c836dc259f5509cbfe6431920bf4b2b5b1d75dd02083145"
430 | },
431 | {
432 | "algorithm": "SHA512",
433 | "checksumValue": "20b4da4d331bc7d180f539ed4a141bdbe003e2c91c71c73ec0133a8d9be6f34e33f2ca115acb242a2b5987bf87d49707e484f431a938fb21dbda6d55fe16256b"
434 | }
435 | ]
436 | },
437 | {
438 | "SPDXID": "SPDXRef-File--etc-services",
439 | "fileName": "/etc/services",
440 | "licenseConcluded": "NOASSERTION",
441 | "checksums": [
442 | {
443 | "algorithm": "SHA1",
444 | "checksumValue": "f562c2bf922d2a0e0c1fb4567cd461d48edbc907"
445 | },
446 | {
447 | "algorithm": "SHA256",
448 | "checksumValue": "d85f9ab44e46d6605d749935cf9827a38f767b0e5e56ae8d948ef67e0759e52d"
449 | },
450 | {
451 | "algorithm": "SHA512",
452 | "checksumValue": "adfae0d2f569c2a2f413b7e27683a007fc8ca689b8c3349672fe0dcb6208c192ede4402eff09c604b7e7b4fd9d8df93b875efa5bdaa6c14ff1d8022a7caad5cd"
453 | }
454 | ]
455 | },
456 | {
457 | "SPDXID": "SPDXRef-File--etc-shadow",
458 | "fileName": "/etc/shadow",
459 | "licenseConcluded": "NOASSERTION",
460 | "checksums": [
461 | {
462 | "algorithm": "SHA1",
463 | "checksumValue": "98289d2ed72352c3d570e5ceb6af3508d363375c"
464 | },
465 | {
466 | "algorithm": "SHA256",
467 | "checksumValue": "9011a201093d11103f6126a778028e5e9c4ef99835ca23569c4cbcbae51d8964"
468 | },
469 | {
470 | "algorithm": "SHA512",
471 | "checksumValue": "8937e4572694513aac54f3686fa0163f4d7076fd6ff339709e22f3d5f94292ed038860edb7162d0ca5e620a82ad0706ce20ac469af2a458cf9debc24b03fd518"
472 | }
473 | ]
474 | },
475 | {
476 | "SPDXID": "SPDXRef-File--etc-shells",
477 | "fileName": "/etc/shells",
478 | "licenseConcluded": "NOASSERTION",
479 | "checksums": [
480 | {
481 | "algorithm": "SHA1",
482 | "checksumValue": "611f0df9a9db1911e7f93d8cc229ef6248026048"
483 | },
484 | {
485 | "algorithm": "SHA256",
486 | "checksumValue": "35fa7f9244d299e08104d223b43e92d746dadb7d7b2d7df6281a60f675b0237d"
487 | },
488 | {
489 | "algorithm": "SHA512",
490 | "checksumValue": "0fcec5d1e1de10272735bcce634ba0d5629f07f8f5b127269072e0d34ac118d7526fd0b424081ef6bcf2dbf1090c25aa060cc88bb2bcbcff22a63006e7f1924a"
491 | }
492 | ]
493 | },
494 | {
495 | "SPDXID": "SPDXRef-File--lib64-ld-linux-x86-64.so.2",
496 | "fileName": "/lib64/ld-linux-x86-64.so.2",
497 | "licenseConcluded": "NOASSERTION",
498 | "checksums": [
499 | {
500 | "algorithm": "SHA1",
501 | "checksumValue": "92367fbd5a3ec8c47ef2c17c5fbba92d42246fbe"
502 | },
503 | {
504 | "algorithm": "SHA256",
505 | "checksumValue": "61773a3ef82f2f0832ef69f3741aeb1cb28758fb47bc87971d1e953612b623eb"
506 | },
507 | {
508 | "algorithm": "SHA512",
509 | "checksumValue": "601bcb0f2a9da6ab4c5145881aa0f5b11756d44051c88a26fe059cf2bdae32ad80483ab1376603724197db7aeece64799b6c88634988067c50f2d3f9eacc9cb1"
510 | }
511 | ]
512 | },
513 | {
514 | "SPDXID": "SPDXRef-File--etc-ld.so.conf",
515 | "fileName": "/etc/ld.so.conf",
516 | "licenseConcluded": "NOASSERTION",
517 | "checksums": [
518 | {
519 | "algorithm": "SHA1",
520 | "checksumValue": "d55863b9861caa7835f7a7878b648652543316dc"
521 | },
522 | {
523 | "algorithm": "SHA256",
524 | "checksumValue": "4fdfcdfbc49472b5cc928d4d7ead19646ae0e1733a04c7c905ac7309b178567c"
525 | },
526 | {
527 | "algorithm": "SHA512",
528 | "checksumValue": "4a38035c75a1646267ccefa3b6cc1f877003ab22fa42bb339a3b289fbc9c932e25f5b32c69df3d0d5adebce60dfb47604e85c6afd957b4d1aa02211ffce932c8"
529 | }
530 | ]
531 | },
532 | {
533 | "SPDXID": "SPDXRef-File--etc-rpc",
534 | "fileName": "/etc/rpc",
535 | "licenseConcluded": "NOASSERTION",
536 | "checksums": [
537 | {
538 | "algorithm": "SHA1",
539 | "checksumValue": "8c68c8283757db3e910865b245077387f9166a08"
540 | },
541 | {
542 | "algorithm": "SHA256",
543 | "checksumValue": "3b24a975dcde688434258566813a83ce256a4c73efd7a8a9c3998327b0b4de68"
544 | },
545 | {
546 | "algorithm": "SHA512",
547 | "checksumValue": "e0f9aa2d9ab153486923ad2a73eca5088593f4d85c43eedbc813d6fb00683292aba3757c90bd6ab953b7d5ce237fe721c84bdee1fcb12dd890ae35f6f924797e"
548 | }
549 | ]
550 | },
551 | {
552 | "SPDXID": "SPDXRef-File--lib64-libBrokenLocale.so.1",
553 | "fileName": "/lib64/libBrokenLocale.so.1",
554 | "licenseConcluded": "NOASSERTION",
555 | "checksums": [
556 | {
557 | "algorithm": "SHA1",
558 | "checksumValue": "327b0178b5ed6dee6d1998a9b9621fa08bbf1c4e"
559 | },
560 | {
561 | "algorithm": "SHA256",
562 | "checksumValue": "22000f827338ec01cd647d6f8b58f55a9e998f6375a69dfe7f486a47bf935984"
563 | },
564 | {
565 | "algorithm": "SHA512",
566 | "checksumValue": "f550bebd1f1d46f1f7eb79fc636db6a1d6d74ea7a48b6134714ee1de90a4c94613a77c275c55a4ab5752817d4d0cf2bde7d0c4b742ddb13509577eba8bda136d"
567 | }
568 | ]
569 | },
570 | {
571 | "SPDXID": "SPDXRef-File--lib64-libanl.so.1",
572 | "fileName": "/lib64/libanl.so.1",
573 | "licenseConcluded": "NOASSERTION",
574 | "checksums": [
575 | {
576 | "algorithm": "SHA1",
577 | "checksumValue": "65ea5828171cd0ea2a781ee6c8c81390c48ecde0"
578 | },
579 | {
580 | "algorithm": "SHA256",
581 | "checksumValue": "dd780cf190711478002d34ac9e50e1f7ad7e19fa66cba16be2c9308621af7646"
582 | },
583 | {
584 | "algorithm": "SHA512",
585 | "checksumValue": "bf0bb9af0bb6a3f7bf39ed2e387b733e702741a3951ef9db9576f7bd347e30b2ff6a6582e6a3b8f818fc090398c46b7711adca4aa9febde4faa85f66e1c3d0e5"
586 | }
587 | ]
588 | },
589 | {
590 | "SPDXID": "SPDXRef-File--lib64-libc.so.6",
591 | "fileName": "/lib64/libc.so.6",
592 | "licenseConcluded": "NOASSERTION",
593 | "checksums": [
594 | {
595 | "algorithm": "SHA1",
596 | "checksumValue": "9a69bcb25106e25c07b7eaec91c1587de271ab7f"
597 | },
598 | {
599 | "algorithm": "SHA256",
600 | "checksumValue": "fb8c614791dab45ea48e61acb5a9d030df7a7c189f8d36b71908bb62930a4be2"
601 | },
602 | {
603 | "algorithm": "SHA512",
604 | "checksumValue": "c81684f109d17fd50cfc56bca720b7edab954bf88a5f4b7d3656b5b60d143173d1b0e528c828c582ea201a633b43e6062d190ed7aee5f49087a5fa18a7292784"
605 | }
606 | ]
607 | },
608 | {
609 | "SPDXID": "SPDXRef-File--lib64-libcC95mallocC95debug.so.0",
610 | "fileName": "/lib64/libc_malloc_debug.so.0",
611 | "licenseConcluded": "NOASSERTION",
612 | "checksums": [
613 | {
614 | "algorithm": "SHA1",
615 | "checksumValue": "260ae3fe2332e6d16c78a33b6dc7d101944eaea3"
616 | },
617 | {
618 | "algorithm": "SHA256",
619 | "checksumValue": "a8601495cf1e6eb774b9b88c24d22bd416d0350eeffc58f83324a4deb5930786"
620 | },
621 | {
622 | "algorithm": "SHA512",
623 | "checksumValue": "d8353c45e66d482cbb1591f5d203495fb7432dc0030d9dd21fb68833fc14ad756a6265e03379d818c29efef44906ae04a418c3ce3766f6efca71e5f5635f184a"
624 | }
625 | ]
626 | },
627 | {
628 | "SPDXID": "SPDXRef-File--lib64-libcrypt.so.1",
629 | "fileName": "/lib64/libcrypt.so.1",
630 | "licenseConcluded": "NOASSERTION",
631 | "checksums": [
632 | {
633 | "algorithm": "SHA1",
634 | "checksumValue": "7a547d4f84d79dfa0eea899269dbccfde6ee6d25"
635 | },
636 | {
637 | "algorithm": "SHA256",
638 | "checksumValue": "1b23b283aa4d14e90e6ebcd580661e17c85fca10f92886b9bb4c46488e83a6ee"
639 | },
640 | {
641 | "algorithm": "SHA512",
642 | "checksumValue": "1e61213a8ecb43962c2112e61c51f25a531ea3f37ef32f8c1cd3323a3960b02b75505df2880ad3d4e0623664f7de5816d708d98c09f6fa71c8c2c33bb4b04d5b"
643 | }
644 | ]
645 | },
646 | {
647 | "SPDXID": "SPDXRef-File--lib64-libdl.so.2",
648 | "fileName": "/lib64/libdl.so.2",
649 | "licenseConcluded": "NOASSERTION",
650 | "checksums": [
651 | {
652 | "algorithm": "SHA1",
653 | "checksumValue": "66f828a2503e6789327334516d9ce28983d91301"
654 | },
655 | {
656 | "algorithm": "SHA256",
657 | "checksumValue": "dc5fa3b44ca5c24d18af169f2536b794a24b94425df7bdd09bd9590bf8b01716"
658 | },
659 | {
660 | "algorithm": "SHA512",
661 | "checksumValue": "93be3aba9262b26113feb8a1cfa45461a0123e1e3cbe8e5cc6581ec4b13ce872677ba8c3c443abe0b3c39be0ca274d34dce9af757722799eff56c4d19598359d"
662 | }
663 | ]
664 | },
665 | {
666 | "SPDXID": "SPDXRef-File--lib64-libm.so.6",
667 | "fileName": "/lib64/libm.so.6",
668 | "licenseConcluded": "NOASSERTION",
669 | "checksums": [
670 | {
671 | "algorithm": "SHA1",
672 | "checksumValue": "835c9425388b31383769db934eade3f3e977530c"
673 | },
674 | {
675 | "algorithm": "SHA256",
676 | "checksumValue": "d73e6c85e5e24d065c2cd89d2ca560ab5247789f378debfb08193802d18039e5"
677 | },
678 | {
679 | "algorithm": "SHA512",
680 | "checksumValue": "b427149a67ffad90c03c4a6f89f7a8e69b9e4332e5e7760dcaa24f495674385cd5135562ae9bd7373141b12f1e048ed52943b61eba258f28849f023858073d42"
681 | }
682 | ]
683 | },
684 | {
685 | "SPDXID": "SPDXRef-File--lib64-libmemusage.so",
686 | "fileName": "/lib64/libmemusage.so",
687 | "licenseConcluded": "NOASSERTION",
688 | "checksums": [
689 | {
690 | "algorithm": "SHA1",
691 | "checksumValue": "79c118836ce424b261885a425d84c29fce3c260d"
692 | },
693 | {
694 | "algorithm": "SHA256",
695 | "checksumValue": "0971a942d513bb98445e51e10b6ea857aeec7c12620939c3ce6d38c538ba1f5c"
696 | },
697 | {
698 | "algorithm": "SHA512",
699 | "checksumValue": "9a9546f7e67af8363f4de1185b9c35ad59599be095f016d1a4cf75e6482edd67a1db9c7e616710d72dbda6ff76215510fd3997804c3d7580c12e6177a2df2716"
700 | }
701 | ]
702 | },
703 | {
704 | "SPDXID": "SPDXRef-File--lib64-libmvec.so.1",
705 | "fileName": "/lib64/libmvec.so.1",
706 | "licenseConcluded": "NOASSERTION",
707 | "checksums": [
708 | {
709 | "algorithm": "SHA1",
710 | "checksumValue": "5a45994a957d32af8d6f27f97d3eff0a619802c2"
711 | },
712 | {
713 | "algorithm": "SHA256",
714 | "checksumValue": "3dbfe93c140cf7150e89b9e5966454dd97d22d0a08a5c9e8c184dac7967772b8"
715 | },
716 | {
717 | "algorithm": "SHA512",
718 | "checksumValue": "fdd4b3ddc67ce24cb36ca6f5efbee21244b72ca30c91032ad0199dd2d5909cf1b502e89d753b0398e1db0c1aed66947a615e419cc4096b6c4384804fd0d3b4dc"
719 | }
720 | ]
721 | },
722 | {
723 | "SPDXID": "SPDXRef-File--lib64-libnsl.so.1",
724 | "fileName": "/lib64/libnsl.so.1",
725 | "licenseConcluded": "NOASSERTION",
726 | "checksums": [
727 | {
728 | "algorithm": "SHA1",
729 | "checksumValue": "24ef0faa3f7a9b61e2614ede6a8c7b3c7a4704a6"
730 | },
731 | {
732 | "algorithm": "SHA256",
733 | "checksumValue": "124b235c407e67ea250f41613c2682275e9ed994357875249816d75ff716ba58"
734 | },
735 | {
736 | "algorithm": "SHA512",
737 | "checksumValue": "ddeb37e2581765f6faef72ebd851b7f58442316c6b63b04b6bab0be22ddba7b351d7e972d758aa8c3c9dfb3f8414e97339b4f4304d1b8889a7351efc5c32c485"
738 | }
739 | ]
740 | },
741 | {
742 | "SPDXID": "SPDXRef-File--lib64-libnssC95compat.so.2",
743 | "fileName": "/lib64/libnss_compat.so.2",
744 | "licenseConcluded": "NOASSERTION",
745 | "checksums": [
746 | {
747 | "algorithm": "SHA1",
748 | "checksumValue": "06d0792859be744ba15f852343aa20c7c41a5e8c"
749 | },
750 | {
751 | "algorithm": "SHA256",
752 | "checksumValue": "387dbab0434bd88a435695149f579a080bfcd4812eb34872e8b0de40ccafe551"
753 | },
754 | {
755 | "algorithm": "SHA512",
756 | "checksumValue": "b45efae541046b1e8661ab46fccb0e2a03caa64d10f1f1faba0aff4376ccf6ab608494669c2155e701ad338490bd3fecf7e1bbaf064bc7082b965d969bd7faea"
757 | }
758 | ]
759 | },
760 | {
761 | "SPDXID": "SPDXRef-File--lib64-libnssC95dns.so.2",
762 | "fileName": "/lib64/libnss_dns.so.2",
763 | "licenseConcluded": "NOASSERTION",
764 | "checksums": [
765 | {
766 | "algorithm": "SHA1",
767 | "checksumValue": "ed6551cae890f6169663996e67f85a11949b667a"
768 | },
769 | {
770 | "algorithm": "SHA256",
771 | "checksumValue": "d4a9ca720bb0f5b5017c77565c05c3c2f13f555f48a966abddde327a692ab339"
772 | },
773 | {
774 | "algorithm": "SHA512",
775 | "checksumValue": "6c08332d21a2fe7e9840ff2e2733fb449537a519a71bc9664598de51756d8ee2ab6c4db13015471e55736122decc375671b9a5f27fc311dcf60b34b641e08eae"
776 | }
777 | ]
778 | },
779 | {
780 | "SPDXID": "SPDXRef-File--lib64-libnssC95files.so.2",
781 | "fileName": "/lib64/libnss_files.so.2",
782 | "licenseConcluded": "NOASSERTION",
783 | "checksums": [
784 | {
785 | "algorithm": "SHA1",
786 | "checksumValue": "88aadee27bf51d1a2982c5cc8f8edd1f891f9293"
787 | },
788 | {
789 | "algorithm": "SHA256",
790 | "checksumValue": "efda4e24f91ea28057719451a9580be6187c72b39713141f8dff1a1872bafbb2"
791 | },
792 | {
793 | "algorithm": "SHA512",
794 | "checksumValue": "11759b7c6772c73ab4d52b24efdeb9c17533c0ef41103c08ee6d4fa6f679f0ecf15702da8a1389eb77f31afa00b01fdd1eb7fc691f9f7fecb5d47d5793e36843"
795 | }
796 | ]
797 | },
798 | {
799 | "SPDXID": "SPDXRef-File--lib64-libpthread.so.0",
800 | "fileName": "/lib64/libpthread.so.0",
801 | "licenseConcluded": "NOASSERTION",
802 | "checksums": [
803 | {
804 | "algorithm": "SHA1",
805 | "checksumValue": "a3cf8bf5f5c2088d448f1b78564a7d05ac3462dd"
806 | },
807 | {
808 | "algorithm": "SHA256",
809 | "checksumValue": "0116fa0a3eeb825de356d4a58a1b5be1ee86daa3398287a78ca9510f54db0f03"
810 | },
811 | {
812 | "algorithm": "SHA512",
813 | "checksumValue": "8dbc20f83df6a240a5307b9283f8023f36a14dc22641f5c36d17ae05eb46e7f7f6b75b68d5c1f2c66419c1827b19586c43d2ac5e8059d900c947c438b0470e94"
814 | }
815 | ]
816 | },
817 | {
818 | "SPDXID": "SPDXRef-File--lib64-libresolv.so.2",
819 | "fileName": "/lib64/libresolv.so.2",
820 | "licenseConcluded": "NOASSERTION",
821 | "checksums": [
822 | {
823 | "algorithm": "SHA1",
824 | "checksumValue": "8c6145d433d59d198dee47df4b48503a666da6f2"
825 | },
826 | {
827 | "algorithm": "SHA256",
828 | "checksumValue": "0dba6fdcd523a9e7220fdb7fc74796a0d32a61e458a5b0169779634b28ba540d"
829 | },
830 | {
831 | "algorithm": "SHA512",
832 | "checksumValue": "fd251af4ca1133a03b0426d5756bac714ed1089ae663a15f6dbaa0d0b86430c36bb4e5adb5690c812c233126a666b6077bdb77c3599e7ba55b6c99ad0a507933"
833 | }
834 | ]
835 | },
836 | {
837 | "SPDXID": "SPDXRef-File--lib64-librt.so.1",
838 | "fileName": "/lib64/librt.so.1",
839 | "licenseConcluded": "NOASSERTION",
840 | "checksums": [
841 | {
842 | "algorithm": "SHA1",
843 | "checksumValue": "68251fb2539affae7442214693cea02be4deb02b"
844 | },
845 | {
846 | "algorithm": "SHA256",
847 | "checksumValue": "a2c9ec49314e65f29174c4e8b13099e8bf984db2c8830a400b9505c2965d4631"
848 | },
849 | {
850 | "algorithm": "SHA512",
851 | "checksumValue": "bcb51cacf054c98ea4dba4e66eece412a8dd9c88aab5e6012385dbd22179a3f631eb48dffded1f389767b8e05237dfb05cb59b8ca36f4acd540dffbd1a35c845"
852 | }
853 | ]
854 | },
855 | {
856 | "SPDXID": "SPDXRef-File--lib64-libthreadC95db.so.1",
857 | "fileName": "/lib64/libthread_db.so.1",
858 | "licenseConcluded": "NOASSERTION",
859 | "checksums": [
860 | {
861 | "algorithm": "SHA1",
862 | "checksumValue": "c4a38d829f9c6bf368cdda8a014c0c8f91a2044d"
863 | },
864 | {
865 | "algorithm": "SHA256",
866 | "checksumValue": "f21da0b3e7c26cf1a79e1c5a4489d1380755d17f9c207008c25a34bc66375c34"
867 | },
868 | {
869 | "algorithm": "SHA512",
870 | "checksumValue": "f2f0645938bd461da6a03abc9bf5e038487e7c8072d5c96611b585f874c3509842bf86bb514f5bef3af128c25843150092455e435433e6fe58694e39a5385498"
871 | }
872 | ]
873 | },
874 | {
875 | "SPDXID": "SPDXRef-File--lib64-libutil.so.1",
876 | "fileName": "/lib64/libutil.so.1",
877 | "licenseConcluded": "NOASSERTION",
878 | "checksums": [
879 | {
880 | "algorithm": "SHA1",
881 | "checksumValue": "2c326b171f0f8121dedf065a8abdca19db099166"
882 | },
883 | {
884 | "algorithm": "SHA256",
885 | "checksumValue": "a18d5ddd84729d04136686c539f3de686757ed58f04d77a0c4271e48384f1a98"
886 | },
887 | {
888 | "algorithm": "SHA512",
889 | "checksumValue": "3075c42b3eee8c69ebb4450e3d11428650298465267a95dca8a934edb1efb58dd667b4c34396834d85004696b3166442b01c1e6ad1c2bd1683d500570f0dc671"
890 | }
891 | ]
892 | },
893 | {
894 | "SPDXID": "SPDXRef-File--sbin-ldconfig",
895 | "fileName": "/sbin/ldconfig",
896 | "licenseConcluded": "NOASSERTION",
897 | "checksums": [
898 | {
899 | "algorithm": "SHA1",
900 | "checksumValue": "bb93c2d1036a60d2b12f2efddf995c890755d14e"
901 | },
902 | {
903 | "algorithm": "SHA256",
904 | "checksumValue": "891d6d7d25a2c43dc59a4578789e2d24622c8f5856b5132921d68246bea35f87"
905 | },
906 | {
907 | "algorithm": "SHA512",
908 | "checksumValue": "f4f216d480e101dc4a3aad0dd7a7a7ed70ee39d66f381e6b307163878d52189014c0f5d30a15dcfa28fb6646fab22ff156ca473b2edc1632f08e732852149f24"
909 | }
910 | ]
911 | },
912 | {
913 | "SPDXID": "SPDXRef-File--usr-lib-libbrotlicommon.so.1.0.9",
914 | "fileName": "/usr/lib/libbrotlicommon.so.1.0.9",
915 | "licenseConcluded": "NOASSERTION",
916 | "checksums": [
917 | {
918 | "algorithm": "SHA1",
919 | "checksumValue": "cedc1eb8badf3949c5a0f301c7ee90e5ed7b4978"
920 | },
921 | {
922 | "algorithm": "SHA256",
923 | "checksumValue": "cf76aaa32afea875887f13dcf1bc337f4c147762c9bab5e7f34f610fc1894e59"
924 | },
925 | {
926 | "algorithm": "SHA512",
927 | "checksumValue": "ddce988ce026fcce2d4ecc37cace24bc2542bca2d3fd0508fb0831fe9705c8eb3effaf2c4bcb913a91fe85ef7f6dd9612fcd474b3a742ffb2bef6f22e415ed78"
928 | }
929 | ]
930 | },
931 | {
932 | "SPDXID": "SPDXRef-File--usr-lib-libbrotlidec.so.1.0.9",
933 | "fileName": "/usr/lib/libbrotlidec.so.1.0.9",
934 | "licenseConcluded": "NOASSERTION",
935 | "checksums": [
936 | {
937 | "algorithm": "SHA1",
938 | "checksumValue": "93e5d5273b0fd0872c60abc009cddbe1eab9d80d"
939 | },
940 | {
941 | "algorithm": "SHA256",
942 | "checksumValue": "ab648b1bb7b208b3ebc716c3fe3072b0143f690a796c203a9b211a0e648f5929"
943 | },
944 | {
945 | "algorithm": "SHA512",
946 | "checksumValue": "7963d2fbae66e3bbe29293b5cc7f6d586c3ea5227e2ee434fb759f096b3a8c60415bd85986d08858537a091f2442a9e5ebf6dd8c3f5e2900260a1000bc1a54db"
947 | }
948 | ]
949 | },
950 | {
951 | "SPDXID": "SPDXRef-File--usr-lib64-libgccC95s.so.1",
952 | "fileName": "/usr/lib64/libgcc_s.so.1",
953 | "licenseConcluded": "NOASSERTION",
954 | "checksums": [
955 | {
956 | "algorithm": "SHA1",
957 | "checksumValue": "33711e9a72fbc0acaa3694ae3c8c8c6cdd61997f"
958 | },
959 | {
960 | "algorithm": "SHA256",
961 | "checksumValue": "eb14ad9295bf6ee39d98620d4bdb308cfa6706838316158f210469e2d737ca75"
962 | },
963 | {
964 | "algorithm": "SHA512",
965 | "checksumValue": "74d25cddcac38535316512d9b22f2a50db6cb07932f69380f79e820b75fba35dccdc6e3a5817975df733abb80dea9db4beacc457ba56a1c78d545a587e85a970"
966 | }
967 | ]
968 | },
969 | {
970 | "SPDXID": "SPDXRef-File--usr-lib-libnghttp2.so.14.24.2",
971 | "fileName": "/usr/lib/libnghttp2.so.14.24.2",
972 | "licenseConcluded": "NOASSERTION",
973 | "checksums": [
974 | {
975 | "algorithm": "SHA1",
976 | "checksumValue": "dd76a34bbfd78bf56aa2feddfdeca4fb18b88334"
977 | },
978 | {
979 | "algorithm": "SHA256",
980 | "checksumValue": "c5c8cd9a935db18770ad1e2e61506896989a22a9846b0e5af98f6e8cef2ce969"
981 | },
982 | {
983 | "algorithm": "SHA512",
984 | "checksumValue": "01a7722d421c2ae27ad63c1351d6cb8e21a9886165b24234cb67292ea1aca30a2d3561a7d7557a49431e955c787081d2427d1a0c49a5f68516bce331d30e1eb7"
985 | }
986 | ]
987 | },
988 | {
989 | "SPDXID": "SPDXRef-File--lib-libz.so.1.2.13",
990 | "fileName": "/lib/libz.so.1.2.13",
991 | "licenseConcluded": "NOASSERTION",
992 | "checksums": [
993 | {
994 | "algorithm": "SHA1",
995 | "checksumValue": "9b00adb3ba6510f80a34c8149e26a080e1df07cd"
996 | },
997 | {
998 | "algorithm": "SHA256",
999 | "checksumValue": "14386fc28b11efa99ddb41c83efe131b545025153687e895e249c73b9609a625"
1000 | },
1001 | {
1002 | "algorithm": "SHA512",
1003 | "checksumValue": "ed1fc98db59604ccad0e8651210378e9c3403721eef578b1e6eb3035c7ee854bced47f8de9f6791c892ec3c27f5ebfe05a7a3625fb12089f256a26580ee57bdd"
1004 | }
1005 | ]
1006 | },
1007 | {
1008 | "SPDXID": "SPDXRef-File--usr-share-man-man3-zlib.3",
1009 | "fileName": "/usr/share/man/man3/zlib.3",
1010 | "licenseConcluded": "NOASSERTION",
1011 | "checksums": [
1012 | {
1013 | "algorithm": "SHA1",
1014 | "checksumValue": "e4eef29d98cc16751f1dac42317b677955ceec94"
1015 | },
1016 | {
1017 | "algorithm": "SHA256",
1018 | "checksumValue": "aefd0162070fcb0379dc18e27b039253cd98c148104c1097dd60e0d0b435e564"
1019 | },
1020 | {
1021 | "algorithm": "SHA512",
1022 | "checksumValue": "b9eb98bc8922d415ad242c34f45289fc4a3c586a39d9b34b1868fa4db94789d62b2b1aef7a9919d52ad63c6b07a54568ee9b8bfd38718b70d03264eb833cae20"
1023 | }
1024 | ]
1025 | },
1026 | {
1027 | "SPDXID": "SPDXRef-File--usr-lib-libcurl.so.4.8.0",
1028 | "fileName": "/usr/lib/libcurl.so.4.8.0",
1029 | "licenseConcluded": "NOASSERTION",
1030 | "checksums": [
1031 | {
1032 | "algorithm": "SHA1",
1033 | "checksumValue": "f3ae11065cafc14e27a1410ae8be28e600bb8336"
1034 | },
1035 | {
1036 | "algorithm": "SHA256",
1037 | "checksumValue": "4f232eeb99e1663d07f0af1af6ea262bf594934b694228e71fd8f159f9a19f32"
1038 | },
1039 | {
1040 | "algorithm": "SHA512",
1041 | "checksumValue": "8044d0df34242699ad73bfe99b9ac3d6bbdaa4f8ebce1e23ee5c7f9fe59db8ad7b01fe94e886941793aee802008a35b05a30bc51426db796aa21e5e91b7ed9be"
1042 | }
1043 | ]
1044 | },
1045 | {
1046 | "SPDXID": "SPDXRef-File--usr-bin-curl",
1047 | "fileName": "/usr/bin/curl",
1048 | "licenseConcluded": "NOASSERTION",
1049 | "checksums": [
1050 | {
1051 | "algorithm": "SHA1",
1052 | "checksumValue": "defee82004d22fc92ab81c0c952a62a2172bda8c"
1053 | },
1054 | {
1055 | "algorithm": "SHA256",
1056 | "checksumValue": "ad291c9572af8fc2ec8fd78d295adf7132c60ad3d10488fb63d120fc967a4132"
1057 | },
1058 | {
1059 | "algorithm": "SHA512",
1060 | "checksumValue": "5940d8647907831e77ec00d81b318ca06655dbb0fd36d112684b03947412f0f98ea85b32548bc0877f3d7ce8f4de9b2c964062df44742b98c8e9bd851faecce9"
1061 | }
1062 | ]
1063 | }
1064 | ],
1065 | "packages": [
1066 | {
1067 | "SPDXID": "SPDXRef-Package-sha256-47fed8868b46b060efb8699dc40e981a0c785650223e03602d8c4493fc75b68c",
1068 | "name": "sha256:47fed8868b46b060efb8699dc40e981a0c785650223e03602d8c4493fc75b68c",
1069 | "filesAnalyzed": false,
1070 | "description": "apko container image",
1071 | "downloadLocation": "NOASSERTION",
1072 | "primaryPackagePurpose": "CONTAINER",
1073 | "checksums": [
1074 | {
1075 | "algorithm": "SHA256",
1076 | "checksumValue": "47fed8868b46b060efb8699dc40e981a0c785650223e03602d8c4493fc75b68c"
1077 | }
1078 | ],
1079 | "externalRefs": [
1080 | {
1081 | "referenceCategory": "PACKAGE-MANAGER",
1082 | "referenceLocator": "pkg:oci/curl@sha256:47fed8868b46b060efb8699dc40e981a0c785650223e03602d8c4493fc75b68c?arch=amd64\u0026mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson\u0026os=linux",
1083 | "referenceType": "purl"
1084 | }
1085 | ]
1086 | },
1087 | {
1088 | "SPDXID": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1089 | "name": "sha256:c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1090 | "versionInfo": "20230201",
1091 | "filesAnalyzed": false,
1092 | "description": "apko operating system layer",
1093 | "downloadLocation": "NOASSERTION",
1094 | "externalRefs": [
1095 | {
1096 | "referenceCategory": "PACKAGE-MANAGER",
1097 | "referenceLocator": "pkg:oci/curl@sha256:c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707?arch=amd64\u0026mediaType=application%2Fvnd.oci.image.layer.v1.tar%2Bgzip\u0026os=linux",
1098 | "referenceType": "purl"
1099 | }
1100 | ]
1101 | },
1102 | {
1103 | "SPDXID": "SPDXRef-Package-ca-certificates-bundle-20230506-r0",
1104 | "name": "ca-certificates-bundle",
1105 | "versionInfo": "20230506-r0",
1106 | "filesAnalyzed": true,
1107 | "hasFiles": [
1108 | "SPDXRef-File--etc-ssl-certs-ca-certificates.crt"
1109 | ],
1110 | "licenseConcluded": "NOASSERTION",
1111 | "licenseDeclared": "MPL-2.0 AND MIT",
1112 | "downloadLocation": "NOASSERTION",
1113 | "copyrightText": "\n",
1114 | "externalRefs": [
1115 | {
1116 | "referenceCategory": "PACKAGE_MANAGER",
1117 | "referenceLocator": "pkg:apk/wolfi/ca-certificates-bundle@20230506-r0?arch=x86_64",
1118 | "referenceType": "purl"
1119 | }
1120 | ],
1121 | "packageVerificationCode": {
1122 | "packageVerificationCodeValue": "d98736c880d3536649f0593cd6ef1168a5683a06"
1123 | }
1124 | },
1125 | {
1126 | "SPDXID": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1127 | "name": "glibc-locale-posix",
1128 | "versionInfo": "2.37-r7",
1129 | "filesAnalyzed": true,
1130 | "hasFiles": [
1131 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95ADDRESS",
1132 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95COLLATE",
1133 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95CTYPE",
1134 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95IDENTIFICATION",
1135 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MEASUREMENT",
1136 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MESSAGES-SYSC95LCC95MESSAGES",
1137 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MONETARY",
1138 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95NAME",
1139 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95NUMERIC",
1140 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95PAPER",
1141 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95TELEPHONE",
1142 | "SPDXRef-File--usr-lib-locale-C.utf8-LCC95TIME"
1143 | ],
1144 | "licenseConcluded": "NOASSERTION",
1145 | "licenseDeclared": "GPL-3.0-or-later",
1146 | "downloadLocation": "NOASSERTION",
1147 | "copyrightText": "\n",
1148 | "externalRefs": [
1149 | {
1150 | "referenceCategory": "PACKAGE_MANAGER",
1151 | "referenceLocator": "pkg:apk/wolfi/glibc-locale-posix@2.37-r7?arch=x86_64",
1152 | "referenceType": "purl"
1153 | }
1154 | ],
1155 | "packageVerificationCode": {
1156 | "packageVerificationCodeValue": "02aee1f1f24b311064d298bf69b9a8dab482232d"
1157 | }
1158 | },
1159 | {
1160 | "SPDXID": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1161 | "name": "wolfi-baselayout",
1162 | "versionInfo": "20230201-r2",
1163 | "filesAnalyzed": true,
1164 | "hasFiles": [
1165 | "SPDXRef-File--etc-group",
1166 | "SPDXRef-File--etc-hosts",
1167 | "SPDXRef-File--etc-nsswitch.conf",
1168 | "SPDXRef-File--etc-os-release",
1169 | "SPDXRef-File--etc-passwd",
1170 | "SPDXRef-File--etc-profile",
1171 | "SPDXRef-File--etc-profile.d-locale.sh",
1172 | "SPDXRef-File--etc-protocols",
1173 | "SPDXRef-File--etc-secfixes.d-wolfi",
1174 | "SPDXRef-File--etc-services",
1175 | "SPDXRef-File--etc-shadow",
1176 | "SPDXRef-File--etc-shells"
1177 | ],
1178 | "licenseConcluded": "NOASSERTION",
1179 | "licenseDeclared": "MIT",
1180 | "downloadLocation": "NOASSERTION",
1181 | "copyrightText": "\n",
1182 | "externalRefs": [
1183 | {
1184 | "referenceCategory": "PACKAGE_MANAGER",
1185 | "referenceLocator": "pkg:apk/wolfi/wolfi-baselayout@20230201-r2?arch=x86_64",
1186 | "referenceType": "purl"
1187 | }
1188 | ],
1189 | "packageVerificationCode": {
1190 | "packageVerificationCodeValue": "a63308da2be71a067fdcc5f7608fe5d33783ffbb"
1191 | }
1192 | },
1193 | {
1194 | "SPDXID": "SPDXRef-Package-ld-linux-2.37-r7",
1195 | "name": "ld-linux",
1196 | "versionInfo": "2.37-r7",
1197 | "filesAnalyzed": true,
1198 | "hasFiles": [
1199 | "SPDXRef-File--lib64-ld-linux-x86-64.so.2"
1200 | ],
1201 | "licenseConcluded": "NOASSERTION",
1202 | "licenseDeclared": "GPL-3.0-or-later",
1203 | "downloadLocation": "NOASSERTION",
1204 | "copyrightText": "\n",
1205 | "externalRefs": [
1206 | {
1207 | "referenceCategory": "PACKAGE_MANAGER",
1208 | "referenceLocator": "pkg:apk/wolfi/ld-linux@2.37-r7?arch=x86_64",
1209 | "referenceType": "purl"
1210 | }
1211 | ],
1212 | "packageVerificationCode": {
1213 | "packageVerificationCodeValue": "2b58fb1067c37804bb6a16c67258e6de16db2b74"
1214 | }
1215 | },
1216 | {
1217 | "SPDXID": "SPDXRef-Package-glibc-2.37-r6",
1218 | "name": "glibc",
1219 | "versionInfo": "2.37-r6",
1220 | "filesAnalyzed": true,
1221 | "hasFiles": [
1222 | "SPDXRef-File--etc-ld.so.conf",
1223 | "SPDXRef-File--etc-rpc",
1224 | "SPDXRef-File--lib64-libBrokenLocale.so.1",
1225 | "SPDXRef-File--lib64-libanl.so.1",
1226 | "SPDXRef-File--lib64-libc.so.6",
1227 | "SPDXRef-File--lib64-libcC95mallocC95debug.so.0",
1228 | "SPDXRef-File--lib64-libcrypt.so.1",
1229 | "SPDXRef-File--lib64-libdl.so.2",
1230 | "SPDXRef-File--lib64-libm.so.6",
1231 | "SPDXRef-File--lib64-libmemusage.so",
1232 | "SPDXRef-File--lib64-libmvec.so.1",
1233 | "SPDXRef-File--lib64-libnsl.so.1",
1234 | "SPDXRef-File--lib64-libnssC95compat.so.2",
1235 | "SPDXRef-File--lib64-libnssC95dns.so.2",
1236 | "SPDXRef-File--lib64-libnssC95files.so.2",
1237 | "SPDXRef-File--lib64-libpthread.so.0",
1238 | "SPDXRef-File--lib64-libresolv.so.2",
1239 | "SPDXRef-File--lib64-librt.so.1",
1240 | "SPDXRef-File--lib64-libthreadC95db.so.1",
1241 | "SPDXRef-File--lib64-libutil.so.1",
1242 | "SPDXRef-File--sbin-ldconfig"
1243 | ],
1244 | "licenseConcluded": "NOASSERTION",
1245 | "licenseDeclared": "GPL-3.0-or-later",
1246 | "downloadLocation": "NOASSERTION",
1247 | "copyrightText": "\n",
1248 | "externalRefs": [
1249 | {
1250 | "referenceCategory": "PACKAGE_MANAGER",
1251 | "referenceLocator": "pkg:apk/wolfi/glibc@2.37-r6?arch=x86_64",
1252 | "referenceType": "purl"
1253 | }
1254 | ],
1255 | "packageVerificationCode": {
1256 | "packageVerificationCodeValue": "de44296ef898d1b65503de8da8f65bf6d3c82c47"
1257 | }
1258 | },
1259 | {
1260 | "SPDXID": "SPDXRef-Package-libbrotlicommon1-1.0.9-r3",
1261 | "name": "libbrotlicommon1",
1262 | "versionInfo": "1.0.9-r3",
1263 | "filesAnalyzed": true,
1264 | "hasFiles": [
1265 | "SPDXRef-File--usr-lib-libbrotlicommon.so.1.0.9"
1266 | ],
1267 | "licenseConcluded": "NOASSERTION",
1268 | "licenseDeclared": "MIT",
1269 | "downloadLocation": "NOASSERTION",
1270 | "copyrightText": "\n",
1271 | "externalRefs": [
1272 | {
1273 | "referenceCategory": "PACKAGE_MANAGER",
1274 | "referenceLocator": "pkg:apk/wolfi/libbrotlicommon1@1.0.9-r3?arch=x86_64",
1275 | "referenceType": "purl"
1276 | }
1277 | ],
1278 | "packageVerificationCode": {
1279 | "packageVerificationCodeValue": "5c42b99275f089513dd5c718ee5abcaac88f9e3d"
1280 | }
1281 | },
1282 | {
1283 | "SPDXID": "SPDXRef-Package-libbrotlidec1-1.0.9-r3",
1284 | "name": "libbrotlidec1",
1285 | "versionInfo": "1.0.9-r3",
1286 | "filesAnalyzed": true,
1287 | "hasFiles": [
1288 | "SPDXRef-File--usr-lib-libbrotlidec.so.1.0.9"
1289 | ],
1290 | "licenseConcluded": "NOASSERTION",
1291 | "licenseDeclared": "MIT",
1292 | "downloadLocation": "NOASSERTION",
1293 | "copyrightText": "\n",
1294 | "externalRefs": [
1295 | {
1296 | "referenceCategory": "PACKAGE_MANAGER",
1297 | "referenceLocator": "pkg:apk/wolfi/libbrotlidec1@1.0.9-r3?arch=x86_64",
1298 | "referenceType": "purl"
1299 | }
1300 | ],
1301 | "packageVerificationCode": {
1302 | "packageVerificationCodeValue": "51a90e00de471ebfb87b5fede3aef8e6e6c56ed5"
1303 | }
1304 | },
1305 | {
1306 | "SPDXID": "SPDXRef-Package-libgcc-13.1.0-r1",
1307 | "name": "libgcc",
1308 | "versionInfo": "13.1.0-r1",
1309 | "filesAnalyzed": true,
1310 | "hasFiles": [
1311 | "SPDXRef-File--usr-lib64-libgccC95s.so.1"
1312 | ],
1313 | "licenseConcluded": "NOASSERTION",
1314 | "licenseDeclared": "GPL-3.0-or-later",
1315 | "downloadLocation": "NOASSERTION",
1316 | "copyrightText": "\n",
1317 | "externalRefs": [
1318 | {
1319 | "referenceCategory": "PACKAGE_MANAGER",
1320 | "referenceLocator": "pkg:apk/wolfi/libgcc@13.1.0-r1?arch=x86_64",
1321 | "referenceType": "purl"
1322 | }
1323 | ],
1324 | "packageVerificationCode": {
1325 | "packageVerificationCodeValue": "d420d355a0f6b351fd0922eda4686ed7d20d13a4"
1326 | }
1327 | },
1328 | {
1329 | "SPDXID": "SPDXRef-Package-libnghttp2-14-1.53.0-r0",
1330 | "name": "libnghttp2-14",
1331 | "versionInfo": "1.53.0-r0",
1332 | "filesAnalyzed": true,
1333 | "hasFiles": [
1334 | "SPDXRef-File--usr-lib-libnghttp2.so.14.24.2"
1335 | ],
1336 | "licenseConcluded": "NOASSERTION",
1337 | "licenseDeclared": "MIT",
1338 | "downloadLocation": "NOASSERTION",
1339 | "copyrightText": "\n",
1340 | "externalRefs": [
1341 | {
1342 | "referenceCategory": "PACKAGE_MANAGER",
1343 | "referenceLocator": "pkg:apk/wolfi/libnghttp2-14@1.53.0-r0?arch=x86_64",
1344 | "referenceType": "purl"
1345 | }
1346 | ],
1347 | "packageVerificationCode": {
1348 | "packageVerificationCodeValue": "43943395f3dc2c68bfe0eb5ca82b2455846696a1"
1349 | }
1350 | },
1351 | {
1352 | "SPDXID": "SPDXRef-Package-zlib-1.2.13-r3",
1353 | "name": "zlib",
1354 | "versionInfo": "1.2.13-r3",
1355 | "filesAnalyzed": true,
1356 | "hasFiles": [
1357 | "SPDXRef-File--lib-libz.so.1.2.13",
1358 | "SPDXRef-File--usr-share-man-man3-zlib.3"
1359 | ],
1360 | "licenseConcluded": "NOASSERTION",
1361 | "licenseDeclared": "MPL-2.0 AND MIT",
1362 | "downloadLocation": "NOASSERTION",
1363 | "copyrightText": "TODO\n",
1364 | "externalRefs": [
1365 | {
1366 | "referenceCategory": "PACKAGE_MANAGER",
1367 | "referenceLocator": "pkg:apk/wolfi/zlib@1.2.13-r3?arch=x86_64",
1368 | "referenceType": "purl"
1369 | }
1370 | ],
1371 | "packageVerificationCode": {
1372 | "packageVerificationCodeValue": "32abb07d47675352453da0b96da439daec22164c"
1373 | }
1374 | },
1375 | {
1376 | "SPDXID": "SPDXRef-Package-libcurl-rustls4-8.1.2-r0",
1377 | "name": "libcurl-rustls4",
1378 | "versionInfo": "8.1.2-r0",
1379 | "filesAnalyzed": true,
1380 | "hasFiles": [
1381 | "SPDXRef-File--usr-lib-libcurl.so.4.8.0"
1382 | ],
1383 | "licenseConcluded": "NOASSERTION",
1384 | "licenseDeclared": "MIT",
1385 | "downloadLocation": "NOASSERTION",
1386 | "copyrightText": "\n",
1387 | "externalRefs": [
1388 | {
1389 | "referenceCategory": "PACKAGE_MANAGER",
1390 | "referenceLocator": "pkg:apk/wolfi/libcurl-rustls4@8.1.2-r0?arch=x86_64",
1391 | "referenceType": "purl"
1392 | }
1393 | ],
1394 | "packageVerificationCode": {
1395 | "packageVerificationCodeValue": "d0c8989164bcb3a684bfa2a46c6b7c09f7f7b5c6"
1396 | }
1397 | },
1398 | {
1399 | "SPDXID": "SPDXRef-Package-curl-8.1.2-r0",
1400 | "name": "curl",
1401 | "versionInfo": "8.1.2-r0",
1402 | "filesAnalyzed": true,
1403 | "hasFiles": [
1404 | "SPDXRef-File--usr-bin-curl"
1405 | ],
1406 | "licenseConcluded": "NOASSERTION",
1407 | "licenseDeclared": "MIT",
1408 | "downloadLocation": "NOASSERTION",
1409 | "copyrightText": "\n",
1410 | "externalRefs": [
1411 | {
1412 | "referenceCategory": "PACKAGE_MANAGER",
1413 | "referenceLocator": "pkg:apk/wolfi/curl@8.1.2-r0?arch=x86_64",
1414 | "referenceType": "purl"
1415 | }
1416 | ],
1417 | "packageVerificationCode": {
1418 | "packageVerificationCodeValue": "86db7f97b251f9c2907879b3b0dd5929c49e0a79"
1419 | }
1420 | }
1421 | ],
1422 | "relationships": [
1423 | {
1424 | "spdxElementId": "SPDXRef-Package-sha256-47fed8868b46b060efb8699dc40e981a0c785650223e03602d8c4493fc75b68c",
1425 | "relationshipType": "CONTAINS",
1426 | "relatedSpdxElement": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707"
1427 | },
1428 | {
1429 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1430 | "relationshipType": "CONTAINS",
1431 | "relatedSpdxElement": "SPDXRef-Package-ca-certificates-bundle-20230506-r0"
1432 | },
1433 | {
1434 | "spdxElementId": "SPDXRef-Package-ca-certificates-bundle-20230506-r0",
1435 | "relationshipType": "CONTAINS",
1436 | "relatedSpdxElement": "SPDXRef-File--etc-ssl-certs-ca-certificates.crt"
1437 | },
1438 | {
1439 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1440 | "relationshipType": "CONTAINS",
1441 | "relatedSpdxElement": "SPDXRef-Package-glibc-locale-posix-2.37-r7"
1442 | },
1443 | {
1444 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1445 | "relationshipType": "CONTAINS",
1446 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95ADDRESS"
1447 | },
1448 | {
1449 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1450 | "relationshipType": "CONTAINS",
1451 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95COLLATE"
1452 | },
1453 | {
1454 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1455 | "relationshipType": "CONTAINS",
1456 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95CTYPE"
1457 | },
1458 | {
1459 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1460 | "relationshipType": "CONTAINS",
1461 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95IDENTIFICATION"
1462 | },
1463 | {
1464 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1465 | "relationshipType": "CONTAINS",
1466 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MEASUREMENT"
1467 | },
1468 | {
1469 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1470 | "relationshipType": "CONTAINS",
1471 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MESSAGES-SYSC95LCC95MESSAGES"
1472 | },
1473 | {
1474 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1475 | "relationshipType": "CONTAINS",
1476 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95MONETARY"
1477 | },
1478 | {
1479 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1480 | "relationshipType": "CONTAINS",
1481 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95NAME"
1482 | },
1483 | {
1484 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1485 | "relationshipType": "CONTAINS",
1486 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95NUMERIC"
1487 | },
1488 | {
1489 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1490 | "relationshipType": "CONTAINS",
1491 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95PAPER"
1492 | },
1493 | {
1494 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1495 | "relationshipType": "CONTAINS",
1496 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95TELEPHONE"
1497 | },
1498 | {
1499 | "spdxElementId": "SPDXRef-Package-glibc-locale-posix-2.37-r7",
1500 | "relationshipType": "CONTAINS",
1501 | "relatedSpdxElement": "SPDXRef-File--usr-lib-locale-C.utf8-LCC95TIME"
1502 | },
1503 | {
1504 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1505 | "relationshipType": "CONTAINS",
1506 | "relatedSpdxElement": "SPDXRef-Package-wolfi-baselayout-20230201-r2"
1507 | },
1508 | {
1509 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1510 | "relationshipType": "CONTAINS",
1511 | "relatedSpdxElement": "SPDXRef-File--etc-group"
1512 | },
1513 | {
1514 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1515 | "relationshipType": "CONTAINS",
1516 | "relatedSpdxElement": "SPDXRef-File--etc-hosts"
1517 | },
1518 | {
1519 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1520 | "relationshipType": "CONTAINS",
1521 | "relatedSpdxElement": "SPDXRef-File--etc-nsswitch.conf"
1522 | },
1523 | {
1524 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1525 | "relationshipType": "CONTAINS",
1526 | "relatedSpdxElement": "SPDXRef-File--etc-os-release"
1527 | },
1528 | {
1529 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1530 | "relationshipType": "CONTAINS",
1531 | "relatedSpdxElement": "SPDXRef-File--etc-passwd"
1532 | },
1533 | {
1534 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1535 | "relationshipType": "CONTAINS",
1536 | "relatedSpdxElement": "SPDXRef-File--etc-profile"
1537 | },
1538 | {
1539 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1540 | "relationshipType": "CONTAINS",
1541 | "relatedSpdxElement": "SPDXRef-File--etc-profile.d-locale.sh"
1542 | },
1543 | {
1544 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1545 | "relationshipType": "CONTAINS",
1546 | "relatedSpdxElement": "SPDXRef-File--etc-protocols"
1547 | },
1548 | {
1549 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1550 | "relationshipType": "CONTAINS",
1551 | "relatedSpdxElement": "SPDXRef-File--etc-secfixes.d-wolfi"
1552 | },
1553 | {
1554 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1555 | "relationshipType": "CONTAINS",
1556 | "relatedSpdxElement": "SPDXRef-File--etc-services"
1557 | },
1558 | {
1559 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1560 | "relationshipType": "CONTAINS",
1561 | "relatedSpdxElement": "SPDXRef-File--etc-shadow"
1562 | },
1563 | {
1564 | "spdxElementId": "SPDXRef-Package-wolfi-baselayout-20230201-r2",
1565 | "relationshipType": "CONTAINS",
1566 | "relatedSpdxElement": "SPDXRef-File--etc-shells"
1567 | },
1568 | {
1569 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1570 | "relationshipType": "CONTAINS",
1571 | "relatedSpdxElement": "SPDXRef-Package-ld-linux-2.37-r7"
1572 | },
1573 | {
1574 | "spdxElementId": "SPDXRef-Package-ld-linux-2.37-r7",
1575 | "relationshipType": "CONTAINS",
1576 | "relatedSpdxElement": "SPDXRef-File--lib64-ld-linux-x86-64.so.2"
1577 | },
1578 | {
1579 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1580 | "relationshipType": "CONTAINS",
1581 | "relatedSpdxElement": "SPDXRef-Package-glibc-2.37-r6"
1582 | },
1583 | {
1584 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1585 | "relationshipType": "CONTAINS",
1586 | "relatedSpdxElement": "SPDXRef-File--etc-ld.so.conf"
1587 | },
1588 | {
1589 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1590 | "relationshipType": "CONTAINS",
1591 | "relatedSpdxElement": "SPDXRef-File--etc-rpc"
1592 | },
1593 | {
1594 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1595 | "relationshipType": "CONTAINS",
1596 | "relatedSpdxElement": "SPDXRef-File--lib64-libBrokenLocale.so.1"
1597 | },
1598 | {
1599 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1600 | "relationshipType": "CONTAINS",
1601 | "relatedSpdxElement": "SPDXRef-File--lib64-libanl.so.1"
1602 | },
1603 | {
1604 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1605 | "relationshipType": "CONTAINS",
1606 | "relatedSpdxElement": "SPDXRef-File--lib64-libc.so.6"
1607 | },
1608 | {
1609 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1610 | "relationshipType": "CONTAINS",
1611 | "relatedSpdxElement": "SPDXRef-File--lib64-libcC95mallocC95debug.so.0"
1612 | },
1613 | {
1614 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1615 | "relationshipType": "CONTAINS",
1616 | "relatedSpdxElement": "SPDXRef-File--lib64-libcrypt.so.1"
1617 | },
1618 | {
1619 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1620 | "relationshipType": "CONTAINS",
1621 | "relatedSpdxElement": "SPDXRef-File--lib64-libdl.so.2"
1622 | },
1623 | {
1624 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1625 | "relationshipType": "CONTAINS",
1626 | "relatedSpdxElement": "SPDXRef-File--lib64-libm.so.6"
1627 | },
1628 | {
1629 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1630 | "relationshipType": "CONTAINS",
1631 | "relatedSpdxElement": "SPDXRef-File--lib64-libmemusage.so"
1632 | },
1633 | {
1634 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1635 | "relationshipType": "CONTAINS",
1636 | "relatedSpdxElement": "SPDXRef-File--lib64-libmvec.so.1"
1637 | },
1638 | {
1639 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1640 | "relationshipType": "CONTAINS",
1641 | "relatedSpdxElement": "SPDXRef-File--lib64-libnsl.so.1"
1642 | },
1643 | {
1644 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1645 | "relationshipType": "CONTAINS",
1646 | "relatedSpdxElement": "SPDXRef-File--lib64-libnssC95compat.so.2"
1647 | },
1648 | {
1649 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1650 | "relationshipType": "CONTAINS",
1651 | "relatedSpdxElement": "SPDXRef-File--lib64-libnssC95dns.so.2"
1652 | },
1653 | {
1654 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1655 | "relationshipType": "CONTAINS",
1656 | "relatedSpdxElement": "SPDXRef-File--lib64-libnssC95files.so.2"
1657 | },
1658 | {
1659 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1660 | "relationshipType": "CONTAINS",
1661 | "relatedSpdxElement": "SPDXRef-File--lib64-libpthread.so.0"
1662 | },
1663 | {
1664 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1665 | "relationshipType": "CONTAINS",
1666 | "relatedSpdxElement": "SPDXRef-File--lib64-libresolv.so.2"
1667 | },
1668 | {
1669 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1670 | "relationshipType": "CONTAINS",
1671 | "relatedSpdxElement": "SPDXRef-File--lib64-librt.so.1"
1672 | },
1673 | {
1674 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1675 | "relationshipType": "CONTAINS",
1676 | "relatedSpdxElement": "SPDXRef-File--lib64-libthreadC95db.so.1"
1677 | },
1678 | {
1679 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1680 | "relationshipType": "CONTAINS",
1681 | "relatedSpdxElement": "SPDXRef-File--lib64-libutil.so.1"
1682 | },
1683 | {
1684 | "spdxElementId": "SPDXRef-Package-glibc-2.37-r6",
1685 | "relationshipType": "CONTAINS",
1686 | "relatedSpdxElement": "SPDXRef-File--sbin-ldconfig"
1687 | },
1688 | {
1689 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1690 | "relationshipType": "CONTAINS",
1691 | "relatedSpdxElement": "SPDXRef-Package-libbrotlicommon1-1.0.9-r3"
1692 | },
1693 | {
1694 | "spdxElementId": "SPDXRef-Package-libbrotlicommon1-1.0.9-r3",
1695 | "relationshipType": "CONTAINS",
1696 | "relatedSpdxElement": "SPDXRef-File--usr-lib-libbrotlicommon.so.1.0.9"
1697 | },
1698 | {
1699 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1700 | "relationshipType": "CONTAINS",
1701 | "relatedSpdxElement": "SPDXRef-Package-libbrotlidec1-1.0.9-r3"
1702 | },
1703 | {
1704 | "spdxElementId": "SPDXRef-Package-libbrotlidec1-1.0.9-r3",
1705 | "relationshipType": "CONTAINS",
1706 | "relatedSpdxElement": "SPDXRef-File--usr-lib-libbrotlidec.so.1.0.9"
1707 | },
1708 | {
1709 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1710 | "relationshipType": "CONTAINS",
1711 | "relatedSpdxElement": "SPDXRef-Package-libgcc-13.1.0-r1"
1712 | },
1713 | {
1714 | "spdxElementId": "SPDXRef-Package-libgcc-13.1.0-r1",
1715 | "relationshipType": "CONTAINS",
1716 | "relatedSpdxElement": "SPDXRef-File--usr-lib64-libgccC95s.so.1"
1717 | },
1718 | {
1719 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1720 | "relationshipType": "CONTAINS",
1721 | "relatedSpdxElement": "SPDXRef-Package-libnghttp2-14-1.53.0-r0"
1722 | },
1723 | {
1724 | "spdxElementId": "SPDXRef-Package-libnghttp2-14-1.53.0-r0",
1725 | "relationshipType": "CONTAINS",
1726 | "relatedSpdxElement": "SPDXRef-File--usr-lib-libnghttp2.so.14.24.2"
1727 | },
1728 | {
1729 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1730 | "relationshipType": "CONTAINS",
1731 | "relatedSpdxElement": "SPDXRef-Package-zlib-1.2.13-r3"
1732 | },
1733 | {
1734 | "spdxElementId": "SPDXRef-Package-zlib-1.2.13-r3",
1735 | "relationshipType": "CONTAINS",
1736 | "relatedSpdxElement": "SPDXRef-File--lib-libz.so.1.2.13"
1737 | },
1738 | {
1739 | "spdxElementId": "SPDXRef-Package-zlib-1.2.13-r3",
1740 | "relationshipType": "CONTAINS",
1741 | "relatedSpdxElement": "SPDXRef-File--usr-share-man-man3-zlib.3"
1742 | },
1743 | {
1744 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1745 | "relationshipType": "CONTAINS",
1746 | "relatedSpdxElement": "SPDXRef-Package-libcurl-rustls4-8.1.2-r0"
1747 | },
1748 | {
1749 | "spdxElementId": "SPDXRef-Package-libcurl-rustls4-8.1.2-r0",
1750 | "relationshipType": "CONTAINS",
1751 | "relatedSpdxElement": "SPDXRef-File--usr-lib-libcurl.so.4.8.0"
1752 | },
1753 | {
1754 | "spdxElementId": "SPDXRef-Package-sha256-c6580b9a4fded304babd53a027a183c3f9d11863ba1c847971793a139801f707",
1755 | "relationshipType": "CONTAINS",
1756 | "relatedSpdxElement": "SPDXRef-Package-curl-8.1.2-r0"
1757 | },
1758 | {
1759 | "spdxElementId": "SPDXRef-Package-curl-8.1.2-r0",
1760 | "relationshipType": "CONTAINS",
1761 | "relatedSpdxElement": "SPDXRef-File--usr-bin-curl"
1762 | }
1763 | ]
1764 | }
1765 |
--------------------------------------------------------------------------------