1*f578df4fSJingwen Chen#!/usr/bin/env python3 2*f578df4fSJingwen Chen# Copyright 2020 Google LLC 3*f578df4fSJingwen Chen# 4*f578df4fSJingwen Chen# Licensed under the Apache License, Version 2.0 (the "License"); 5*f578df4fSJingwen Chen# you may not use this file except in compliance with the License. 6*f578df4fSJingwen Chen# You may obtain a copy of the License at 7*f578df4fSJingwen Chen# 8*f578df4fSJingwen Chen# https://www.apache.org/licenses/LICENSE-2.0 9*f578df4fSJingwen Chen# 10*f578df4fSJingwen Chen# Unless required by applicable law or agreed to in writing, software 11*f578df4fSJingwen Chen# distributed under the License is distributed on an "AS IS" BASIS, 12*f578df4fSJingwen Chen# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*f578df4fSJingwen Chen# See the License for the specific language governing permissions and 14*f578df4fSJingwen Chen# limitations under the License. 15*f578df4fSJingwen Chen 16*f578df4fSJingwen Chen"""Proof of concept license checker. 17*f578df4fSJingwen Chen 18*f578df4fSJingwen ChenThis is only a demonstration. It will be replaced with other tools. 19*f578df4fSJingwen Chen""" 20*f578df4fSJingwen Chen 21*f578df4fSJingwen Chenimport argparse 22*f578df4fSJingwen Chenimport codecs 23*f578df4fSJingwen Chenimport datetime 24*f578df4fSJingwen Chenimport json 25*f578df4fSJingwen Chenimport os 26*f578df4fSJingwen Chen 27*f578df4fSJingwen Chen 28*f578df4fSJingwen ChenTOOL = 'https//github.com/bazelbuild/rules_license/tools:write_sbom' 29*f578df4fSJingwen Chen 30*f578df4fSJingwen Chendef _load_package_data(package_info): 31*f578df4fSJingwen Chen with codecs.open(package_info, encoding='utf-8') as inp: 32*f578df4fSJingwen Chen return json.loads(inp.read()) 33*f578df4fSJingwen Chen 34*f578df4fSJingwen Chendef _write_sbom_header(out, package): 35*f578df4fSJingwen Chen header = [ 36*f578df4fSJingwen Chen 'SPDXVersion: SPDX-2.2', 37*f578df4fSJingwen Chen 'DataLicense: CC0-1.0', 38*f578df4fSJingwen Chen 'SPDXID: SPDXRef-DOCUMENT', 39*f578df4fSJingwen Chen 'DocumentName: %s' % package, 40*f578df4fSJingwen Chen # TBD 41*f578df4fSJingwen Chen # 'DocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3 42*f578df4fSJingwen Chen 'Creator: Person: %s' % os.getlogin(), 43*f578df4fSJingwen Chen 'Creator: Tool: %s' % TOOL, 44*f578df4fSJingwen Chen datetime.datetime.utcnow().strftime('Created: %Y-%m-%d-%H:%M:%SZ'), 45*f578df4fSJingwen Chen '', 46*f578df4fSJingwen Chen '##### Package: %s' % package, 47*f578df4fSJingwen Chen ] 48*f578df4fSJingwen Chen out.write('\n'.join(header)) 49*f578df4fSJingwen Chen 50*f578df4fSJingwen Chen 51*f578df4fSJingwen Chen 52*f578df4fSJingwen Chendef _write_sbom(out, packages): 53*f578df4fSJingwen Chen """Produce a basic SBOM 54*f578df4fSJingwen Chen 55*f578df4fSJingwen Chen Args: 56*f578df4fSJingwen Chen out: file object to write to 57*f578df4fSJingwen Chen packages: package metadata. A big blob of JSON. 58*f578df4fSJingwen Chen """ 59*f578df4fSJingwen Chen for p in packages: 60*f578df4fSJingwen Chen name = p.get('package_name') or '<unknown>' 61*f578df4fSJingwen Chen out.write('\n') 62*f578df4fSJingwen Chen out.write('SPDXID: "%s"\n' % name) 63*f578df4fSJingwen Chen out.write(' name: "%s"\n' % name) 64*f578df4fSJingwen Chen if p.get('package_version'): 65*f578df4fSJingwen Chen out.write(' versionInfo: "%s"\n' % p['package_version']) 66*f578df4fSJingwen Chen # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one. 67*f578df4fSJingwen Chen cn = p.get('copyright_notice') 68*f578df4fSJingwen Chen if cn: 69*f578df4fSJingwen Chen out.write(' copyrightText: "%s"\n' % cn) 70*f578df4fSJingwen Chen kinds = p.get('license_kinds') 71*f578df4fSJingwen Chen if kinds: 72*f578df4fSJingwen Chen out.write(' licenseDeclared: "%s"\n' % 73*f578df4fSJingwen Chen ','.join([k['name'] for k in kinds])) 74*f578df4fSJingwen Chen url = p.get('package_url') 75*f578df4fSJingwen Chen if url: 76*f578df4fSJingwen Chen out.write(' downloadLocation: %s\n' % url) 77*f578df4fSJingwen Chen 78*f578df4fSJingwen Chen 79*f578df4fSJingwen Chendef main(): 80*f578df4fSJingwen Chen parser = argparse.ArgumentParser( 81*f578df4fSJingwen Chen description='Demonstraton license compliance checker') 82*f578df4fSJingwen Chen 83*f578df4fSJingwen Chen parser.add_argument('--licenses_info', 84*f578df4fSJingwen Chen help='path to JSON file containing all license data') 85*f578df4fSJingwen Chen parser.add_argument('--out', default='sbom.out', help='SBOM output') 86*f578df4fSJingwen Chen args = parser.parse_args() 87*f578df4fSJingwen Chen 88*f578df4fSJingwen Chen license_data = _load_package_data(args.licenses_info) 89*f578df4fSJingwen Chen target = license_data[0] # we assume only one target for the demo 90*f578df4fSJingwen Chen 91*f578df4fSJingwen Chen top_level_target = target['top_level_target'] 92*f578df4fSJingwen Chen dependencies = target['dependencies'] 93*f578df4fSJingwen Chen # It's not really packages, but this is close proxy for now 94*f578df4fSJingwen Chen licenses = target['licenses'] 95*f578df4fSJingwen Chen package_infos = target['packages'] 96*f578df4fSJingwen Chen 97*f578df4fSJingwen Chen # These are similar dicts, so merge them by package. This is not 98*f578df4fSJingwen Chen # strictly true, as different licenese can appear in the same 99*f578df4fSJingwen Chen # package, but it is good enough for demonstrating the sbom. 100*f578df4fSJingwen Chen 101*f578df4fSJingwen Chen all = {x['bazel_package']: x for x in licenses} 102*f578df4fSJingwen Chen for pi in package_infos: 103*f578df4fSJingwen Chen p = all.get(pi['bazel_package']) 104*f578df4fSJingwen Chen if p: 105*f578df4fSJingwen Chen p.update(pi) 106*f578df4fSJingwen Chen else: 107*f578df4fSJingwen Chen all[pi['bazel_package']] = pi 108*f578df4fSJingwen Chen 109*f578df4fSJingwen Chen err = 0 110*f578df4fSJingwen Chen with codecs.open(args.out, mode='w', encoding='utf-8') as out: 111*f578df4fSJingwen Chen _write_sbom_header(out, package=top_level_target) 112*f578df4fSJingwen Chen _write_sbom(out, all.values()) 113*f578df4fSJingwen Chen return err 114*f578df4fSJingwen Chen 115*f578df4fSJingwen Chen 116*f578df4fSJingwen Chenif __name__ == '__main__': 117*f578df4fSJingwen Chen main() 118