├── .bom.yaml ├── .chainguard └── source.yaml ├── .github ├── dependabot.yaml └── workflows │ ├── go-build-and-test.yaml │ ├── golangci-lint.yaml │ └── release.yaml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yaml ├── LICENSE ├── Makefile ├── README.md ├── docs ├── functions.md └── running.md ├── examples ├── README.md ├── bom-binary.spdx.json ├── bom-github.spdx.json ├── compose.cel ├── curl.spdx.json ├── files.cel ├── getnodebyid.cel ├── loadsbom.cel ├── nodesbypurltype.cel └── packages.cel ├── go.mod ├── go.sum ├── internal └── cmd │ ├── exec.go │ ├── interactive.go │ ├── options.go │ ├── root.go │ └── run.go ├── main.go ├── pkg ├── elements │ ├── bomshell.go │ ├── document.go │ ├── node.go │ └── nodelist.go ├── functions │ ├── functions.go │ ├── functions_test.go │ ├── utility.go │ └── utility_test.go ├── loader │ └── loader.go ├── render │ └── render.go ├── shell │ ├── environment.go │ ├── implementation.go │ ├── implementation_test.go │ ├── runner.go │ ├── runner_implementation.go │ └── shell.go └── ui │ ├── interactive.go │ └── subshell.go ├── release └── ldflags.sh └── tutorial ├── 01.the-sbom-graph.md ├── 02.the-bomshell-model.md ├── 03.invoking-bomshell.md ├── 04.the-workbench.md ├── 05.the-language.md └── 06.sbom-composition.md /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/files.cel: -------------------------------------------------------------------------------- 1 | // This bomshell query extracts all files from the SBOM and returns them 2 | sboms[0].files().ToDocument() 3 | -------------------------------------------------------------------------------- /examples/getnodebyid.cel: -------------------------------------------------------------------------------- 1 | sboms[0].NodeByID("Package-curl-8.1.2-r0") 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/nodesbypurltype.cel: -------------------------------------------------------------------------------- 1 | sboms[0].NodesByPurlType("golang").ToDocument() 2 | -------------------------------------------------------------------------------- /examples/packages.cel: -------------------------------------------------------------------------------- 1 | // This bomshell query extracts all packages from the SBOM and returns them 2 | 3 | sboms[0].packages().ToDocument() 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------