Examples

Complex Serialize

  1# This file is part of CycloneDX Python Library
  2#
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#     http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14#
 15# SPDX-License-Identifier: Apache-2.0
 16# Copyright (c) OWASP Foundation. All Rights Reserved.
 17
 18import sys
 19from typing import TYPE_CHECKING
 20
 21from packageurl import PackageURL
 22
 23from cyclonedx.builder.this import this_component as cdx_lib_component
 24from cyclonedx.exception import MissingOptionalDependencyException
 25from cyclonedx.factory.license import LicenseFactory
 26from cyclonedx.model import XsUri
 27from cyclonedx.model.bom import Bom
 28from cyclonedx.model.component import Component, ComponentType
 29from cyclonedx.model.contact import OrganizationalEntity
 30from cyclonedx.output import make_outputter
 31from cyclonedx.output.json import JsonV1Dot5
 32from cyclonedx.schema import OutputFormat, SchemaVersion
 33from cyclonedx.validation import make_schemabased_validator
 34from cyclonedx.validation.json import JsonStrictValidator
 35
 36if TYPE_CHECKING:
 37    from cyclonedx.output.json import Json as JsonOutputter
 38    from cyclonedx.output.xml import Xml as XmlOutputter
 39    from cyclonedx.validation.xml import XmlValidator
 40
 41
 42lc_factory = LicenseFactory()
 43
 44# region build the BOM
 45
 46bom = Bom()
 47bom.metadata.tools.components.add(cdx_lib_component())
 48bom.metadata.tools.components.add(Component(
 49    name='my-own-SBOM-generator',
 50    type=ComponentType.APPLICATION,
 51))
 52
 53bom.metadata.component = root_component = Component(
 54    name='myApp',
 55    type=ComponentType.APPLICATION,
 56    licenses=[lc_factory.make_from_string('MIT')],
 57    bom_ref='myApp',
 58)
 59
 60component1 = Component(
 61    type=ComponentType.LIBRARY,
 62    name='some-component',
 63    group='acme',
 64    version='1.33.7-beta.1',
 65    licenses=[lc_factory.make_from_string('(c) 2021 Acme inc.')],
 66    supplier=OrganizationalEntity(
 67        name='Acme Inc',
 68        urls=[XsUri('https://www.acme.org')]
 69    ),
 70    bom_ref='myComponent@1.33.7-beta.1',
 71    purl=PackageURL('generic', 'acme', 'some-component', '1.33.7-beta.1')
 72)
 73bom.components.add(component1)
 74bom.register_dependency(root_component, [component1])
 75
 76component2 = Component(
 77    type=ComponentType.LIBRARY,
 78    name='some-library',
 79    licenses=[lc_factory.make_from_string('GPL-3.0-only WITH Classpath-exception-2.0')]
 80)
 81bom.components.add(component2)
 82bom.register_dependency(component1, [component2])
 83
 84# endregion build the BOM
 85
 86# region JSON
 87"""demo with explicit instructions for SchemaVersion, outputter and validator"""
 88
 89my_json_outputter: 'JsonOutputter' = JsonV1Dot5(bom)
 90serialized_json = my_json_outputter.output_as_string(indent=2)
 91print(serialized_json)
 92my_json_validator = JsonStrictValidator(SchemaVersion.V1_6)
 93try:
 94    validation_errors = my_json_validator.validate_str(serialized_json)
 95    if validation_errors:
 96        print('JSON invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr)
 97        sys.exit(2)
 98    print('JSON valid')
 99except MissingOptionalDependencyException as error:
100    print('JSON-validation was skipped due to', error)
101
102# endregion JSON
103
104print('', '=' * 30, '', sep='\n')
105
106# region XML
107"""demo with implicit instructions for SchemaVersion, outputter and validator. TypeCheckers will catch errors."""
108
109my_xml_outputter: 'XmlOutputter' = make_outputter(bom, OutputFormat.XML, SchemaVersion.V1_6)
110serialized_xml = my_xml_outputter.output_as_string(indent=2)
111print(serialized_xml)
112my_xml_validator: 'XmlValidator' = make_schemabased_validator(
113    my_xml_outputter.output_format, my_xml_outputter.schema_version)
114try:
115    validation_errors = my_xml_validator.validate_str(serialized_xml)
116    if validation_errors:
117        print('XML invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr)
118        sys.exit(2)
119    print('XML valid')
120except MissingOptionalDependencyException as error:
121    print('XML-validation was skipped due to', error)
122
123# endregion XML

Complex Deserialize

  1# This file is part of CycloneDX Python Library
  2#
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#     http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14#
 15# SPDX-License-Identifier: Apache-2.0
 16# Copyright (c) OWASP Foundation. All Rights Reserved.
 17
 18import sys
 19from json import loads as json_loads
 20from typing import TYPE_CHECKING
 21
 22from defusedxml import ElementTree as SafeElementTree  # type:ignore[import-untyped]
 23
 24from cyclonedx.exception import MissingOptionalDependencyException
 25from cyclonedx.model.bom import Bom
 26from cyclonedx.schema import OutputFormat, SchemaVersion
 27from cyclonedx.validation import make_schemabased_validator
 28from cyclonedx.validation.json import JsonStrictValidator
 29
 30if TYPE_CHECKING:
 31    from cyclonedx.validation.xml import XmlValidator
 32
 33# region JSON
 34
 35json_data = """{
 36  "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
 37  "bomFormat": "CycloneDX",
 38  "specVersion": "1.6",
 39  "serialNumber": "urn:uuid:88fabcfa-7529-4ba2-8256-29bec0c03900",
 40  "version": 1,
 41  "metadata": {
 42    "timestamp": "2024-02-10T21:38:53.313120+00:00",
 43    "tools": [
 44      {
 45        "vendor": "CycloneDX",
 46        "name": "cyclonedx-python-lib",
 47        "version": "6.4.1",
 48        "externalReferences": [
 49          {
 50            "type": "build-system",
 51            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions"
 52          },
 53          {
 54            "type": "distribution",
 55            "url": "https://pypi.org/project/cyclonedx-python-lib/"
 56          },
 57          {
 58            "type": "documentation",
 59            "url": "https://cyclonedx-python-library.readthedocs.io/"
 60          },
 61          {
 62            "type": "issue-tracker",
 63            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues"
 64          },
 65          {
 66            "type": "license",
 67            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE"
 68          },
 69          {
 70            "type": "release-notes",
 71            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md"
 72          },
 73          {
 74            "type": "vcs",
 75            "url": "https://github.com/CycloneDX/cyclonedx-python-lib"
 76          },
 77          {
 78            "type": "website",
 79            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/#readme"
 80          }
 81        ]
 82      }
 83    ],
 84    "component": {
 85      "bom-ref": "myApp",
 86      "name": "myApp",
 87      "type": "application",
 88      "licenses": [
 89        {
 90          "license": {
 91            "id": "MIT"
 92          }
 93        }
 94      ]
 95    }
 96  },
 97  "components": [
 98    {
 99      "bom-ref": "myComponent@1.33.7-beta.1",
100      "type": "library",
101      "group": "acme",
102      "name": "some-component",
103      "version": "1.33.7-beta.1",
104      "purl": "pkg:generic/acme/some-component@1.33.7-beta.1",
105      "licenses": [
106        {
107          "license": {
108            "name": "(c) 2021 Acme inc."
109          }
110        }
111      ],
112      "supplier": {
113        "name": "Acme Inc",
114        "url": [
115          "https://www.acme.org"
116        ]
117      }
118    },
119    {
120      "bom-ref": "some-lib",
121      "type": "library",
122      "name": "some-library",
123      "licenses": [
124        {
125          "expression": "GPL-3.0-only WITH Classpath-exception-2.0"
126        }
127      ]
128    }
129  ],
130  "dependencies": [
131    {
132      "ref": "some-lib"
133    },
134    {
135      "dependsOn": [
136        "myComponent@1.33.7-beta.1"
137      ],
138      "ref": "myApp"
139    },
140    {
141      "dependsOn": [
142        "some-lib"
143      ],
144      "ref": "myComponent@1.33.7-beta.1"
145    }
146  ]
147}"""
148my_json_validator = JsonStrictValidator(SchemaVersion.V1_6)
149try:
150    validation_errors = my_json_validator.validate_str(json_data)
151    if validation_errors:
152        print('JSON invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr)
153        sys.exit(2)
154    print('JSON valid')
155except MissingOptionalDependencyException as error:
156    print('JSON-validation was skipped due to', error)
157bom_from_json = Bom.from_json(  # type: ignore[attr-defined]
158    json_loads(json_data))
159print('bom_from_json', repr(bom_from_json))
160
161# endregion JSON
162
163print('', '=' * 30, '', sep='\n')
164
165# endregion XML
166
167xml_data = """<?xml version="1.0" ?>
168<bom xmlns="http://cyclonedx.org/schema/bom/1.6"
169  serialNumber="urn:uuid:88fabcfa-7529-4ba2-8256-29bec0c03900"
170  version="1"
171>
172  <metadata>
173    <timestamp>2024-02-10T21:38:53.313120+00:00</timestamp>
174    <tools>
175      <tool>
176        <vendor>CycloneDX</vendor>
177        <name>cyclonedx-python-lib</name>
178        <version>6.4.1</version>
179        <externalReferences>
180          <reference type="build-system">
181            <url>https://github.com/CycloneDX/cyclonedx-python-lib/actions</url>
182          </reference>
183          <reference type="distribution">
184            <url>https://pypi.org/project/cyclonedx-python-lib/</url>
185          </reference>
186          <reference type="documentation">
187            <url>https://cyclonedx-python-library.readthedocs.io/</url>
188          </reference>
189          <reference type="issue-tracker">
190            <url>https://github.com/CycloneDX/cyclonedx-python-lib/issues</url>
191          </reference>
192          <reference type="license">
193            <url>https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE</url>
194          </reference>
195          <reference type="release-notes">
196            <url>https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md</url>
197          </reference>
198          <reference type="vcs">
199            <url>https://github.com/CycloneDX/cyclonedx-python-lib</url>
200          </reference>
201          <reference type="website">
202            <url>https://github.com/CycloneDX/cyclonedx-python-lib/#readme</url>
203          </reference>
204        </externalReferences>
205      </tool>
206    </tools>
207    <component type="application" bom-ref="myApp">
208      <name>myApp</name>
209      <licenses>
210        <license>
211          <id>MIT</id>
212        </license>
213      </licenses>
214    </component>
215  </metadata>
216  <components>
217    <component type="library" bom-ref="myComponent@1.33.7-beta.1">
218      <supplier>
219        <name>Acme Inc</name>
220        <url>https://www.acme.org</url>
221      </supplier>
222      <group>acme</group>
223      <name>some-component</name>
224      <version>1.33.7-beta.1</version>
225      <licenses>
226        <license>
227          <name>(c) 2021 Acme inc.</name>
228        </license>
229      </licenses>
230      <purl>pkg:generic/acme/some-component@1.33.7-beta.1</purl>
231    </component>
232    <component type="library" bom-ref="some-lib">
233      <name>some-library</name>
234      <licenses>
235        <expression>GPL-3.0-only WITH Classpath-exception-2.0</expression>
236      </licenses>
237    </component>
238  </components>
239  <dependencies>
240    <dependency ref="some-lib"/>
241    <dependency ref="myApp">
242      <dependency ref="myComponent@1.33.7-beta.1"/>
243    </dependency>
244    <dependency ref="myComponent@1.33.7-beta.1">
245      <dependency ref="some-lib"/>
246    </dependency>
247  </dependencies>
248</bom>"""
249my_xml_validator: 'XmlValidator' = make_schemabased_validator(OutputFormat.XML, SchemaVersion.V1_6)
250try:
251    validation_errors = my_xml_validator.validate_str(xml_data)
252    if validation_errors:
253        print('XML invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr)
254        sys.exit(2)
255    print('XML valid')
256except MissingOptionalDependencyException as error:
257    print('XML-validation was skipped due to', error)
258bom_from_xml = Bom.from_xml(  # type: ignore[attr-defined]
259    SafeElementTree.fromstring(xml_data))
260print('bom_from_xml', repr(bom_from_xml))
261
262# endregion XML
263
264print('', '=' * 30, '', sep='\n')
265
266print('assert bom_from_json equals bom_from_xml')
267assert bom_from_json == bom_from_xml, 'expected to have equal BOMs from JSON and XML'