Skip to content

Feat/support SBOM generation#291

Open
viveksahu26 wants to merge 71 commits intointerlynk-io:mainfrom
viveksahu26:feat/support_sbom_generation
Open

Feat/support SBOM generation#291
viveksahu26 wants to merge 71 commits intointerlynk-io:mainfrom
viveksahu26:feat/support_sbom_generation

Conversation

@viveksahu26
Copy link
Copy Markdown
Contributor

@viveksahu26 viveksahu26 commented Apr 9, 2026

closes #289
This PR adds the following changes:

  • added sbom sub-command of generate
    • it add new command sbomasm generate sbom
    • parse and extract up CLI flags
    • implement aritifact/component model
    • implement recursive file discovery with Interlynk schema validation
    • implement collecting component list, merging it and removing duplicates component
    • implement component fieltering using tags
    • implement dependency-of resolution and dependency graph construction
    • implement CycloneDX SBOM builder
    • Implement SPDX SBOM builder
  • Implemented hash caculation during sbom generation for supported ways
  • Implemented pedigree support for vendor patches
  • Replaced dependency-of by depends-on and acoordingly changed it's functionality
  • Implemented sbomasm generate config command
  • Updated and added documentation
  • Added sbomasm generate components command
  • Added support of strict validatior on strict mode
  • Added support for version pinning
  • Performed major features testings
  • Performed all 24 test suits testing
  • [Tests]: Added Artifact Parsing Tests
  • [Tests]: Added Components Parsing Tests

TODO:

  • write e2e tests

Demo as per current implmentation:

 $ ./sbomasm generate sbom \ 
 -i .components.json \
 -i libs/libmqtt/.components.json \
 -i src/cjson/.components.json \
 -i src/miniz/.components.json \
 -o device-firmware-2.1.0.spdx.json --format spdx
 

o/p of device-firmware-2.1.0.spdx.json:

{
 "spdxVersion": "SPDX-2.3",
 "dataLicense": "CC0-1.0",
 "SPDXID": "SPDXRef-DOCUMENT",
 "name": "device-firmware",
 "documentNamespace": "https://spdx.org/spdxdocs/device-firmware-b7da2ce2-3af5-4a24-a06c-dd0a6033a129",
 "creationInfo": {
   "creators": [
     "Tool: sbomasm-v2.0.0-20260413160043-47ab5f6b3c99+dirty"
   ],
   "created": "2026-04-13T16:13:23Z"
 },
 "packages": [
   {
     "name": "device-firmware",
     "SPDXID": "SPDXRef-device-firmware-2.1.0",
     "versionInfo": "2.1.0",
     "downloadLocation": "NOASSERTION",
     "filesAnalyzed": false,
     "licenseConcluded": "MIT",
     "copyrightText": "Copyright 2026 Acme Corp",
     "description": "Main firmware for Acme IoT gateway",
     "externalRefs": [
       {
         "referenceCategory": "PACKAGE-MANAGER",
         "referenceType": "purl",
         "referenceLocator": "pkg:generic/acme/device-firmware@2.1.0"
       }
     ]
   },
   {
     "name": "libtls",
     "SPDXID": "SPDXRef-libtls-3.9.0",
     "versionInfo": "3.9.0",
     "downloadLocation": "NOASSERTION",
     "filesAnalyzed": false,
     "checksums": [
       {
         "algorithm": "SHA-256",
         "checksumValue": "e3b0c44..."
       }
     ],
     "licenseConcluded": "ISC",
     "externalRefs": [
       {
         "referenceCategory": "PACKAGE-MANAGER",
         "referenceType": "purl",
         "referenceLocator": "pkg:generic/openbsd/libtls@3.9.0"
       }
     ]
   },
   {
     "name": "libgui",
     "SPDXID": "SPDXRef-libgui-2.0.0",
     "versionInfo": "2.0.0",
     "downloadLocation": "NOASSERTION",
     "filesAnalyzed": false,
     "checksums": [
       {
         "algorithm": "SHA-256",
         "checksumValue": "abc123..."
       }
     ],
     "licenseConcluded": "MIT",
     "externalRefs": [
       {
         "referenceCategory": "PACKAGE-MANAGER",
         "referenceType": "purl",
         "referenceLocator": "pkg:generic/lvgl/libgui@2.0.0"
       }
     ]
   },
   {
     "name": "libmqtt",
     "SPDXID": "SPDXRef-libmqtt-4.3.0",
     "versionInfo": "4.3.0",
     "downloadLocation": "NOASSERTION",
     "filesAnalyzed": false,
     "checksums": [
       {
         "algorithm": "SHA-256",
         "checksumValue": "9f86d08..."
       }
     ],
     "licenseConcluded": "EPL-2.0",
     "externalRefs": [
       {
         "referenceCategory": "PACKAGE-MANAGER",
         "referenceType": "purl",
         "referenceLocator": "pkg:generic/acme/libmqtt@4.3.0"
       }
     ]
   },
   {
     "name": "cjson",
     "SPDXID": "SPDXRef-cjson-1.7.17",
     "versionInfo": "1.7.17",
     "downloadLocation": "NOASSERTION",
     "filesAnalyzed": false,
     "checksums": [
       {
         "algorithm": "SHA-256",
         "checksumValue": "d2735c2..."
       }
     ],
     "licenseConcluded": "MIT",
     "externalRefs": [
       {
         "referenceCategory": "PACKAGE-MANAGER",
         "referenceType": "purl",
         "referenceLocator": "pkg:github/DaveGamble/cJSON@1.7.17"
       }
     ]
   },
   {
     "name": "miniz",
     "SPDXID": "SPDXRef-miniz-3.0.2",
     "versionInfo": "3.0.2",
     "downloadLocation": "NOASSERTION",
     "filesAnalyzed": false,
     "checksums": [
       {
         "algorithm": "SHA-256",
         "checksumValue": "f81bc5a..."
       }
     ],
     "licenseConcluded": "MIT",
     "externalRefs": [
       {
         "referenceCategory": "PACKAGE-MANAGER",
         "referenceType": "purl",
         "referenceLocator": "pkg:github/richgel999/miniz@3.0.2"
       }
     ]
   }
 ],
 "relationships": [
   {
     "spdxElementId": "SPDXRef-DOCUMENT",
     "relatedSpdxElement": "SPDXRef-device-firmware-2.1.0",
     "relationshipType": "DESCRIBES"
   },
   {
     "spdxElementId": "SPDXRef-device-firmware-2.1.0",
     "relatedSpdxElement": "SPDXRef-libgui-2.0.0",
     "relationshipType": "DEPENDS_ON"
   },
   {
     "spdxElementId": "SPDXRef-device-firmware-2.1.0",
     "relatedSpdxElement": "SPDXRef-libmqtt-4.3.0",
     "relationshipType": "DEPENDS_ON"
   },
   {
     "spdxElementId": "SPDXRef-device-firmware-2.1.0",
     "relatedSpdxElement": "SPDXRef-cjson-1.7.17",
     "relationshipType": "DEPENDS_ON"
   },
   {
     "spdxElementId": "SPDXRef-device-firmware-2.1.0",
     "relatedSpdxElement": "SPDXRef-miniz-3.0.2",
     "relationshipType": "DEPENDS_ON"
   },
   {
     "spdxElementId": "SPDXRef-libmqtt-4.3.0",
     "relatedSpdxElement": "SPDXRef-libtls-3.9.0",
     "relationshipType": "DEPENDS_ON"
   }
 ]
}

and for cyclonedx:

$ ./sbomasm generate sbom \               
  -i .components.json \
  -i libs/libmqtt/.components.json \
  -i src/cjson/.components.json \
  -i src/miniz/.components.json \
  -o device-firmware-2.1.0.cdx.json

ANd o/p of device-firmware-2.1.0.cdx.json:

{
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:17fa1318-7393-4de1-bffc-68daa69f3ea3",
"version": 1,
"metadata": {
  "timestamp": "2026-04-13T16:13:28Z",
  "tools": {
    "components": [
      {
        "type": "application",
        "supplier": {
          "name": "Interlynk",
          "url": [
            "https://interlynk.io"
          ],
          "contact": [
            {
              "email": "support@interlynk.io"
            }
          ]
        },
        "name": "sbomasm",
        "version": "v2.0.0-20260413160043-47ab5f6b3c99+dirty",
        "description": "sbomasm: The Complete SBOM Management Toolkit",
        "licenses": [
          {
            "license": {
              "id": "Apache-2.0"
            }
          }
        ]
      }
    ]
  },
  "component": {
    "bom-ref": "pkg:generic/acme/device-firmware@2.1.0",
    "type": "firmware",
    "supplier": {
      "name": "Acme Corp",
      "contact": [
        {
          "email": "engineering@acme.com"
        }
      ]
    },
    "authors": [
      {
        "name": "Jane Doe",
        "email": "jane@acme.com"
      }
    ],
    "name": "device-firmware",
    "version": "2.1.0",
    "description": "Main firmware for Acme IoT gateway",
    "licenses": [
      {
        "license": {
          "id": "MIT"
        }
      }
    ],
    "copyright": "Copyright 2026 Acme Corp",
    "cpe": "cpe:2.3:a:acme:device-firmware:2.1.0:*:*:*:*:*:*:*",
    "purl": "pkg:generic/acme/device-firmware@2.1.0"
  }
},
"components": [
  {
    "bom-ref": "pkg:generic/openbsd/libtls@3.9.0",
    "type": "library",
    "supplier": {
      "name": "OpenBSD"
    },
    "name": "libtls",
    "version": "3.9.0",
    "hashes": [
      {
        "alg": "SHA-256",
        "content": "e3b0c44..."
      }
    ],
    "licenses": [
      {
        "license": {
          "id": "ISC"
        }
      }
    ],
    "purl": "pkg:generic/openbsd/libtls@3.9.0"
  },
  {
    "bom-ref": "pkg:generic/lvgl/libgui@2.0.0",
    "type": "library",
    "supplier": {
      "name": "LVGL"
    },
    "name": "libgui",
    "version": "2.0.0",
    "hashes": [
      {
        "alg": "SHA-256",
        "content": "abc123..."
      }
    ],
    "licenses": [
      {
        "license": {
          "id": "MIT"
        }
      }
    ],
    "purl": "pkg:generic/lvgl/libgui@2.0.0"
  },
  {
    "bom-ref": "pkg:generic/acme/libmqtt@4.3.0",
    "type": "library",
    "supplier": {
      "name": "Acme Corp"
    },
    "name": "libmqtt",
    "version": "4.3.0",
    "hashes": [
      {
        "alg": "SHA-256",
        "content": "9f86d08..."
      }
    ],
    "licenses": [
      {
        "license": {
          "id": "EPL-2.0"
        }
      }
    ],
    "purl": "pkg:generic/acme/libmqtt@4.3.0"
  },
  {
    "bom-ref": "pkg:github/DaveGamble/cJSON@1.7.17",
    "type": "library",
    "supplier": {
      "name": "Dave Gamble"
    },
    "name": "cjson",
    "version": "1.7.17",
    "hashes": [
      {
        "alg": "SHA-256",
        "content": "d2735c2..."
      }
    ],
    "licenses": [
      {
        "license": {
          "id": "MIT"
        }
      }
    ],
    "purl": "pkg:github/DaveGamble/cJSON@1.7.17"
  },
  {
    "bom-ref": "pkg:github/richgel999/miniz@3.0.2",
    "type": "library",
    "supplier": {
      "name": "Rich Geldreich"
    },
    "name": "miniz",
    "version": "3.0.2",
    "hashes": [
      {
        "alg": "SHA-256",
        "content": "f81bc5a..."
      }
    ],
    "licenses": [
      {
        "license": {
          "id": "MIT"
        }
      }
    ],
    "purl": "pkg:github/richgel999/miniz@3.0.2"
  }
],
"dependencies": [
  {
    "ref": "pkg:generic/acme/libmqtt@4.3.0",
    "dependsOn": [
      "pkg:generic/openbsd/libtls@3.9.0"
    ]
  },
  {
    "ref": "pkg:generic/acme/device-firmware@2.1.0",
    "dependsOn": [
      "pkg:generic/lvgl/libgui@2.0.0",
      "pkg:generic/acme/libmqtt@4.3.0",
      "pkg:github/DaveGamble/cJSON@1.7.17",
      "pkg:github/richgel999/miniz@3.0.2"
    ]
  }
]
}

Copilot AI review requested due to automatic review settings April 9, 2026 15:58
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an SBOM generation workflow to sbomasm via a new generate sbom subcommand, building CycloneDX or SPDX SBOM outputs from distributed component manifest files plus artifact metadata.

Changes:

  • Introduces pkg/generate/gsbom (artifact config loader, recursive input discovery, JSON/CSV parsing, merge/dedup, tag filtering, dependency graph, serializers).
  • Adds CLI subcommands under generate: config and sbom, with flags for inputs, recursion, tags, output, and format.
  • Implements CycloneDX + SPDX JSON serialization for the generated BOM model.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
pkg/generate/gsbom/serializer_spdx.go SPDX document + package/relationship construction and JSON output
pkg/generate/gsbom/serializer_cdx.go CycloneDX BOM construction (metadata/components/dependencies) and JSON output
pkg/generate/gsbom/parser.go Component manifest parsing for JSON + CSV with schema marker validation
pkg/generate/gsbom/merge.go Merge + dedup helpers; name@version keying
pkg/generate/gsbom/input_collector.go Collects explicit inputs and optionally discovers manifests via recursion
pkg/generate/gsbom/gsbom.go Orchestrates end-to-end SBOM generation pipeline
pkg/generate/gsbom/filter.go Tag include/exclude filtering for components
pkg/generate/gsbom/dependency.go Dependency graph construction from dependency-of references
pkg/generate/gsbom/config.go Loads .artifact-metadata.yaml into an internal artifact model
pkg/generate/gsbom/builder.go Assembles final BOM model (artifact + components + dependency edges)
cmd/generate.go Converts generate into a parent command with subcommands
cmd/generate_sbom.go Implements sbomasm generate sbom command + flags
cmd/generate_config.go Implements sbomasm generate config command

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

for i, col := range columns {
colIndex[col] = i
}

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseCSV builds colIndex from the CSV header but never validates required columns are present. If the CSV is missing/typo’d (e.g., "name"/"version"), lookups like colIndex["name"] will default to 0 and can silently map the wrong column or later cause index-out-of-range panics. Add explicit checks that required headers exist before reading records and return a clear error listing missing columns.

Suggested change
requiredColumns := []string{
"name",
"version",
"type",
"license",
"purl",
"cpe",
"dependency_of",
"tags",
}
var missingColumns []string
for _, col := range requiredColumns {
if _, ok := colIndex[col]; !ok {
missingColumns = append(missingColumns, col)
}
}
if len(missingColumns) > 0 {
return nil, fmt.Errorf("missing required columns: %s", strings.Join(missingColumns, ", "))
}

Copilot uses AI. Check for mistakes.
Comment on lines +144 to +151
c := Component{
Name: record[colIndex["name"]],
Version: record[colIndex["version"]],
Type: record[colIndex["type"]],
License: record[colIndex["license"]],
PURL: record[colIndex["purl"]],
CPE: record[colIndex["cpe"]],
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseCSV indexes into record[...] assuming each row has all expected columns. A short/malformed row will panic with "index out of range". Consider validating len(record) against the max required column index (or using a helper to safely fetch fields) and returning a parsing error with row context instead of panicking.

Copilot uses AI. Check for mistakes.
Comment thread pkg/generate/gsbom/parser.go Outdated
Comment on lines +153 to +161
// dependency_of
if v := record[colIndex["dependency_of"]]; v != "" {
c.DependencyOf = strings.Split(v, ",")
}

// tags
if v := record[colIndex["tags"]]; v != "" {
c.Tags = strings.Split(v, ",")
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When splitting CSV fields like dependency_of and tags on commas, values aren’t TrimSpace’d. This makes common inputs like "libtls@3.9.0, libfoo@1.2.3" fail dependency resolution / tag matching due to leading spaces. Trim whitespace for each entry and drop empty strings after splitting.

Copilot uses AI. Check for mistakes.
Comment thread pkg/generate/gsbom/parser.go Outdated
Comment on lines +96 to +98
if doc.Schema != "interlynk/component-manifest/v1" {
return nil, fmt.Errorf("invalid schema")
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema validation error "invalid schema" doesn’t include the schema value that was found or the expected value, which makes debugging harder. Include both (and ideally the file path) in the returned error message.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +64
for _, c := range components {
childKey := componentKey(c)

// Case 1: has dependency-of
if len(c.DependencyOf) > 0 {
for _, parentRef := range c.DependencyOf {

// Check if parent exists
if _, ok := compMap[parentRef]; !ok {
warnings = append(warnings,
fmt.Errorf("missing dependency reference: %s → %s", childKey, parentRef))
continue
}

// parent -> child
graph.Edges[parentRef] = append(graph.Edges[parentRef], childKey)
}
} else {
// Case 2: top-level (handled later in SBOM builder)
continue
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a component has DependencyOf entries but all of them are missing from compMap, this function only emits warnings and the component becomes disconnected: it won’t be treated as top-level (len(DependencyOf)!=0) and no edge is added. Consider either (a) treating components with no valid parents as top-level, or (b) returning an error so the generated SBOM doesn’t silently omit dependency relationships for that component.

Copilot uses AI. Check for mistakes.
Comment thread pkg/generate/gsbom/input_collector.go Outdated
Comment on lines +27 to +45
// 1. Add explicit inputs FIRST
files = append(files, params.InputFiles...)

// 2. Handle recurse
if params.RecursePath != "" {
err := filepath.Walk(params.RecursePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
warnings = append(warnings, fmt.Errorf("error accessing path %s: %v", path, err))
return nil
}

if info.IsDir() {
return nil
}

// Match filename
if info.Name() == params.Filename {
files = append(files, path)
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CollectInputFiles can return duplicate paths when the same file is provided via --input and also discovered via --recurse (or if inputs repeat). That leads to redundant parsing and can inflate duplicate-component warnings. Consider de-duplicating file paths (e.g., by absolute/cleaned path) before returning.

Copilot uses AI. Check for mistakes.
Comment thread pkg/generate/gsbom/config.go Outdated
"fmt"
"os"

"go.yaml.in/yaml/v2"
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file imports go.yaml.in/yaml/v2 while the rest of the repo uses gopkg.in/yaml.v2 (e.g., pkg/assemble/config.go:33). Using a different YAML module increases dependency surface and can cause subtle behavior differences. Prefer the same YAML package already used elsewhere unless there’s a specific need.

Suggested change
"go.yaml.in/yaml/v2"
"gopkg.in/yaml.v2"

Copilot uses AI. Check for mistakes.
Comment thread cmd/generate_sbom.go Outdated
Comment on lines +73 to +80
generateSbomCmd.Flags().StringP("config", "c", ".artifact-metadata.yaml", "artifact metadata config file")
generateSbomCmd.Flags().StringSliceP("input", "i", []string{}, "component input files")
generateSbomCmd.Flags().StringP("output", "o", "", "output SBOM file (default stdout)")
generateSbomCmd.Flags().StringSliceP("tags", "t", []string{}, "include components with these tags")
generateSbomCmd.Flags().StringSlice("exclude-tags", []string{}, "exclude components with these tags")
generateSbomCmd.Flags().String("format", "cyclonedx(default)", "output format (cyclonedx|spdx)")
generateSbomCmd.Flags().StringP("recurse", "r", "", "recursively discover component files")
generateSbomCmd.Flags().String("filename", ".components.json", "filename for recursive discovery")
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --format flag default is "cyclonedx(default)", but the help text advertises accepted values "cyclonedx|spdx". This makes the default not match the documented set and can confuse users/scripts. Prefer defaulting to "cyclonedx" and (optionally) validate the flag value to error on unknown formats instead of silently falling back.

Copilot uses AI. Check for mistakes.
Comment thread cmd/generate.go
Comment on lines 22 to +35
var generateCmd = &cobra.Command{
Use: "generate",
Short: "Generate a sample config file for assembling sboms",
Long: `The generate command will generate a sample config file for assembling sboms.
Example:
$ sbomasm generate > config.yaml

Please fill in all the fields that are known. Unknown fields can be left blank.`,
Args: cobra.NoArgs,
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("%s", assemble.DefaultConfigYaml())
},
$ sbomasm generate config
$ sbomasm generate sbom -r . -o device-firmware-2.1.0.cdx.json
$ sbomasm generate sbom \
-i .components.json \
-i libs/libmqtt/.components.json \
-i src/cjson/.components.json \
-i src/miniz/.components.json \
-o device-firmware-2.1.0.cdx.json
`,
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateCmd’s Short/Long text still describes only generating a sample assemble config, but the command now has subcommands (config, sbom). Update the help text to reflect the new structure and avoid misleading CLI users.

Copilot uses AI. Check for mistakes.
Comment thread cmd/generate_config.go
Comment on lines +24 to +29
var generateConfigCmd = &cobra.Command{
Use: "config",
Short: "Generate artifact metadata config",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("%s", assemble.DefaultConfigYaml())
},
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generate config is described as generating artifact metadata, but it prints assemble.DefaultConfigYaml(), which includes unrelated sections (output/assemble) not used by gsbom’s .artifact-metadata.yaml parser. Consider generating a minimal artifact-metadata template (app section only) to match the command’s intent and the PR description.

Copilot uses AI. Check for mistakes.
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Generate SBOM from component metadata files

2 participants