File kbom-0.3.1.obscpio of Package kbom

07070100000000000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001300000000kbom-0.3.1/.github07070100000001000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000002200000000kbom-0.3.1/.github/ISSUE_TEMPLATE07070100000002000081A4000000000000000000000001671FABC1000000AE000000000000000000000000000000000000002D00000000kbom-0.3.1/.github/ISSUE_TEMPLATE/config.ymlblank_issues_enabled: true
contact_links:
  - name: Ask a question
    url: https://github.com/rad-security/kbom/discussions
    about: Please ask and answer questions here.
07070100000003000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001D00000000kbom-0.3.1/.github/workflows07070100000004000081A4000000000000000000000001671FABC100000716000000000000000000000000000000000000002700000000kbom-0.3.1/.github/workflows/build.ymlname: build

on:
  pull_request:

jobs:
  version:
    runs-on: ubuntu-latest
    if: startsWith(github.head_ref, 'renovate') == false
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Detect Version for Docker
        id: docker-version
        run: echo "VERSION=$(SEP="-" scripts/version)" >> $GITHUB_OUTPUT
      - name: Detect Version
        id: version
        run: echo "VERSION=$(scripts/version)" >> $GITHUB_OUTPUT
    outputs:
      docker-version: ${{ steps.docker-version.outputs.VERSION }}
      version: ${{ steps.version.outputs.VERSION }}

  build:
    runs-on: ubuntu-latest
    needs:
      - version
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          # Force version to solve cache restore issue: https://github.com/actions/setup-go/issues/506
          go-version: 1.23.2
          check-latest: true # https://github.com/actions/setup-go#check-latest-version
          cache: true # https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs

      - name: Build
        run: go build -race ./...

      - name: Install GoReleaser
        uses: goreleaser/goreleaser-action@v5
        with:
          version: latest
          install-only: true

      - name: Snapshot
        run: make snapshot
        env:
          GORELEASER_CURRENT_TAG: ${{ needs.version.outputs.docker-version }}

      - name: Grype scan
        id: scan
        uses: anchore/scan-action@v5
        with:
          path: "."
          fail-build: true
          severity-cutoff: medium
          output-format: sarif

      - name: Upload SARIF report
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: ${{ steps.scan.outputs.sarif }}
07070100000005000081A4000000000000000000000001671FABC100000084000000000000000000000000000000000000002500000000kbom-0.3.1/.github/workflows/dco.ymlname: DCO Check

on:
  pull_request:

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: KineticCafe/actions-dco@v1
07070100000006000081A4000000000000000000000001671FABC100000269000000000000000000000000000000000000002F00000000kbom-0.3.1/.github/workflows/golangci-lint.ymlname: golangci-lint

on:
  pull_request:

permissions:
  contents: read

jobs:
  golangci:
    name: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.23'
          check-latest: true # https://github.com/actions/setup-go#check-latest-version
          cache: true # https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v6
        with:
          version: v1.60.3
          args: --timeout=5m
07070100000007000081A4000000000000000000000001671FABC100000938000000000000000000000000000000000000002900000000kbom-0.3.1/.github/workflows/release.ymlname: release

on:
  push:
    tags:
      - "v*"
  workflow_call:
    secrets:
      PERSONAL_ACCESS_TOKEN:
        required: true
      PUBLIC_GCR_JSON_KEY:
        required: true

permissions:
   contents: write # needed to write releases
   id-token: write # needed for keyless signing

jobs:
  version:
    runs-on: ubuntu-latest
    if: startsWith(github.head_ref, 'renovate') == false
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Detect Version for Docker
        id: docker-version
        run: echo "VERSION=$(SEP="-" scripts/version)" >> $GITHUB_OUTPUT
      - name: Detect Version
        id: version
        run: echo "VERSION=$(scripts/version)" >> $GITHUB_OUTPUT
    outputs:
      docker-version: ${{ steps.docker-version.outputs.VERSION }}
      version: ${{ steps.version.outputs.VERSION }}

  goreleaser:
    timeout-minutes: 90
    runs-on: ubuntu-latest
    needs:
      - version
    env:
      SUMMARY: ${{ needs.version.outputs.docker-version }}
      VERSION: ${{ needs.version.outputs.version }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.23'
          check-latest: true # https://github.com/actions/setup-go#check-latest-version
          cache: true # https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs

      - uses: sigstore/cosign-installer@v3
      - uses: anchore/sbom-action/download-syft@v0

      - name: Login to GCR
        uses: docker/login-action@v3
        with:
          registry: us.gcr.io
          username: _json_key
          password: ${{ secrets.PUBLIC_GCR_JSON_KEY }}

      - name: Install GoReleaser
        uses: goreleaser/goreleaser-action@v5
        with:
          version: latest
          install-only: true

      - name: Generate SBOM
        uses: CycloneDX/gh-gomod-generate-sbom@v2
        with:
          args: mod -licenses -json -output bom.json
          version: ^v1

      - name: Release
        if: startsWith(github.ref , 'refs/tags/v') == true
        run: make release
        env:
          GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
07070100000008000081A4000000000000000000000001671FABC10000031E000000000000000000000000000000000000002600000000kbom-0.3.1/.github/workflows/scan.ymlname: scan

on:
  workflow_dispatch:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: '18 10 * * 3'

permissions:
  contents: read

jobs:
  scan-codeql:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: 1.23.x
          cache-dependency-path: |
            **/go.sum
            **/go.mod
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: go
      - name: Autobuild
        uses: github/codeql-action/autobuild@v3
      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3
07070100000009000081A4000000000000000000000001671FABC1000001B9000000000000000000000000000000000000002600000000kbom-0.3.1/.github/workflows/test.ymlname: test

on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.23'
          check-latest: true # https://github.com/actions/setup-go#check-latest-version
          cache: true # https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs

      - name: Test
        run: go test -race ./...
0707010000000A000081A4000000000000000000000001671FABC100000090000000000000000000000000000000000000001600000000kbom-0.3.1/.gitignore# Directories
vendor
release
archive

# Files
.envrc

# KBOM Binaries
/kbom*

# BOM file
bom.json

# Coverage
coverage.out
coverage_report.html
0707010000000B000081A4000000000000000000000001671FABC100000510000000000000000000000000000000000000001900000000kbom-0.3.1/.golangci.ymllinters-settings:
  dupl:
    threshold: 110
  funlen:
    lines: 100
    statements: 50
  goconst:
    min-len: 2
    min-occurrences: 3
  gocritic:
    enabled-tags:
    - diagnostic
    - experimental
    - opinionated
    - performance
    - style
    disabled-checks:
    - dupImport # https://github.com/go-critic/go-critic/issues/845
    - ifElseChain
    - octalLiteral
    - whyNoLint
  gocyclo:
    min-complexity: 15
  goimports:
    local-prefixes: github.com/rad-security
  golint:
    min-confidence: 0
  lll:
    line-length: 140
  maligned:
    suggest-new: true
  misspell:
    locale: US

linters:
  # please, do not use `enable-all`: it's deprecated and will be removed soon.
  # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
  disable-all: true
  enable:
  - bodyclose
  - dogsled
  - dupl
  - errcheck
  - exportloopref
  - funlen
  - goconst
  - gocritic
  - gocyclo
  - gofmt
  - goimports
  - goprintffuncname
  - gosec
  - gosimple
  - govet
  - ineffassign
  - lll
  - misspell
  - nakedret
  - noctx
  - nolintlint
  - staticcheck
  - stylecheck
  - typecheck
  - unconvert
  - unparam
  - unused
  - whitespace

issues:
  exclude-rules:
    - path: _test\.go
      linters:
        - funlen

run:
  timeout: 2m
0707010000000C000081A4000000000000000000000001671FABC100000C28000000000000000000000000000000000000001B00000000kbom-0.3.1/.goreleaser.ymldist: release
env:
  - PACKAGE_NAME=github.com/{{.Env.GITHUB_ORG}}/{{.Env.APP_NAME}}
  - CFG_PACKAGE_NAME=github.com/{{.Env.GITHUB_ORG}}/{{.Env.APP_NAME}}/internal/config
before:
  hooks:
    - go mod tidy

release:
  extra_files:
    - glob: ./bom.json
  github:
    name: kbom
    owner: rad-security
  discussion_category_name: Announcements

builds:
  - binary: kbom
    goos:
      - darwin
      - linux
      - windows
    goarch:
      - "386"
      - amd64
      - arm
      - arm64
    goarm:
      - "7"
    ignore:
      - goos: darwin
        goarch: "386"
    ldflags:
      - -X "{{.Env.CFG_PACKAGE_NAME}}.AppName=kbom"
      - -X "{{.Env.CFG_PACKAGE_NAME}}.AppVersion={{.Env.VERSION}}"
      - -X "{{.Env.CFG_PACKAGE_NAME}}.BuildTime={{.Env.BUILD_TIME}}"
      - -X "{{.Env.CFG_PACKAGE_NAME}}.LastCommitUser={{.Env.LAST_COMMIT_USER}}"
      - -X "{{.Env.CFG_PACKAGE_NAME}}.LastCommitHash={{.Env.LAST_COMMIT_HASH}}"
      - -X "{{.Env.CFG_PACKAGE_NAME}}.LastCommitTime={{.Env.LAST_COMMIT_TIME}}"
    main: ./
    env:
      - CGO_ENABLED=0

# create a source tarball
# https://goreleaser.com/customization/source/
source:
  enabled: true

# creates SBOMs of all archives and the source tarball using syft
# https://goreleaser.com/customization/sbom
sboms:
  - artifacts: archive
  - id: source # Two different sbom configurations need two different IDs
    artifacts: source

# signs the checksum file
# all files (including the sboms) are included in the checksum, so we don't need to sign each one if we don't want to
# https://goreleaser.com/customization/sign
signs:
  - cmd: cosign
    env:
      - COSIGN_EXPERIMENTAL=1
    certificate: '${artifact}.pem'
    args:
      - sign-blob
      - '--output-certificate=${certificate}'
      - '--output-signature=${signature}'
      - '${artifact}'
      - "--yes" # needed on cosign 2.0.0+
    artifacts: checksum
    output: true

brews:
  - repository:
      owner: rad-security
      name: homebrew-kbom
    homepage: "https://github.com/rad-security/kbom"
    description: "The Kubernetes Bill of Materials (KBOM) standard provides insight into container orchestration tools widely used across the industry."
    license: "Apache 2"
    test: |
      system "#{bin}/kbom", "version"

dockers:
  - goos: linux
    goarch: amd64
    dockerfile: build/package/Dockerfile.gorelease
    image_templates:
      - us.gcr.io/{{.Env.GCR_ORG}}/{{.Env.APP_NAME}}:{{- if .IsSnapshot -}}{{ .Env.VERSION }}{{- else -}}{{ .Tag }}{{- end -}}
    build_flag_templates:
      - "--pull"
      - "--label=org.opencontainers.image.created={{.Date}}"
      - "--label=org.opencontainers.image.title={{.ProjectName}}"
      - "--label=org.opencontainers.image.revision={{.FullCommit}}"
      - "--label=org.opencontainers.image.version={{.Version}}"
      - "--label=org.opencontainers.image.source=https://{{.Env.PACKAGE_NAME}}"
      - "--platform=linux/amd64"

checksum:
  name_template: "checksums.txt"

snapshot:
  name_template: "snapshot-{{ .ShortCommit }}-{{ .Timestamp }}"

changelog:
  sort: asc
  filters:
    exclude:
      - "^docs:"
      - "^test:"
0707010000000D000081A4000000000000000000000001671FABC100000288000000000000000000000000000000000000002300000000kbom-0.3.1/.pre-commit-config.yamlrepos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.3.0
    hooks:
      - id: check-byte-order-marker
      - id: check-case-conflict
      - id: check-merge-conflict
      - id: check-symlinks
      - id: detect-aws-credentials
        args: ['--allow-missing-credentials']
      - id: detect-private-key
      - id: end-of-file-fixer
      - id: mixed-line-ending
      - id: trailing-whitespace
  - repo: https://github.com/dnephin/pre-commit-golang.git
    rev: v0.5.0
    hooks:
      - id: go-fmt
      - id: go-imports
      - id: go-mod-tidy
      - id: go-build
      - id: go-unit-tests
      - id: golangci-lint
0707010000000E000081A4000000000000000000000001671FABC10000001C000000000000000000000000000000000000001600000000kbom-0.3.1/CODEOWNERS* @rad-security/engineering
0707010000000F000081A4000000000000000000000001671FABC100000A00000000000000000000000000000000000000001B00000000kbom-0.3.1/CONTRIBUTING.md# Contributing

KBOM is [Apache 2.0 licensed](https://github.com/rad-security/kbom/blob/main/LICENSE) and
accepts contributions via GitHub pull requests. This document outlines
some of the conventions on to make it easier to get your contribution
accepted.

We gratefully welcome improvements to issues and documentation as well as to
code.

## Certificate of Origin

By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution.

We require all commits to be signed. By signing off with your signature, you
certify that you wrote the patch or otherwise have the right to contribute the
material by the rules of the [DCO](DCO):

`Signed-off-by: Firstname Lastname <firstname.lastname@example.com>`

The signature must contain your real name
(sorry, no pseudonyms or anonymous contributions)
If your `user.name` and `user.email` are configured in your Git config,
you can sign your commit automatically with `git commit -s`.

## Communications

To discuss ideas and specifications we use [GitHub Discussions](https://github.com/rad-security/kbom/discussions).

## How to run the KBOM generator in local environment

Prerequisites:

* go >= 1.20
* kind
* golangci-lint

Initialise repo:

```bash
make initialise
```

To generate your first KBOM file we need to have access to a Kubernetes cluster.
If you don't have any you could create your local cluster with `Kind`.

Create kind cluster(optional):

```bash
kind create cluster --name kbom-test
```

Build `kbom` binary:

```bash
make build
```

Generate your first `kbom` file:

```bash
./kbom generate
```

## Acceptance policy

These things will make a PR more likely to be accepted:

* a well-described requirement
* tests for new code
* tests for old code!
* new code and tests follow the conventions in old code and tests
* a good commit message (see below)
* all code must abide [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
* names should abide [What's in a name](https://talks.golang.org/2014/names.slide#1)
* code must build on both Linux and Darwin, via plain `go build`
* code should have appropriate test coverage and tests should be written to work with `go test`

In general, we will merge a PR once one maintainer has endorsed it.
For substantial changes, more people may become involved, and you might
get asked to resubmit the PR or divide the changes into more than one PR.
07070100000010000081A4000000000000000000000001671FABC100002C5D000000000000000000000000000000000000001300000000kbom-0.3.1/LICENSE                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
07070100000011000081A4000000000000000000000001671FABC10000080C000000000000000000000000000000000000001400000000kbom-0.3.1/MakefileAPP_NAME := kbom
GCR_ORG := ksoc-public
GITHUB_ORG := rad-security
GIT_REPO ?= github.com/$(GITHUB_ORG)/$(APP_NAME)
VERSION := $(shell SEP="-" bash scripts/version)

BUILD_TIME ?= $(shell date -u '+%Y-%m-%d %H:%M:%S')
LAST_COMMIT_USER ?= $(shell git log -1 --format='%cn <%ce>')
LAST_COMMIT_HASH ?= $(shell git log -1 --format=%H)
LAST_COMMIT_TIME ?= $(shell git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')

export APP_NAME
export GCR_ORG
export GITHUB_ORG
export VERSION
export BUILD_TIME
export LAST_COMMIT_USER
export LAST_COMMIT_HASH
export LAST_COMMIT_TIME

.PHONY: initialise
initialise: ## Initialises the project, set ups git hooks
	pre-commit install

.PHONY: release
release: ## Builds a release
	goreleaser release --clean --timeout 90m

.PHONY: semtag-%
semtag-%: ## Creates a new tag using semtag
	semtag final -s $*

.PHONY: snapshot
snapshot: ## Builds a snapshot release
	GORELEASER_CURRENT_TAG=$(GORELEASER_CURRENT_TAG) \
		goreleaser build --snapshot --clean --single-target --timeout 90m

.PHONY: docker_push_all
docker_push_all: ## Pushes all docker images
	docker push $$(docker images -a  | grep $(APP_NAME) | awk '{ print $$1 ":" $$2 }')

.PHONY: build
build: ## Builds kbom binary
	CGO_ENABLED=0 \
	go build \
	-v \
	-ldflags "-s -w \
	-X '$(GIT_REPO)/internal/config.AppName=$(APP_NAME)' \
	-X '$(GIT_REPO)/internal/config.AppVersion=$(VERSION)' \
	-X '$(GIT_REPO)/internal/config.BuildTime=$(BUILD_TIME)' \
	-X '$(GIT_REPO)/internal/config.LastCommitUser=$(LAST_COMMIT_USER)' \
	-X '$(GIT_REPO)/internal/config.LastCommitHash=$(LAST_COMMIT_HASH)' \
	-X '$(GIT_REPO)/internal/config.LastCommitTime=$(LAST_COMMIT_TIME)'" \
	-o $(APP_NAME) .

.PHONY: test
test: ## Runs unit tests
	go test -coverprofile coverage.out -v --race ./...
	go tool cover -html=coverage.out -o coverage_report.html

.PHONY: help
help: ## Displays this help screen
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ \
	{ printf "  \033[36m%-15s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
07070100000012000081A4000000000000000000000001671FABC10000094E000000000000000000000000000000000000001500000000kbom-0.3.1/README.md# KBOM - Kubernetes Bill of Materials

![GitHub release (latest by date)](https://img.shields.io/github/v/release/rad-security/kbom)
![Hex.pm](https://img.shields.io/hexpm/l/apa)
[![Go Report Card](https://goreportcard.com/badge/github.com/rad-security/kbom)](https://goreportcard.com/report/github.com/rad-security/kbom)
[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7273/badge)](https://bestpractices.coreinfrastructure.org/projects/7273)

The Kubernetes Bill of Materials (KBOM) standard provides insight into container orchestration tools widely used across the industry.

As a first draft, we have created a rough specification which should fall in line with other Bill of Materials (BOM) standards.

The KBOM project provides an initial specification in JSON and has been constructed for extensibilty across various cloud service providers (CSPs) as well as DIY Kubernetes.

## Getting Started

### Installation

```sh
brew install rad-security/homebrew-kbom/kbom
```

or

```sh
make build
```

### Usage

`KBOM generate` generates a KBOM file for your Kubernetes cluster

```sh
kbom generate [flags]
```

Optional flags include:

```plain
Flags:
  -f, --format string     Format (json, yaml, cyclonedx-json, cyclonedx-xml) (default "json")
  -h, --help              help for generate
  -p, --out-path string   Path to write KBOM file to. Works only with --output=file (default ".")
  -o, --output string     Output (stdout, file) (default "stdout")
      --short             Short - only include metadata, nodes, images and resources counters
```

## Schema

The high level object model can be found [here](docs/schema.md).

## Supported Kubernetes Versions

We have tested *kbom* with all versions newer than *v1.19*, and can confirm that it is fully compatible with each of these versions. This means that you can use our tool with confidence, knowing that it has been thoroughly tested with.

## Supported Cloud Providers

We have tested our tool with all of the main cloud providers, including `Azure`, `AWS`, and `Google Cloud`. Of course it's possible to generate `kbom` file for any K8s cluster, but please have in mind that in some cases not all metadata entries will be set.

## Contributing

KBOM is Apache 2.0 licensed and accepts contributions via GitHub pull requests. See the [CONTRIBUTING](CONTRIBUTING.md) file for details.
07070100000013000081A4000000000000000000000001671FABC1000001BA000000000000000000000000000000000000001700000000kbom-0.3.1/SECURITY.md# Security Policy

## Supported Versions

We currently support the latest release for security patching and will deploy forward releases.

For example if there is a vulnerability in release `v0.1.0` we will fix that release in version `v0.1.1-fix` or `v0.1.1`

## Reporting a Vulnerability

If you are aware of a vulnverability please feel free to disclose it responsibly [here](https://github.com/rad-security/kbom/security/advisories/new).
07070100000014000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001100000000kbom-0.3.1/build07070100000015000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001900000000kbom-0.3.1/build/package07070100000016000081A4000000000000000000000001671FABC100000052000000000000000000000000000000000000002E00000000kbom-0.3.1/build/package/Dockerfile.goreleaseFROM alpine:3.20.0

RUN mkdir /app

COPY kbom /app/kbom

ENTRYPOINT ["/app/kbom"]
07070100000017000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000000F00000000kbom-0.3.1/cmd07070100000018000081A4000000000000000000000001671FABC100001CB3000000000000000000000000000000000000001D00000000kbom-0.3.1/cmd/cyclonexdx.gopackage cmd

import (
	"fmt"
	"hash/fnv"
	"slices"
	"time"

	"github.com/CycloneDX/cyclonedx-go"
	"github.com/google/uuid"
	"github.com/mitchellh/hashstructure/v2"

	"github.com/rad-security/kbom/internal/model"
)

const (
	CdxPrefix           = "cdx:"
	RADPrefix           = "rad:kbom:"
	K8sComponentType    = "k8s:component:type"
	K8sComponentName    = "k8s:component:name"
	K8sComponentVersion = "k8s:component:version"

	ClusterType   = "cluster"
	NodeType      = "node"
	ContainerType = "container"
)

func transformToCycloneDXBOM(kbom *model.KBOM) *cyclonedx.BOM { //nolint:funlen
	cdxBOM := cyclonedx.NewBOM()

	cdxBOM.SerialNumber = uuid.New().URN()
	cdxBOM.Metadata = &cyclonedx.Metadata{
		Timestamp: time.Now().Format(time.RFC3339),
		Tools: &[]cyclonedx.Tool{
			{
				Vendor:  kbom.GeneratedBy.Vendor,
				Name:    kbom.GeneratedBy.Name,
				Version: kbom.GeneratedBy.Version,
			},
		},
	}

	components := []cyclonedx.Component{}
	dependencies := []cyclonedx.Dependency{}
	clusterProperties := []cyclonedx.Property{
		{
			Name:  CdxPrefix + K8sComponentType,
			Value: ClusterType,
		},
		{
			Name:  CdxPrefix + "k8s:component:name",
			Value: kbom.Cluster.Name,
		},
		{
			Name:  RADPrefix + "k8s:cluster:nodes",
			Value: fmt.Sprintf("%d", kbom.Cluster.NodesCount),
		},
	}

	if kbom.Cluster.Location.Name != "" && kbom.Cluster.Location.Name != "unknown" {
		clusterProperties = append(clusterProperties, cyclonedx.Property{
			Name:  RADPrefix + "k8s:cluster:location:name",
			Value: kbom.Cluster.Location.Name,
		})
	}

	if kbom.Cluster.Location.Region != "" {
		clusterProperties = append(clusterProperties, cyclonedx.Property{
			Name:  RADPrefix + "k8s:cluster:location:region",
			Value: kbom.Cluster.Location.Region,
		})
	}

	if kbom.Cluster.Location.Zone != "" {
		clusterProperties = append(clusterProperties, cyclonedx.Property{
			Name:  RADPrefix + "k8s:cluster:location:zone",
			Value: kbom.Cluster.Location.Zone,
		})
	}

	clusterComponent := cyclonedx.Component{
		BOMRef:     kbom.Cluster.BOMRef(),
		Type:       cyclonedx.ComponentTypePlatform,
		Name:       kbom.Cluster.BOMName(),
		Version:    kbom.Cluster.K8sVersion,
		Properties: &clusterProperties,
	}
	cdxBOM.Metadata.Component = &clusterComponent

	clusterDependencies := make(map[string]string)
	for i := range kbom.Cluster.Nodes {
		n := kbom.Cluster.Nodes[i]
		bomRef := id(n)
		components = append(components, cyclonedx.Component{
			BOMRef: bomRef,
			Type:   cyclonedx.ComponentTypePlatform,
			Name:   n.Name,
			Properties: &[]cyclonedx.Property{
				{
					Name:  CdxPrefix + K8sComponentType,
					Value: NodeType,
				},
				{
					Name:  CdxPrefix + K8sComponentName,
					Value: n.Name,
				},
				{
					Name:  RADPrefix + "k8s:node:osImage",
					Value: n.OsImage,
				},
				{
					Name:  RADPrefix + "k8s:node:arch",
					Value: n.Architecture,
				},
				{
					Name:  RADPrefix + "k8s:node:kernel",
					Value: n.KernelVersion,
				},
				{
					Name:  RADPrefix + "k8s:node:bootId",
					Value: n.BootID,
				},
				{
					Name:  RADPrefix + "k8s:node:type",
					Value: n.Type,
				},
				{
					Name:  RADPrefix + "k8s:node:operatingSystem",
					Value: n.OperatingSystem,
				},
				{
					Name:  RADPrefix + "k8s:node:machineId",
					Value: n.MachineID,
				},
				{
					Name:  RADPrefix + "k8s:node:hostname",
					Value: n.Hostname,
				},
				{
					Name:  RADPrefix + "k8s:node:containerRuntimeVersion",
					Value: n.ContainerRuntimeVersion,
				},
				{
					Name:  RADPrefix + "k8s:node:kubeletVersion",
					Value: n.KubeletVersion,
				},
				{
					Name:  RADPrefix + "k8s:node:kubeProxyVersion",
					Value: n.KubeProxyVersion,
				},
				{
					Name:  RADPrefix + "k8s:node:capacity:cpu",
					Value: n.Capacity.CPU,
				},
				{
					Name:  RADPrefix + "k8s:node:capacity:memory",
					Value: n.Capacity.Memory,
				},
				{
					Name:  RADPrefix + "k8s:node:capacity:pods",
					Value: n.Capacity.Pods,
				},
				{
					Name:  RADPrefix + "k8s:node:capacity:ephemeralStorage",
					Value: n.Capacity.EphemeralStorage,
				},
				{
					Name:  RADPrefix + "k8s:node:allocatable:cpu",
					Value: n.Allocatable.CPU,
				},
				{
					Name:  RADPrefix + "k8s:node:allocatable:memory",
					Value: n.Allocatable.Memory,
				},
				{
					Name:  RADPrefix + "k8s:node:allocatable:pods",
					Value: n.Allocatable.Pods,
				},
				{
					Name:  RADPrefix + "k8s:node:allocatable:ephemeralStorage",
					Value: n.Allocatable.EphemeralStorage,
				},
			},
		})
		clusterDependencies[bomRef] = bomRef
	}

	for _, img := range kbom.Cluster.Components.Images {
		bomRef := img.PkgID()
		container := cyclonedx.Component{
			BOMRef:     bomRef,
			Type:       cyclonedx.ComponentTypeContainer,
			Name:       img.Name,
			Version:    img.Digest,
			PackageURL: bomRef,
			Properties: &[]cyclonedx.Property{
				{
					Name:  CdxPrefix + K8sComponentType,
					Value: ContainerType,
				},
				{
					Name:  CdxPrefix + K8sComponentName,
					Value: img.Name,
				},
				{
					Name:  RADPrefix + "pkg:type",
					Value: "oci",
				},
				{
					Name:  RADPrefix + "pkg:name",
					Value: img.Name,
				},
				{
					Name:  RADPrefix + "pkg:version",
					Value: img.Version,
				},
				{
					Name:  RADPrefix + "pkg:digest",
					Value: img.Digest,
				},
			},
		}

		components = append(components, container)

		if img.ControlPlane {
			clusterDependencies[bomRef] = bomRef
		}
	}

	for _, resList := range kbom.Cluster.Components.Resources {
		for _, res := range resList.Resources {
			properties := []cyclonedx.Property{
				{
					Name:  CdxPrefix + K8sComponentType,
					Value: resList.Kind,
				},
				{
					Name:  CdxPrefix + K8sComponentName,
					Value: res.Name,
				},
				{
					Name:  RADPrefix + "k8s:component:apiVersion",
					Value: resList.APIVersion,
				},
			}

			if version, ok := res.AdditionalProperties["version"]; ok {
				properties = append(properties, cyclonedx.Property{
					Name:  RADPrefix + K8sComponentVersion,
					Value: version,
				})
			}

			if resList.Namespaced {
				properties = append(properties, cyclonedx.Property{
					Name:  RADPrefix + "k8s:component:namespace",
					Value: res.Namespace,
				})
			}

			resource := cyclonedx.Component{
				BOMRef:     id(res),
				Type:       cyclonedx.ComponentTypeApplication, // TODO: this is not perfect but we don't have a better option
				Name:       res.Name,
				Version:    res.APIVersion,
				Properties: &properties,
			}

			components = append(components, resource)
		}
	}

	clusterDependenciesArr := make([]string, 0)
	for _, dep := range clusterDependencies {
		clusterDependenciesArr = append(clusterDependenciesArr, dep)
	}
	slices.Sort(clusterDependenciesArr)

	dependencies = append(dependencies,
		cyclonedx.Dependency{
			Ref:          clusterComponent.BOMRef,
			Dependencies: &clusterDependenciesArr,
		},
	)

	cdxBOM.Components = &components
	cdxBOM.Dependencies = &dependencies

	return cdxBOM
}

func id(obj interface{}) string {
	f, err := hashstructure.Hash(obj, hashstructure.FormatV2, &hashstructure.HashOptions{
		ZeroNil:      true,
		SlicesAsSets: true,
		Hasher:       fnv.New64(),
	})

	// this should never happen, but if it does, we don't want to crash - use empty string
	if err != nil {
		fmt.Printf("failed to hash object: %v", err)
		return ""
	}

	return fmt.Sprintf("%016x", f)
}
07070100000019000081A4000000000000000000000001671FABC1000003BA000000000000000000000000000000000000001900000000kbom-0.3.1/cmd/format.gopackage cmd

import "fmt"

type Format struct {
	Name          string
	FileExtension string
}

var JSONFormat = Format{
	Name:          "json",
	FileExtension: "json",
}

var YAMLFormat = Format{
	Name:          "yaml",
	FileExtension: "yaml",
}

var CycloneDXJsonFormat = Format{
	Name:          "cyclonedx-json",
	FileExtension: "json",
}

var CycloneDXXMLFormat = Format{
	Name:          "cyclonedx-xml",
	FileExtension: "xml",
}

func formatNames() []string {
	return []string{
		JSONFormat.Name,
		YAMLFormat.Name,
		CycloneDXJsonFormat.Name,
		CycloneDXXMLFormat.Name,
	}
}

func formatFromName(name string) (Format, error) {
	switch name {
	case JSONFormat.Name:
		return JSONFormat, nil
	case YAMLFormat.Name:
		return YAMLFormat, nil
	case CycloneDXJsonFormat.Name:
		return CycloneDXJsonFormat, nil
	case CycloneDXXMLFormat.Name:
		return CycloneDXXMLFormat, nil
	default:
		return Format{}, fmt.Errorf("format %q is not supported", name)
	}
}
0707010000001A000081A4000000000000000000000001671FABC100001137000000000000000000000000000000000000001B00000000kbom-0.3.1/cmd/generate.gopackage cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path"
	"strings"
	"time"

	"github.com/CycloneDX/cyclonedx-go"
	"github.com/google/uuid"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v3"

	"github.com/rad-security/kbom/internal/config"
	"github.com/rad-security/kbom/internal/kube"
	"github.com/rad-security/kbom/internal/model"
	"github.com/rad-security/kbom/internal/utils"
)

const (
	Company     = "RAD Security"
	BOMFormat   = "rad"
	SpecVersion = "0.3"

	StdOutput  = "stdout"
	FileOutput = "file"
)

var (
	short   bool
	output  string
	format  string
	outPath string

	generatedAt = time.Now()
	kbomID      = uuid.New().String()
)

var GenerateCmd = &cobra.Command{
	Use:   "generate",
	Short: "Generate KBOM for the provided K8s cluster",
	RunE:  runGenerate,
}

func init() {
	GenerateCmd.Flags().BoolVar(&short, "short", false, "Short - only include metadata, nodes, images and resources counters")
	GenerateCmd.Flags().StringVarP(&output, "output", "o", StdOutput, "Output (stdout, file)")
	GenerateCmd.Flags().StringVarP(&format, "format", "f", JSONFormat.Name, fmt.Sprintf("Format (%s)", strings.Join(formatNames(), ", ")))
	GenerateCmd.Flags().StringVarP(&outPath, "out-path", "p", ".", "Path to write KBOM file to. Works only with --output=file")

	utils.BindFlags(GenerateCmd)
}

func runGenerate(cmd *cobra.Command, _ []string) error {
	k8sClient, err := kube.NewClient(k8sContext)
	if err != nil {
		return err
	}

	return generateKBOM(k8sClient)
}

func generateKBOM(k8sClient kube.K8sClient) error {
	parsedFormat, err := formatFromName(format)
	if err != nil {
		return err
	}

	ctx := context.Background()
	k8sVersion, caCertDigest, err := k8sClient.Metadata(ctx)
	if err != nil {
		return err
	}

	clusterName, err := k8sClient.ClusterName(ctx)
	if err != nil {
		return err
	}

	full := !short
	nodes, err := k8sClient.AllNodes(ctx, full)
	if err != nil {
		return err
	}

	loc, err := k8sClient.Location(ctx)
	if err != nil {
		return err
	}

	allImages, err := k8sClient.AllImages(ctx)
	if err != nil {
		return err
	}

	resources, err := k8sClient.AllResources(ctx, full)
	if err != nil {
		return err
	}

	kbom := model.KBOM{
		ID:          kbomID,
		BOMFormat:   BOMFormat,
		SpecVersion: SpecVersion,
		GeneratedAt: generatedAt,
		GeneratedBy: model.Tool{
			Vendor:     Company,
			BuildTime:  config.BuildTime,
			Name:       config.AppName,
			Version:    config.AppVersion,
			Commit:     config.LastCommitHash,
			CommitTime: config.LastCommitTime,
		},
		Cluster: model.Cluster{
			Name:         clusterName,
			Location:     loc,
			CNIVersion:   "", // TODO: get CNI version
			K8sVersion:   k8sVersion,
			CACertDigest: caCertDigest,
			NodesCount:   len(nodes),
			Nodes:        nodes,
			Components: model.Components{
				Images:    allImages,
				Resources: resources,
			},
		},
	}

	if err := printKBOM(&kbom, parsedFormat); err != nil {
		return err
	}

	return nil
}

func printKBOM(kbom *model.KBOM, f Format) error {
	writer, err := getWriter(kbom, f)
	if err != nil {
		return err
	}
	defer writer.Close()

	switch format {
	case JSONFormat.Name:
		enc := json.NewEncoder(writer)
		enc.SetIndent("", "  ")
		return enc.Encode(kbom)
	case YAMLFormat.Name:
		enc := yaml.NewEncoder(writer)
		enc.SetIndent(2)
		return enc.Encode(kbom)
	case CycloneDXJsonFormat.Name:
		cyclonexKbom := transformToCycloneDXBOM(kbom)
		enc := cyclonedx.NewBOMEncoder(writer, cyclonedx.BOMFileFormatJSON)
		enc.SetPretty(true)
		enc.SetEscapeHTML(false)
		return enc.Encode(cyclonexKbom)
	case CycloneDXXMLFormat.Name:
		cyclonexKbom := transformToCycloneDXBOM(kbom)
		enc := cyclonedx.NewBOMEncoder(writer, cyclonedx.BOMFileFormatXML)
		enc.SetPretty(true)
		enc.SetEscapeHTML(false)
		return enc.Encode(cyclonexKbom)
	default:
		return fmt.Errorf("format %q is not supported", format)
	}
}

func getWriter(kbom *model.KBOM, format Format) (io.WriteCloser, error) {
	switch output {
	case StdOutput:
		return out, nil
	case FileOutput:
		formattedTime := kbom.GeneratedAt.Format("2006-01-02-15-04-05")
		key := kbom.ID[:8]
		if len(kbom.Cluster.CACertDigest) > 8 {
			key = kbom.Cluster.CACertDigest[:8]
		}

		f, err := os.Create(path.Join(outPath, fmt.Sprintf("kbom-%s-%s.%s", key, formattedTime, format.FileExtension)))
		if err != nil {
			return nil, err
		}

		return f, nil
	default:
		return nil, fmt.Errorf("output %q is not supported", output)
	}
}
0707010000001B000081A4000000000000000000000001671FABC1000038C2000000000000000000000000000000000000002000000000kbom-0.3.1/cmd/generate_test.gopackage cmd

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/rad-security/kbom/internal/kube"
	"github.com/rad-security/kbom/internal/model"
)

func TestGenerateKBOM(t *testing.T) {
	type testCase struct {
		name string

		// mocks
		clientMock kube.K8sClient
		idMock     string
		timeMock   string

		// flags
		output string
		format string

		expectedOut string
		expectedErr error
	}

	testCases := []testCase{
		{
			name: "metadata error",
			clientMock: &mockedK8sClient{
				metadata: func(context.Context) (string, string, error) {
					return "", "", fmt.Errorf("metadata error")
				},
			},
			expectedErr: fmt.Errorf("metadata error"),
		},
		{
			name: "location error",
			clientMock: &mockedK8sClient{
				location: func(context.Context) (*model.Location, error) {
					return nil, fmt.Errorf("location error")
				},
			},
			expectedErr: fmt.Errorf("location error"),
		},
		{
			name: "all nodes error",
			clientMock: &mockedK8sClient{
				allNodes: func(context.Context, bool) ([]model.Node, error) {
					return nil, fmt.Errorf("all nodes error")
				},
			},
			expectedErr: fmt.Errorf("all nodes error"),
		},
		{
			name: "all resources error",
			clientMock: &mockedK8sClient{
				allResources: func(context.Context, bool) (map[string]model.ResourceList, error) {
					return nil, fmt.Errorf("all resources error")
				},
			},
			expectedErr: fmt.Errorf("all resources error"),
		},
		{
			name: "all images error",
			clientMock: &mockedK8sClient{
				allImages: func(context.Context) ([]model.Image, error) {
					return nil, fmt.Errorf("all images error")
				},
			},
			expectedErr: fmt.Errorf("all images error"),
		},
		{
			name:        "print KBOM - stdout - wrong format",
			clientMock:  &mockedK8sClient{},
			timeMock:    "2023-04-26T10:00:00.000000+00:00",
			idMock:      "00000001",
			format:      "wrong",
			expectedErr: fmt.Errorf("format \"wrong\" is not supported"),
		},
		{
			name:        "print KBOM - wrong output - JSON",
			clientMock:  &mockedK8sClient{},
			timeMock:    "2023-04-26T10:00:00.000000+00:00",
			idMock:      "00000001",
			output:      "wrong",
			expectedErr: fmt.Errorf("output \"wrong\" is not supported"),
		},
		{
			name: "print full KBOM - stdout - json",
			clientMock: &mockedK8sClient{
				clusterName: func(context.Context) (string, error) {
					return "test-cluster", nil
				},
				metadata: func(context.Context) (string, string, error) {
					return "012345678", "1.25.1", nil
				},
				location: func(context.Context) (*model.Location, error) {
					return &model.Location{
						Name:   "aws",
						Region: "us-east-1",
						Zone:   "us-east-1a",
					}, nil
				},
				allNodes: func(context.Context, bool) ([]model.Node, error) {
					return []model.Node{
						{
							Name:     "ip-10-0-65-00.us-east-1.compute.internal",
							Type:     "t3.small",
							Hostname: "ip-10-0-65-00.us-east-1.compute.internal",
							Capacity: &model.Capacity{
								CPU:              "2",
								Memory:           "1970512Ki",
								Pods:             "11",
								EphemeralStorage: "524275692Ki",
							},
							Allocatable: &model.Capacity{
								CPU:              "1930m",
								Memory:           "1483088Ki",
								Pods:             "11",
								EphemeralStorage: "482098735124",
							},
							Labels: map[string]string{
								"beta.kubernetes.io/arch":          "amd64",
								"beta.kubernetes.io/instance-type": "t3.small",
								"beta.kubernetes.io/os":            "linux",
								"topology.kubernetes.io/region":    "us-west-2",
								"topology.kubernetes.io/zone":      "us-west-2a",
							},
							Annotations: map[string]string{
								"node.alpha.kubernetes.io/ttl": "0",
							},
							MachineID:               "00001",
							Architecture:            "amd64",
							ContainerRuntimeVersion: "containerd://1.6.8+bottlerocket",
							BootID:                  "00001",
							KernelVersion:           "5.15.59",
							KubeProxyVersion:        "v1.24.6",
							KubeletVersion:          "v1.24.6",
							OperatingSystem:         "linux",
							OsImage:                 "Bottlerocket OS 1.11.1 (aws-k8s-1.24)",
						},
						{
							Name:     "ip-10-0-65-01.us-east-1.compute.internal",
							Type:     "t3.small",
							Hostname: "ip-10-0-65-01.us-east-1.compute.internal",
							Capacity: &model.Capacity{
								CPU:              "2",
								Memory:           "1970512Ki",
								Pods:             "11",
								EphemeralStorage: "524275692Ki",
							},
							Allocatable: &model.Capacity{
								CPU:              "1930m",
								Memory:           "1483088Ki",
								Pods:             "11",
								EphemeralStorage: "482098735124",
							},
							Labels: map[string]string{
								"beta.kubernetes.io/arch":          "amd64",
								"beta.kubernetes.io/instance-type": "t3.small",
								"beta.kubernetes.io/os":            "linux",
								"topology.kubernetes.io/region":    "us-west-2",
								"topology.kubernetes.io/zone":      "us-west-2a",
							},
							Annotations: map[string]string{
								"node.alpha.kubernetes.io/ttl": "0",
							},
							MachineID:               "00002",
							Architecture:            "amd64",
							ContainerRuntimeVersion: "containerd://1.6.8+bottlerocket",
							BootID:                  "00002",
							KernelVersion:           "5.15.59",
							KubeProxyVersion:        "v1.24.6",
							KubeletVersion:          "v1.24.6",
							OperatingSystem:         "linux",
							OsImage:                 "Bottlerocket OS 1.11.1 (aws-k8s-1.24)",
						},
					}, nil
				},
				allImages: func(context.Context) ([]model.Image, error) {
					return []model.Image{
						{
							Name:     "nginx",
							Version:  "1.17.1",
							FullName: "nginx:1.17.1",
							Digest:   "sha256:0000000000000000000000000000000000000000000000000000000000000001",
						},
						{
							Name:     "redis",
							Version:  "7.0.1",
							FullName: "redis:7.0.1",
							Digest:   "sha256:0000000000000000000000000000000000000000000000000000000000000002",
						},
					}, nil
				},
				allResources: func(context.Context, bool) (map[string]model.ResourceList, error) {
					return map[string]model.ResourceList{
						"/v1, Resource=namespaces": {
							Kind:           "Namespace",
							APIVersion:     "v1",
							Namespaced:     false,
							ResourcesCount: 2,
							Resources: []model.Resource{
								{
									Name:                 "backend",
									AdditionalProperties: map[string]string{"version": "v1.0.0"},
								},
								{
									Name:                 "frontend",
									AdditionalProperties: map[string]string{"version": "v2.0.0"},
								},
							},
						},
					}, nil
				},
			},
			timeMock:    "2023-04-26T10:00:00.000000+00:00",
			idMock:      "00000001",
			expectedErr: nil,
			expectedOut: expectedOutJSON,
		},
		{
			name:        "print KBOM - stdout - yaml",
			clientMock:  &mockedK8sClient{},
			timeMock:    "2023-04-26T10:00:00.000000+00:00",
			idMock:      "00000001",
			format:      YAMLFormat.Name,
			expectedOut: expectedOutYAML,
		},
		{
			name:        "print KBOM - file - yaml",
			clientMock:  &mockedK8sClient{},
			timeMock:    "2023-04-26T10:00:00.000000+00:00",
			idMock:      "00000001",
			format:      YAMLFormat.Name,
			output:      FileOutput,
			expectedOut: expectedOutYAML,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			mock := &stdoutMock{buf: bytes.Buffer{}}
			out = mock
			kbomID = tc.idMock
			if tc.timeMock != "" {
				mockedTime, err := time.Parse(time.RFC3339, tc.timeMock)
				assert.NoError(t, err)
				generatedAt = mockedTime
			}

			if tc.format != "" {
				format = tc.format
			} else {
				format = JSONFormat.Name
			}

			if tc.output != "" {
				output = tc.output
			} else {
				output = StdOutput
			}

			err := generateKBOM(tc.clientMock)
			if tc.expectedErr != nil {
				assert.EqualError(t, err, tc.expectedErr.Error())
			} else {
				assert.NoError(t, err)
			}

			if output == FileOutput {
				filename := fmt.Sprintf("kbom-%s-2023-04-26-10-00-00.%s", mockCACert[:8], format)
				assert.FileExists(t, filename)
				file, err := os.Open(filename)
				assert.NoError(t, err)

				buf := new(bytes.Buffer)
				_, err = buf.ReadFrom(file)
				assert.NoError(t, err)
				file.Close()

				assert.Equal(t, tc.expectedOut, buf.String())
				assert.NoError(t, os.Remove(filename))
			} else {
				assert.Equal(t, tc.expectedOut, mock.buf.String())
			}
		})
	}
}

type mockedK8sClient struct {
	clusterName  func(context.Context) (string, error)
	metadata     func(context.Context) (string, string, error)
	location     func(context.Context) (*model.Location, error)
	allImages    func(context.Context) ([]model.Image, error)
	allNodes     func(context.Context, bool) ([]model.Node, error)
	allResources func(context.Context, bool) (map[string]model.ResourceList, error)
}

func (m *mockedK8sClient) ClusterName(ctx context.Context) (clusterName string, err error) {
	if m.clusterName == nil {
		return "test-cluster", nil
	}
	return m.clusterName(ctx)
}

func (m *mockedK8sClient) Metadata(ctx context.Context) (ver, ca string, err error) {
	if m.metadata == nil {
		return "1.25.1", mockCACert, nil
	}
	return m.metadata(ctx)
}

func (m *mockedK8sClient) Location(ctx context.Context) (*model.Location, error) {
	if m.location == nil {
		return nil, nil
	}
	return m.location(ctx)
}

func (m *mockedK8sClient) AllImages(ctx context.Context) ([]model.Image, error) {
	if m.allImages == nil {
		return nil, nil
	}
	return m.allImages(ctx)
}

func (m *mockedK8sClient) AllNodes(ctx context.Context, full bool) ([]model.Node, error) {
	if m.allNodes == nil {
		return nil, nil
	}
	return m.allNodes(ctx, full)
}

func (m *mockedK8sClient) AllResources(ctx context.Context, full bool) (map[string]model.ResourceList, error) {
	if m.allResources == nil {
		return nil, nil
	}
	return m.allResources(ctx, full)
}

var mockCACert = "1234567890"

var expectedOutJSON = `{
  "id": "00000001",
  "bom_format": "rad",
  "spec_version": "0.3",
  "generated_at": "2023-04-26T10:00:00Z",
  "generated_by": {
    "vendor": "RAD Security",
    "name": "unknown",
    "build_time": "unknown",
    "version": "unknown",
    "commit": "unknown",
    "commit_time": "unknown"
  },
  "cluster": {
    "name": "test-cluster",
    "ca_cert_digest": "1.25.1",
    "k8s_version": "012345678",
    "location": {
      "name": "aws",
      "region": "us-east-1",
      "zone": "us-east-1a"
    },
    "nodes_count": 2,
    "nodes": [
      {
        "name": "ip-10-0-65-00.us-east-1.compute.internal",
        "type": "t3.small",
        "hostname": "ip-10-0-65-00.us-east-1.compute.internal",
        "capacity": {
          "cpu": "2",
          "memory": "1970512Ki",
          "pods": "11",
          "ephemeral_storage": "524275692Ki"
        },
        "allocatable": {
          "cpu": "1930m",
          "memory": "1483088Ki",
          "pods": "11",
          "ephemeral_storage": "482098735124"
        },
        "labels": {
          "beta.kubernetes.io/arch": "amd64",
          "beta.kubernetes.io/instance-type": "t3.small",
          "beta.kubernetes.io/os": "linux",
          "topology.kubernetes.io/region": "us-west-2",
          "topology.kubernetes.io/zone": "us-west-2a"
        },
        "annotations": {
          "node.alpha.kubernetes.io/ttl": "0"
        },
        "machine_id": "00001",
        "architecture": "amd64",
        "container_runtime_version": "containerd://1.6.8+bottlerocket",
        "boot_id": "00001",
        "kernel_version": "5.15.59",
        "kube_proxy_version": "v1.24.6",
        "kubelet_version": "v1.24.6",
        "operating_system": "linux",
        "os_image": "Bottlerocket OS 1.11.1 (aws-k8s-1.24)"
      },
      {
        "name": "ip-10-0-65-01.us-east-1.compute.internal",
        "type": "t3.small",
        "hostname": "ip-10-0-65-01.us-east-1.compute.internal",
        "capacity": {
          "cpu": "2",
          "memory": "1970512Ki",
          "pods": "11",
          "ephemeral_storage": "524275692Ki"
        },
        "allocatable": {
          "cpu": "1930m",
          "memory": "1483088Ki",
          "pods": "11",
          "ephemeral_storage": "482098735124"
        },
        "labels": {
          "beta.kubernetes.io/arch": "amd64",
          "beta.kubernetes.io/instance-type": "t3.small",
          "beta.kubernetes.io/os": "linux",
          "topology.kubernetes.io/region": "us-west-2",
          "topology.kubernetes.io/zone": "us-west-2a"
        },
        "annotations": {
          "node.alpha.kubernetes.io/ttl": "0"
        },
        "machine_id": "00002",
        "architecture": "amd64",
        "container_runtime_version": "containerd://1.6.8+bottlerocket",
        "boot_id": "00002",
        "kernel_version": "5.15.59",
        "kube_proxy_version": "v1.24.6",
        "kubelet_version": "v1.24.6",
        "operating_system": "linux",
        "os_image": "Bottlerocket OS 1.11.1 (aws-k8s-1.24)"
      }
    ],
    "components": {
      "images": [
        {
          "full_name": "nginx:1.17.1",
          "name": "nginx",
          "version": "1.17.1",
          "digest": "sha256:0000000000000000000000000000000000000000000000000000000000000001"
        },
        {
          "full_name": "redis:7.0.1",
          "name": "redis",
          "version": "7.0.1",
          "digest": "sha256:0000000000000000000000000000000000000000000000000000000000000002"
        }
      ],
      "resources": {
        "/v1, Resource=namespaces": {
          "kind": "Namespace",
          "api_version": "v1",
          "namespaced": false,
          "count": 2,
          "resources": [
            {
              "name": "backend",
              "additional_properties": {
                "version": "v1.0.0"
              }
            },
            {
              "name": "frontend",
              "additional_properties": {
                "version": "v2.0.0"
              }
            }
          ]
        }
      }
    }
  }
}
`
var expectedOutYAML = `id: "00000001"
bomformat: rad
specversion: "0.3"
generatedat: 2023-04-26T10:00:00Z
generatedby:
  vendor: RAD Security
  name: unknown
  buildtime: unknown
  version: unknown
  commit: unknown
  committime: unknown
cluster:
  name: test-cluster
  cacertdigest: "1234567890"
  k8sversion: 1.25.1
  cniversion: ""
  location: null
  nodescount: 0
  nodes: []
  components:
    images: []
    resources: {}
`
0707010000001C000081A4000000000000000000000001671FABC100000811000000000000000000000000000000000000001700000000kbom-0.3.1/cmd/root.gopackage cmd

import (
	"fmt"
	"io"
	"os"
	"path"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"golang.org/x/term"

	"github.com/rad-security/kbom/internal/utils"
)

const (
	confDir = ".config/rad"
)

var (
	verbose    bool
	k8sContext string

	out io.WriteCloser = os.Stdout
)

var rootCmd = &cobra.Command{
	Use:   "kbom",
	Short: "KBOM - Kubernetes Bill of Materials",

	PersistentPreRun: setup,
}

func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func init() {
	rootCmd.AddCommand(GenerateCmd)
	rootCmd.AddCommand(versionCmd)
	rootCmd.AddCommand(schemaCmd)

	rootCmd.PersistentFlags().StringVarP(&k8sContext, "context", "c", "", "Kubernetes context to use, defaults to current context")
	rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging (DEBUG and below)")

	rootCmd.SilenceErrors = true
	rootCmd.SilenceUsage = true
}

func setup(cmd *cobra.Command, _ []string) {
	initLogger()
	initConfig()
	utils.BindFlags(cmd)
}

func initConfig() {
	home, err := os.UserHomeDir()
	cobra.CheckErr(err)

	viper.AddConfigPath(home)
	viper.SetConfigType("json")
	viper.SetConfigName(path.Join(confDir, "kbom.json"))

	if err := viper.ReadInConfig(); err != nil {
		// It's okay if there isn't a config file
		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
			fmt.Println("error reading config file")
			os.Exit(1)
		}
	}

	// Environment variables can't have dashes
	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
	viper.AutomaticEnv()
}

func initLogger() {
	defaultLogger := zerolog.New(os.Stderr)

	logLevel := zerolog.InfoLevel
	if verbose {
		logLevel = zerolog.TraceLevel
	}

	zerolog.SetGlobalLevel(logLevel)

	// use color logger when run in terminal
	if isTerminal() {
		defaultLogger = zerolog.New(zerolog.NewConsoleWriter())
	}

	log.Logger = defaultLogger.With().Timestamp().Stack().Logger()
}

func isTerminal() bool {
	return term.IsTerminal(int(os.Stdout.Fd())) //nolint:gosec
}
0707010000001D000081A4000000000000000000000001671FABC100000163000000000000000000000000000000000000001C00000000kbom-0.3.1/cmd/root_test.gopackage cmd

import (
	"bytes"
	"testing"
)

func TestRoot(t *testing.T) {
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)

	Execute()

	// Check if output contains expected string
	expected := "KBOM - Kubernetes Bill of Materials"
	if !bytes.Contains(buf.Bytes(), []byte(expected)) {
		t.Errorf("Execute() output = %q, want %q", buf.String(), expected)
	}
}
0707010000001E000081A4000000000000000000000001671FABC1000001D1000000000000000000000000000000000000001900000000kbom-0.3.1/cmd/schema.gopackage cmd

import (
	"encoding/json"

	"github.com/invopop/jsonschema"
	"github.com/spf13/cobra"

	"github.com/rad-security/kbom/internal/model"
)

var schemaCmd = &cobra.Command{
	Use:   "schema",
	Short: "Print the KBOM json file schema",
	RunE:  runGenerateSchema,
}

func runGenerateSchema(cmd *cobra.Command, _ []string) error {
	schema := jsonschema.Reflect(&model.KBOM{})
	enc := json.NewEncoder(out)
	enc.SetIndent("", "  ")

	return enc.Encode(schema)
}
0707010000001F000081A4000000000000000000000001671FABC100001CEA000000000000000000000000000000000000001E00000000kbom-0.3.1/cmd/schema_test.gopackage cmd

import (
	"bytes"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestRunGenerateSchema(t *testing.T) {
	mock := &stdoutMock{buf: bytes.Buffer{}}
	out = mock

	err := runGenerateSchema(nil, []string{})
	assert.NoError(t, err)

	assert.Equal(t, expectedSchema, mock.buf.String())
}

type stdoutMock struct {
	buf bytes.Buffer
}

func (m *stdoutMock) Write(p []byte) (n int, err error) {
	return m.buf.Write(p)
}

func (m *stdoutMock) Close() error {
	return nil
}

var expectedSchema = `{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/rad-security/kbom/internal/model/kbom",
  "$ref": "#/$defs/KBOM",
  "$defs": {
    "Capacity": {
      "properties": {
        "cpu": {
          "type": "string"
        },
        "memory": {
          "type": "string"
        },
        "pods": {
          "type": "string"
        },
        "ephemeral_storage": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "cpu",
        "memory",
        "pods",
        "ephemeral_storage"
      ]
    },
    "Cluster": {
      "properties": {
        "name": {
          "type": "string"
        },
        "ca_cert_digest": {
          "type": "string"
        },
        "k8s_version": {
          "type": "string"
        },
        "cni_version": {
          "type": "string"
        },
        "location": {
          "$ref": "#/$defs/Location"
        },
        "nodes_count": {
          "type": "integer"
        },
        "nodes": {
          "items": {
            "$ref": "#/$defs/Node"
          },
          "type": "array"
        },
        "components": {
          "$ref": "#/$defs/Components"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "name",
        "ca_cert_digest",
        "k8s_version",
        "location",
        "nodes_count",
        "nodes",
        "components"
      ]
    },
    "Components": {
      "properties": {
        "images": {
          "items": {
            "$ref": "#/$defs/Image"
          },
          "type": "array"
        },
        "resources": {
          "additionalProperties": {
            "$ref": "#/$defs/ResourceList"
          },
          "type": "object"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "resources"
      ]
    },
    "Image": {
      "properties": {
        "full_name": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "version": {
          "type": "string"
        },
        "digest": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "full_name",
        "name",
        "version",
        "digest"
      ]
    },
    "KBOM": {
      "properties": {
        "id": {
          "type": "string"
        },
        "bom_format": {
          "type": "string"
        },
        "spec_version": {
          "type": "string"
        },
        "generated_at": {
          "type": "string",
          "format": "date-time"
        },
        "generated_by": {
          "$ref": "#/$defs/Tool"
        },
        "cluster": {
          "$ref": "#/$defs/Cluster"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "id",
        "bom_format",
        "spec_version",
        "generated_at",
        "generated_by",
        "cluster"
      ]
    },
    "Location": {
      "properties": {
        "name": {
          "type": "string"
        },
        "region": {
          "type": "string"
        },
        "zone": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "name",
        "region",
        "zone"
      ]
    },
    "Node": {
      "properties": {
        "name": {
          "type": "string"
        },
        "type": {
          "type": "string"
        },
        "hostname": {
          "type": "string"
        },
        "capacity": {
          "$ref": "#/$defs/Capacity"
        },
        "allocatable": {
          "$ref": "#/$defs/Capacity"
        },
        "labels": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object"
        },
        "annotations": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object"
        },
        "machine_id": {
          "type": "string"
        },
        "architecture": {
          "type": "string"
        },
        "container_runtime_version": {
          "type": "string"
        },
        "boot_id": {
          "type": "string"
        },
        "kernel_version": {
          "type": "string"
        },
        "kube_proxy_version": {
          "type": "string"
        },
        "kubelet_version": {
          "type": "string"
        },
        "operating_system": {
          "type": "string"
        },
        "os_image": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "name",
        "type",
        "hostname",
        "capacity",
        "allocatable",
        "labels",
        "annotations",
        "machine_id",
        "architecture",
        "container_runtime_version",
        "boot_id",
        "kernel_version",
        "kube_proxy_version",
        "kubelet_version",
        "operating_system",
        "os_image"
      ]
    },
    "Resource": {
      "properties": {
        "kind": {
          "type": "string"
        },
        "api_version": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "namespace": {
          "type": "string"
        },
        "additional_properties": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "name"
      ]
    },
    "ResourceList": {
      "properties": {
        "kind": {
          "type": "string"
        },
        "api_version": {
          "type": "string"
        },
        "namespaced": {
          "type": "boolean"
        },
        "count": {
          "type": "integer"
        },
        "resources": {
          "items": {
            "$ref": "#/$defs/Resource"
          },
          "type": "array"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "kind",
        "api_version",
        "namespaced",
        "count"
      ]
    },
    "Tool": {
      "properties": {
        "vendor": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "build_time": {
          "type": "string"
        },
        "version": {
          "type": "string"
        },
        "commit": {
          "type": "string"
        },
        "commit_time": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "vendor",
        "name",
        "build_time",
        "version",
        "commit",
        "commit_time"
      ]
    }
  }
}
`
07070100000020000081A4000000000000000000000001671FABC10000025F000000000000000000000000000000000000001A00000000kbom-0.3.1/cmd/version.gopackage cmd

import (
	"fmt"

	"github.com/spf13/cobra"

	"github.com/rad-security/kbom/internal/config"
)

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print the KBOM generator version",
	Long:  `All software has versions. This is KBOM's`,
	RunE:  runPrintVersion,
}

func runPrintVersion(cmd *cobra.Command, _ []string) error {
	fmt.Fprintf(out, "%s version %s\n", config.AppName, config.AppVersion)
	fmt.Fprintf(out, "build date: %s\n", config.BuildTime)
	fmt.Fprintf(out, "commit: %s\n\n", config.LastCommitHash)
	fmt.Fprintln(out, "https://github.com/rad-security/kbom")

	return nil
}
07070100000021000081A4000000000000000000000001671FABC100000265000000000000000000000000000000000000001F00000000kbom-0.3.1/cmd/version_test.gopackage cmd

import (
	"bytes"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/rad-security/kbom/internal/config"
)

func TestVersion(t *testing.T) {
	mock := &stdoutMock{buf: bytes.Buffer{}}
	out = mock

	config.AppName = "kbom"
	config.AppVersion = "1.0.0"
	config.BuildTime = "2021-01-01T00:00:00Z"
	config.LastCommitHash = "1234567890"

	err := runPrintVersion(nil, []string{})
	assert.NoError(t, err)

	assert.Equal(t, expectedVersion, mock.buf.String())
}

var expectedVersion = `kbom version 1.0.0
build date: 2021-01-01T00:00:00Z
commit: 1234567890

https://github.com/rad-security/kbom
`
07070100000022000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001000000000kbom-0.3.1/docs07070100000023000081A4000000000000000000000001671FABC100000244000000000000000000000000000000000000001A00000000kbom-0.3.1/docs/schema.md# KBOM Schema

The section below describes the high level object model for KBOM.

## Cluster Details

Instances:

- Name
- Hostname
- CloudType
- Creation Timestamp
- Capacity
- Allocatable resources
- OS Version
- Kernel Version
- Architecture
- CRI Version
- Kubelet Version
- Kube Proxy Version

Images:

- Name
- FullName
- Version
- Digest

KubeObjects:

- Kind
- Api Version
- Count
- Details

This overall structure provides a base spec to be expanded upon by the community.

The intent of the standard is to be extensible to support various use cases across the industry.
07070100000024000081A4000000000000000000000001671FABC100000EFA000000000000000000000000000000000000001C00000000kbom-0.3.1/docs/taxonomy.md# Custom RAD KBOM Taxonomy

This is the RAD KBOM CycloneDX property namespace and name taxonomy. All of the namespaces are prefixed with `rad:kbom:`.

Following Taxonomy is used by the `KBOM` tool as extension to: [https://github.com/CycloneDX/cyclonedx-property-taxonomy](https://github.com/CycloneDX/cyclonedx-property-taxonomy).

## `rad:kbom:k8s:component` Namespace Taxonomy

| Namespace                            | Description                                                       |
| ------------------------------------ | ----------------------------------------------------------------- |
| `rad:kbom:k8s:component:apiVersion` | API Version of the Kubernetes component.                          |
| `rad:kbom:k8s:component:namespace`  | Namespace of the  Kubernetes component.                           |

## `rad:kbom:k8s:cluster` Namespace Taxonomy

| Property                                  | Description                    |
| ----------------------------------------- | ------------------------------ |
| `rad:kbom:k8s:cluster:location:name`     | Name of the location.          |
| `rad:kbom:k8s:cluster:location:region`   | Region of the cluster.         |
| `rad:kbom:k8s:cluster:location:zone`     | Zone where cluster is located. |

## `rad:kbom:k8s:node` Namespace Taxonomy

| Property                                           | Description                          |
| -------------------------------------------------- | ------------------------------------ |
| `rad:kbom:k8s:node:osImage`                       | Node's operating system image        |
| `rad:kbom:k8s:node:arch`                          | Node's architecture                  |
| `rad:kbom:k8s:node:kernel`                        | Node's kernel version                |
| `rad:kbom:k8s:node:bootId`                        | Node's Boot identifier               |
| `rad:kbom:k8s:node:type`                          | Node's type                          |
| `rad:kbom:k8s:node:operatingSystem`               | Node's operating system              |
| `rad:kbom:k8s:node:machineId`                     | Node's machine identifier            |
| `rad:kbom:k8s:node:hostname`                      | Node's hostname                      |
| `rad:kbom:k8s:node:containerRuntimeVersion`       | Node's container runtime version     |
| `rad:kbom:k8s:node:kubeletVersion`                | Node's kubelet version               |
| `rad:kbom:k8s:node:kubeProxyVersion`              | Node's kube proxy version            |
| `rad:kbom:k8s:node:capacity:cpu`                  | Node's CPU capacity                  |
| `rad:kbom:k8s:node:capacity:memory`               | Node's Memory capacity               |
| `rad:kbom:k8s:node:capacity:pods`                 | Node's Pods capacity                 |
| `rad:kbom:k8s:node:capacity:ephemeralStorage`     | Node's ephemeral storage capacity    |
| `rad:kbom:k8s:node:allocatable:cpu`               | Node's allocatable CPU               |
| `rad:kbom:k8s:node:allocatable:memory`            | Node's allocatable Memory            |
| `rad:kbom:k8s:node:allocatable:pods`              | Node's allocatable Pods              |
| `rad:kbom:k8s:node:allocatable:ephemeralStorage`  | Node's allocatable ephemeral storage |

## `rad:kbom:pkg` Namespace Taxonomy

| Property                          | Description                                        |
| --------------------------------- | -------------------------------------------------- |
| `rad:kbom:pkg:type`              | Type of the package.                               |
| `rad:kbom:pkg:name`              | Name of the package.                               |
| `rad:kbom:pkg:version`           | Version of the package.                            |
| `rad:kbom:pkg:digest`            | Digest of the package.                             |
07070100000025000081A4000000000000000000000001671FABC100000D32000000000000000000000000000000000000001200000000kbom-0.3.1/go.modmodule github.com/rad-security/kbom

go 1.23

require (
	github.com/CycloneDX/cyclonedx-go v0.7.2
	github.com/Masterminds/semver v1.5.0
	github.com/distribution/reference v0.6.0
	github.com/google/uuid v1.6.0
	github.com/invopop/jsonschema v0.12.0
	github.com/mitchellh/hashstructure/v2 v2.0.2
	github.com/rs/zerolog v1.33.0
	github.com/spf13/cobra v1.8.0
	github.com/spf13/pflag v1.0.5
	github.com/spf13/viper v1.18.2
	github.com/stretchr/testify v1.9.0
	golang.org/x/term v0.20.0
	gopkg.in/yaml.v3 v3.0.1
	k8s.io/api v0.29.0
	k8s.io/apimachinery v0.30.1
	k8s.io/client-go v0.29.0
)

require (
	github.com/bahlo/generic-list-go v0.2.0 // indirect
	github.com/buger/jsonparser v1.1.1 // indirect
	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
	github.com/fsnotify/fsnotify v1.7.0 // indirect
	github.com/go-logr/logr v1.4.1 // indirect
	github.com/go-openapi/jsonpointer v0.19.6 // indirect
	github.com/go-openapi/jsonreference v0.20.2 // indirect
	github.com/go-openapi/swag v0.22.3 // indirect
	github.com/gogo/protobuf v1.3.2 // indirect
	github.com/golang/protobuf v1.5.4 // indirect
	github.com/google/gnostic-models v0.6.8 // indirect
	github.com/google/gofuzz v1.2.0 // indirect
	github.com/hashicorp/hcl v1.0.0 // indirect
	github.com/imdario/mergo v0.3.6 // indirect
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
	github.com/josharian/intern v1.0.0 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/magiconair/properties v1.8.7 // indirect
	github.com/mailru/easyjson v0.7.7 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.19 // indirect
	github.com/mitchellh/mapstructure v1.5.0 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/opencontainers/go-digest v1.0.0 // indirect
	github.com/pelletier/go-toml/v2 v2.1.0 // indirect
	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
	github.com/sagikazarmark/locafero v0.4.0 // indirect
	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
	github.com/sourcegraph/conc v0.3.0 // indirect
	github.com/spf13/afero v1.11.0 // indirect
	github.com/spf13/cast v1.6.0 // indirect
	github.com/subosito/gotenv v1.6.0 // indirect
	github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
	go.uber.org/atomic v1.9.0 // indirect
	go.uber.org/multierr v1.9.0 // indirect
	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
	golang.org/x/net v0.23.0 // indirect
	golang.org/x/oauth2 v0.15.0 // indirect
	golang.org/x/sys v0.20.0 // indirect
	golang.org/x/text v0.14.0 // indirect
	golang.org/x/time v0.5.0 // indirect
	google.golang.org/appengine v1.6.7 // indirect
	google.golang.org/protobuf v1.33.0 // indirect
	gopkg.in/inf.v0 v0.9.1 // indirect
	gopkg.in/ini.v1 v1.67.0 // indirect
	gopkg.in/yaml.v2 v2.4.0 // indirect
	k8s.io/klog/v2 v2.120.1 // indirect
	k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
	k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
	sigs.k8s.io/yaml v1.3.0 // indirect
)
07070100000026000081A4000000000000000000000001671FABC100005424000000000000000000000000000000000000001200000000kbom-0.3.1/go.sumgithub.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ=
github.com/CycloneDX/cyclonedx-go v0.7.2/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A=
k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA=
k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U=
k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8=
k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
07070100000027000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001400000000kbom-0.3.1/internal07070100000028000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001B00000000kbom-0.3.1/internal/config07070100000029000081A4000000000000000000000001671FABC10000021F000000000000000000000000000000000000002500000000kbom-0.3.1/internal/config/config.gopackage config

var (
	// AppName - variable injected by -X flag during build
	AppName = "unknown"

	// AppVersion - variable injected by -X flag during build
	AppVersion = "unknown"

	// LastCommitTime - variable injected by -X flag during build
	LastCommitTime = "unknown"

	// LastCommitUser - variable injected by -X flag during build
	LastCommitUser = "unknown"

	// LastCommitHash - variable injected by -X flag during build
	LastCommitHash = "unknown"

	// BuildTime - variable injected by -X flag during build
	BuildTime = "unknown"
)
0707010000002A000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001900000000kbom-0.3.1/internal/kube0707010000002B000081A4000000000000000000000001671FABC100002EC8000000000000000000000000000000000000002100000000kbom-0.3.1/internal/kube/kube.gopackage kube

import (
	"context"
	"crypto/sha256"
	"fmt"
	"os"
	"strings"

	"github.com/Masterminds/semver"
	"github.com/distribution/reference"
	"github.com/rs/zerolog/log"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/rad-security/kbom/internal/model"
)

type K8sClient interface {
	ClusterName(ctx context.Context) (string, error)
	Metadata(ctx context.Context) (string, string, error)
	Location(ctx context.Context) (*model.Location, error)
	AllImages(ctx context.Context) ([]model.Image, error)
	AllNodes(ctx context.Context, full bool) ([]model.Node, error)
	AllResources(ctx context.Context, full bool) (map[string]model.ResourceList, error)
}

func NewClient(k8sContext string) (K8sClient, error) {
	currentK8sContext := k8sContext

	cfg, err := rest.InClusterConfig()
	if err != nil {
		kubeConfigPath := os.Getenv("KUBECONFIG")
		if kubeConfigPath == "" {
			kubeConfigPath = os.Getenv("HOME") + "/.kube/config"
		}

		clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
			&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfigPath},
			&clientcmd.ConfigOverrides{
				CurrentContext: k8sContext,
			})

		rawConfig, err := clientConfig.RawConfig()
		if err != nil {
			return nil, fmt.Errorf("failed to get kubernetes out-cluster client: %w", err)
		}

		if k8sContext == "" {
			currentK8sContext = rawConfig.CurrentContext
		}

		cfg, err = clientConfig.ClientConfig()
		if err != nil {
			return nil, fmt.Errorf("failed to get kubernetes out-cluster client: %w", err)
		}
	}

	clientset, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, fmt.Errorf("can not create kubernetes client: %w", err)
	}

	dynamicClient, err := dynamic.NewForConfig(cfg)
	if err != nil {
		return nil, fmt.Errorf("can not create kubernetes dynamic client: %w", err)
	}

	rest.SetDefaultWarningHandler(rest.NoWarnings{})

	return &k8sDB{
		k8sContext:    currentK8sContext,
		cfg:           cfg,
		client:        clientset,
		dynamicClient: dynamicClient,
	}, nil
}

type k8sDB struct {
	k8sContext    string
	cfg           *rest.Config
	client        kubernetes.Interface
	dynamicClient dynamic.Interface
}

func (k *k8sDB) ClusterName(ctx context.Context) (string, error) {
	// TODO: find a better way to get cluster name, but for now use cluster context name
	return k.k8sContext, nil
}

func (k *k8sDB) Location(ctx context.Context) (*model.Location, error) {
	// fetch first node
	node, err := k.client.CoreV1().Nodes().List(ctx, metav1.ListOptions{Limit: 1})
	if err != nil {
		return nil, fmt.Errorf("failed to list nodes: %v", err)
	}

	if len(node.Items) == 0 {
		return nil, fmt.Errorf("no node found")
	}

	// get location from node labels
	return &model.Location{
		Name:   getCloudName(node.Items[0].Labels),
		Region: getLabelValue(node.Items[0].Labels, "topology.kubernetes.io/region"),
		Zone:   getLabelValue(node.Items[0].Labels, "topology.kubernetes.io/zone"),
	}, nil
}

// AllNodes returns all nodes in the cluster
func (k *k8sDB) AllNodes(ctx context.Context, full bool) ([]model.Node, error) {
	nodes, err := k.client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to list nodes: %v", err)
	}

	modelNodes := make([]model.Node, 0)
	for i := range nodes.Items {
		var labels, annotations map[string]string
		if full {
			labels = nodes.Items[i].Labels
			annotations = nodes.Items[i].Annotations
		}

		modelNodes = append(modelNodes, model.Node{
			Name:     nodes.Items[i].Name,
			OsImage:  nodes.Items[i].Status.NodeInfo.OSImage,
			Hostname: getLabelValue(nodes.Items[i].Labels, "kubernetes.io/hostname"),
			Type:     getLabelValue(nodes.Items[i].Labels, "node.kubernetes.io/instance-type"),
			Capacity: &model.Capacity{
				CPU:              nodes.Items[i].Status.Capacity.Cpu().String(),
				Memory:           nodes.Items[i].Status.Capacity.Memory().String(),
				EphemeralStorage: nodes.Items[i].Status.Capacity.StorageEphemeral().String(),
				Pods:             nodes.Items[i].Status.Capacity.Pods().String(),
			},
			Allocatable: &model.Capacity{
				CPU:              nodes.Items[i].Status.Allocatable.Cpu().String(),
				Memory:           nodes.Items[i].Status.Allocatable.Memory().String(),
				EphemeralStorage: nodes.Items[i].Status.Allocatable.StorageEphemeral().String(),
				Pods:             nodes.Items[i].Status.Allocatable.Pods().String(),
			},
			Labels:                  labels,
			Annotations:             annotations,
			MachineID:               nodes.Items[i].Status.NodeInfo.MachineID,
			Architecture:            nodes.Items[i].Status.NodeInfo.Architecture,
			KernelVersion:           nodes.Items[i].Status.NodeInfo.KernelVersion,
			ContainerRuntimeVersion: nodes.Items[i].Status.NodeInfo.ContainerRuntimeVersion,
			BootID:                  nodes.Items[i].Status.NodeInfo.BootID,
			KubeProxyVersion:        nodes.Items[i].Status.NodeInfo.KubeProxyVersion,
			KubeletVersion:          nodes.Items[i].Status.NodeInfo.KubeletVersion,
			OperatingSystem:         nodes.Items[i].Status.NodeInfo.OperatingSystem,
		})
	}

	return modelNodes, nil
}

func (k *k8sDB) AllImages(ctx context.Context) ([]model.Image, error) {
	namespaces, err := k.client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to list namespaces: %v", err)
	}

	images := make(map[string]model.Image)
	for i := range namespaces.Items {
		pods, err := k.client.CoreV1().Pods(namespaces.Items[i].Name).List(ctx, metav1.ListOptions{})
		if err != nil {
			return nil, fmt.Errorf("failed to list pods: %w", err)
		}
		namespace := namespaces.Items[i].Name

		log.Debug().Str("namespace", namespace).Int("count", len(pods.Items)).Msg("Found pods in namespace")

		for j := range pods.Items {
			pod := pods.Items[j]

			for k := range pod.Spec.InitContainers {
				img, err := containerToImage(pod.Spec.InitContainers[k].Image,
					pod.Spec.InitContainers[k].Name, pod.Status.InitContainerStatuses, namespace)
				if err != nil {
					return nil, err
				}

				images[img.FullName] = *img
			}

			for k := range pod.Spec.Containers {
				img, err := containerToImage(pod.Spec.Containers[k].Image, pod.Spec.Containers[k].Name, pod.Status.ContainerStatuses, namespace)
				if err != nil {
					return nil, err
				}

				images[img.FullName] = *img
			}

			for k := range pod.Spec.EphemeralContainers {
				img, err := containerToImage(pod.Spec.EphemeralContainers[k].Image,
					pod.Spec.EphemeralContainers[k].Name, pod.Status.EphemeralContainerStatuses, namespace)
				if err != nil {
					return nil, err
				}

				images[img.FullName] = *img
			}
		}
	}

	toReturn := make([]model.Image, 0)
	for _, v := range images {
		toReturn = append(toReturn, v)
	}

	return toReturn, nil
}

func containerToImage(img, imgName string, statuses []v1.ContainerStatus, namespace string) (*model.Image, error) {
	if img == "" {
		return nil, fmt.Errorf("container %s has no image", img)
	}

	named, err := reference.ParseNormalizedNamed(img)
	if err != nil {
		return nil, err
	}

	controlPlane := false
	if namespace == "kube-system" {
		controlPlane = true
	}

	res := &model.Image{
		FullName:     img,
		ControlPlane: controlPlane,
	}

	res.Name = named.Name()
	tagged, ok := named.(reference.Tagged)
	if ok {
		res.Version = tagged.Tag()
	}

	digested, ok := named.(reference.Digested)
	if ok {
		res.Digest = digested.Digest().String()
	}

	// search in statuses for ImageID to get digest
	for i := range statuses {
		if imgName == statuses[i].Name {
			if statuses[i].State.Running == nil && statuses[i].State.Terminated == nil {
				break // We can get valid digest only from running or terminated containers
			}
			if strings.Contains(statuses[i].ImageID, "@") {
				res.Digest = strings.Split(statuses[i].ImageID, "@")[1]
			} else if strings.HasPrefix(statuses[i].ImageID, "sha256:") {
				res.Digest = statuses[i].ImageID
			}
			break
		}
	}

	return res, nil
}

// Metadata returns the kubernetes version
func (k *k8sDB) Metadata(ctx context.Context) (k8sVersion, caDigest string, err error) {
	if _, err := rest.InClusterConfig(); err != nil {
		hash := sha256.Sum256(k.cfg.CAData)
		caDigest = fmt.Sprintf("%x", hash[:])
	} else {
		caConfigMap, err := k.client.CoreV1().ConfigMaps("kube-system").Get(ctx, "kube-root-ca.crt", metav1.GetOptions{})
		if err != nil {
			log.Debug().Err(err).Msg("failed to get kube-root-ca.crt")
		} else {
			caCert, ok := caConfigMap.Data["ca.crt"]
			if !ok {
				return "", "", fmt.Errorf("can't find 'ca.crt' in configMap 'kube-root-ca.crt'")
			}

			caDigest = fmt.Sprintf("%x", sha256.Sum256([]byte(caCert)))
		}
	}

	version, err := k.client.Discovery().ServerVersion()
	if err != nil {
		return caDigest, "", fmt.Errorf("error getting k8s version: %w", err)
	}

	ver := strings.Trim(version.GitVersion, "v")

	sVer, err := semver.NewVersion(ver)
	if err != nil {
		return caDigest, "", fmt.Errorf("error parsing k8s version: %w", err)
	}

	ver = fmt.Sprintf("%d.%d.%d", sVer.Major(), sVer.Minor(), sVer.Patch())

	return ver, caDigest, nil
}

func (k *k8sDB) AllResources(ctx context.Context, full bool) (map[string]model.ResourceList, error) {
	apiResourceList, err := k.client.Discovery().ServerPreferredResources()
	if err != nil {
		return nil, fmt.Errorf("failed to get api groups: %w", err)
	}

	resourceMap := make(map[string]model.ResourceList)
	for _, apiResource := range apiResourceList {
		gv, err := schema.ParseGroupVersion(apiResource.GroupVersion)
		if err != nil {
			return nil, fmt.Errorf("failed to parse group version: %w", err)
		}

		for i := range apiResource.APIResources {
			res := apiResource.APIResources[i]
			gvr := schema.GroupVersionResource{
				Group:    gv.Group,
				Version:  gv.Version,
				Resource: res.Name,
			}

			resourceList, err := k.dynamicClient.Resource(gvr).List(ctx, metav1.ListOptions{})
			if err != nil {
				log.Debug().Err(err).Interface("gvr", gvr).Msg("Failed to list resources")
				continue
			}

			log.Debug().Interface("gvr", gvr).Int("count", len(resourceList.Items)).Msg("Found resources")

			if len(resourceList.Items) > 0 {
				resourceMap[gvr.String()] = model.ResourceList{
					Kind:           resourceList.Items[0].GetKind(),
					APIVersion:     gvr.GroupVersion().String(),
					Namespaced:     res.Namespaced,
					ResourcesCount: len(resourceList.Items),
					Resources:      make([]model.Resource, 0),
				}
			}

			if full {
				for _, item := range resourceList.Items {
					res := model.Resource{
						Name:                 item.GetName(),
						Namespace:            item.GetNamespace(),
						AdditionalProperties: map[string]string{},
					}
					if version, ok := getVersion(item); ok {
						res.AdditionalProperties["version"] = version
					}
					val := resourceMap[gvr.String()]
					val.Resources = append(val.Resources, res)
					resourceMap[gvr.String()] = val
				}
			}
		}
	}

	return resourceMap, nil
}

func getVersion(item unstructured.Unstructured) (version string, ok bool) {
	obj := item.Object
	if obj == nil {
		return "", false
	}

	spec, ok := obj["spec"].(map[string]interface{})
	if !ok {
		return "", false
	}

	version, ok = spec["version"].(string)
	return
}

func getLabelValue(labels map[string]string, key string) string {
	for k, v := range labels {
		if k == key {
			return v
		}
	}

	return ""
}

func getCloudName(labels map[string]string) string {
	if labels == nil {
		return "unknown"
	}

	if _, ok := labels["k8s.io/cloud-provider-aws"]; ok {
		return "aws"
	}

	if _, ok := labels["topology.gke.io/zone"]; ok {
		return "gcloud"
	}

	if _, ok := labels["kubernetes.azure.com/cluster"]; ok {
		return "azure"
	}

	return "unknown"
}
0707010000002C000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001A00000000kbom-0.3.1/internal/model0707010000002D000081A4000000000000000000000001671FABC100000FEF000000000000000000000000000000000000002200000000kbom-0.3.1/internal/model/kbom.gopackage model

import (
	"fmt"
	"net/url"
	"strings"
	"time"
)

const (
	ociPrefix         = "oci"
	k8sPrefix         = "k8s"
	pkgPrefix         = "pkg"
	kubernetesPkgName = "k8s.io/kubernetes"
)

type KBOM struct {
	ID          string    `json:"id"`
	BOMFormat   string    `json:"bom_format"`
	SpecVersion string    `json:"spec_version"`
	GeneratedAt time.Time `json:"generated_at"`
	GeneratedBy Tool      `json:"generated_by"`

	Cluster Cluster `json:"cluster"`
}

type Tool struct {
	Vendor     string `json:"vendor"`
	Name       string `json:"name"`
	BuildTime  string `json:"build_time"`
	Version    string `json:"version"`
	Commit     string `json:"commit"`
	CommitTime string `json:"commit_time"`
}

type Cluster struct {
	Name         string     `json:"name"`
	CACertDigest string     `json:"ca_cert_digest"`
	K8sVersion   string     `json:"k8s_version"`
	CNIVersion   string     `json:"cni_version,omitempty"`
	Location     *Location  `json:"location"`
	NodesCount   int        `json:"nodes_count"`
	Nodes        []Node     `json:"nodes"`
	Components   Components `json:"components"`
}

func (c *Cluster) BOMRef() string {
	return fmt.Sprintf("%s:%s/%s@%s", pkgPrefix, k8sPrefix, url.QueryEscape(kubernetesPkgName), c.K8sVersion)
}

func (c *Cluster) BOMName() string {
	return kubernetesPkgName
}

type Components struct {
	Images    []Image                 `json:"images,omitempty"`
	Resources map[string]ResourceList `json:"resources"`
}

type Resource struct {
	Kind                 string            `json:"kind,omitempty"`
	APIVersion           string            `json:"api_version,omitempty"`
	Name                 string            `json:"name"`
	Namespace            string            `json:"namespace,omitempty"`
	AdditionalProperties map[string]string `json:"additional_properties,omitempty"`
}

type ResourceList struct {
	Kind           string     `json:"kind"`
	APIVersion     string     `json:"api_version"`
	Namespaced     bool       `json:"namespaced"`
	ResourcesCount int        `json:"count"`
	Resources      []Resource `json:"resources,omitempty"`
}

type Location struct {
	Name   string `json:"name"`
	Region string `json:"region"`
	Zone   string `json:"zone"`
}

type Node struct {
	Name                    string            `json:"name"`
	Type                    string            `json:"type"`
	Hostname                string            `json:"hostname"`
	Capacity                *Capacity         `json:"capacity"`
	Allocatable             *Capacity         `json:"allocatable"`
	Labels                  map[string]string `json:"labels"`
	Annotations             map[string]string `json:"annotations"`
	MachineID               string            `json:"machine_id"`
	Architecture            string            `json:"architecture"`
	ContainerRuntimeVersion string            `json:"container_runtime_version"`
	BootID                  string            `json:"boot_id"`
	KernelVersion           string            `json:"kernel_version"`
	KubeProxyVersion        string            `json:"kube_proxy_version"`
	KubeletVersion          string            `json:"kubelet_version"`
	OperatingSystem         string            `json:"operating_system"`
	OsImage                 string            `json:"os_image"`
}

type Image struct {
	FullName     string `json:"full_name"`
	Name         string `json:"name"`
	Version      string `json:"version"`
	Digest       string `json:"digest"`
	ControlPlane bool   `json:"-"`
}

func (i *Image) PkgID() string {
	parts := strings.Split(i.Name, "/")
	baseName := fmt.Sprintf("%s:%s/%s", pkgPrefix, ociPrefix, parts[len(parts)-1])

	urlValues := url.Values{
		"repository_url": []string{i.Name},
	}

	if i.Version != "" {
		urlValues.Add("tag", i.Version)
	}

	if i.Digest != "" {
		baseName = fmt.Sprintf("%s@%s", baseName, url.QueryEscape(i.Digest))
	}

	return fmt.Sprintf("%s?%s", baseName, urlValues.Encode())
}

type Capacity struct {
	CPU              string `json:"cpu"`
	Memory           string `json:"memory"`
	Pods             string `json:"pods"`
	EphemeralStorage string `json:"ephemeral_storage"`
}
0707010000002E000081A4000000000000000000000001671FABC1000004F3000000000000000000000000000000000000002700000000kbom-0.3.1/internal/model/kbom_test.gopackage model

import (
	"testing"
)

func TestImagePkgID(t *testing.T) {
	testCases := []struct {
		name        string
		image       Image
		expectedID  string
		expectedErr error
	}{
		{
			name: "VersionAndDigest",
			image: Image{
				FullName: "full_name",
				Name:     "repo/name",
				Version:  "version",
				Digest:   "sha256:digest",
			},
			expectedID: "pkg:oci/name@sha256%3Adigest?repository_url=repo%2Fname&tag=version",
		},
		{
			name: "VersionOnly",
			image: Image{
				FullName: "full_name",
				Name:     "repo/name",
				Version:  "version",
			},
			expectedID: "pkg:oci/name?repository_url=repo%2Fname&tag=version",
		},
		{
			name: "DigestOnly",
			image: Image{
				FullName: "full_name",
				Name:     "repo/subrepo/name",
				Digest:   "sha256:digest",
			},
			expectedID: "pkg:oci/name@sha256%3Adigest?repository_url=repo%2Fsubrepo%2Fname",
		},
		{
			name: "NoVersionOrDigest",
			image: Image{
				FullName: "full_name",
				Name:     "repo/name",
			},
			expectedID: "pkg:oci/name?repository_url=repo%2Fname",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := tc.image.PkgID()
			if result != tc.expectedID {
				t.Errorf("Expected %s, but got %s", tc.expectedID, result)
			}
		})
	}
}
0707010000002F000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001A00000000kbom-0.3.1/internal/utils07070100000030000081A4000000000000000000000001671FABC1000001E5000000000000000000000000000000000000002300000000kbom-0.3.1/internal/utils/utils.gopackage utils

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

// BindFlags binds the viper config values to the flags
func BindFlags(cmd *cobra.Command) {
	cmd.Flags().VisitAll(func(f *pflag.Flag) {
		configName := f.Name

		if !f.Changed && viper.IsSet(configName) {
			val := viper.Get(configName)
			err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
		}
	})
}
07070100000031000081A4000000000000000000000001671FABC1000003A3000000000000000000000000000000000000002800000000kbom-0.3.1/internal/utils/utils_test.gopackage utils

import (
	"testing"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

func TestBindFlags(t *testing.T) {
	// Set up a new cobra command
	cmd := &cobra.Command{
		Use: "test",
		Run: func(cmd *cobra.Command, args []string) {},
	}

	// Add some flags to the command
	cmd.Flags().String("foo", "", "foo flag")
	cmd.Flags().Int("bar", 0, "bar flag")

	// Initialize viper with some values
	viper.Set("foo", "foo-value")
	viper.Set("bar", 123)

	// Bind the viper config values to the command flags
	BindFlags(cmd)

	// Check that the flag values were set correctly
	fooFlag := cmd.Flags().Lookup("foo")
	if fooFlag.Value.String() != "foo-value" {
		t.Errorf("expected foo flag value to be 'foo-value', but got '%s'", fooFlag.Value.String())
	}

	barFlag := cmd.Flags().Lookup("bar")
	if barFlag.Value.String() != "123" {
		t.Errorf("expected bar flag value to be '123', but got '%s'", barFlag.Value.String())
	}
}
07070100000032000081A4000000000000000000000001671FABC100000058000000000000000000000000000000000000001300000000kbom-0.3.1/main.gopackage main

import "github.com/rad-security/kbom/cmd"

func main() {
	cmd.Execute()
}
07070100000033000081A4000000000000000000000001671FABC10000006B000000000000000000000000000000000000001900000000kbom-0.3.1/renovate.json{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:base"
  ]
}
07070100000034000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001300000000kbom-0.3.1/scripts07070100000035000081ED000000000000000000000001671FABC10000032D000000000000000000000000000000000000001B00000000kbom-0.3.1/scripts/version#!/bin/bash

set -euo pipefail

SEP=${SEP:-"-"}

exact_tag=$(git describe --exact-match 2>/dev/null | sed -e 's/^v//g' || echo '')
last_tag=$(git describe --tags --abbrev=0 2>/dev/null)
commits=$(git log --oneline "${last_tag}"..HEAD | wc -l | tr -d ' ')
revision=$(git rev-parse --short HEAD || echo unknown)

if echo "${exact_tag}" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+$"; then
    echo "$exact_tag"
    exit 0
fi

if echo "${exact_tag}" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$"; then
    echo "$exact_tag"
    exit 0
fi

if echo "${exact_tag}" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+-rc[0-9]+$"; then
    echo "$exact_tag"
    exit 0
fi

if [ "$commits" -eq 0 ]; then
    if [ -n "$last_tag" ]; then
        echo "${last_tag//v/}"
        exit 0
    fi
fi

echo "${last_tag//v/}${SEP}${commits}-${revision}"
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!248 blocks
openSUSE Build Service is sponsored by