github.com/glimps-jbo/go-licenses@v0.0.0-20230908151000-e06d3c113277/report.go (about) 1 // Copyright 2019 Google Inc. All Rights Reserved. 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 package main 16 17 import ( 18 "context" 19 "encoding/csv" 20 "fmt" 21 "os" 22 "text/template" 23 "time" 24 25 "github.com/glimps-jbo/go-licenses/internal/third_party/pkgsite/source" 26 "github.com/glimps-jbo/go-licenses/licenses" 27 "github.com/spf13/cobra" 28 "golang.org/x/sync/errgroup" 29 "k8s.io/klog/v2" 30 ) 31 32 const ( 33 UNKNOWN = "Unknown" 34 ) 35 36 var ( 37 reportHelp = "Prints report of all licenses that apply to one or more Go packages and their dependencies." 38 reportCmd = &cobra.Command{ 39 Use: "report <package> [package...]", 40 Short: reportHelp, 41 Long: reportHelp + packageHelp, 42 Args: cobra.MinimumNArgs(1), 43 RunE: reportMain, 44 } 45 46 templateFile string 47 timeout = time.Second * 20 48 ) 49 50 func init() { 51 reportCmd.Flags().StringVar(&templateFile, "template", "", "Custom Go template file to use for report") 52 reportCmd.Flags().DurationVar(&timeout, "timeout", time.Second*20, "Download package timeout") 53 54 rootCmd.AddCommand(reportCmd) 55 } 56 57 type libraryData struct { 58 Name string 59 Version string 60 LicensePath string 61 LicenseURL string 62 LicenseNames []string 63 } 64 65 type libraryDataFlat struct { 66 Name string 67 Version string 68 LicensePath string 69 LicenseURL string 70 LicenseName string 71 } 72 73 // LicenseText reads and returns the contents of LicensePath, if set 74 // or an empty string if not. 75 func (lib libraryDataFlat) LicenseText() (string, error) { 76 if lib.LicensePath == "" { 77 return "", nil 78 } 79 data, err := os.ReadFile(lib.LicensePath) 80 if err != nil { 81 return "", err 82 } 83 return string(data), nil 84 } 85 86 func reportMain(_ *cobra.Command, args []string) error { 87 classifier, err := licenses.NewClassifier() 88 if err != nil { 89 return err 90 } 91 92 libs, err := licenses.Libraries(context.Background(), classifier, includeTests, ignore, args...) 93 if err != nil { 94 return err 95 } 96 97 reportData := make([]libraryData, len(libs)) 98 client := source.NewClient(timeout) 99 group, gctx := errgroup.WithContext(context.Background()) 100 for idx, lib := range libs { 101 idx := idx 102 lib := lib 103 104 reportData[idx] = libraryData{ 105 Name: lib.Name(), 106 Version: UNKNOWN, 107 LicensePath: UNKNOWN, 108 LicenseURL: UNKNOWN, 109 LicenseNames: nil, 110 } 111 112 if version := lib.Version(); version != "" { 113 reportData[idx].Version = version 114 } 115 116 if lib.LicenseFile != "" { 117 reportData[idx].LicensePath = lib.LicenseFile 118 } 119 120 for _, license := range lib.Licenses { 121 reportData[idx].LicenseNames = append(reportData[idx].LicenseNames, license.Name) 122 } 123 124 if lib.LicenseFile != "" { 125 group.Go(func() error { 126 url, err := lib.FileURL(gctx, client, lib.LicenseFile) 127 if err == nil { 128 reportData[idx].LicenseURL = url 129 } else { 130 klog.Warningf("Error discovering license URL: %s", err) 131 } 132 return nil 133 }) 134 } 135 } 136 137 if err := group.Wait(); err != nil { 138 return err 139 } 140 141 // Flatten the report data 142 reportDataFlat := make([]libraryDataFlat, 0, len(reportData)) 143 for _, lib := range reportData { 144 if len(lib.LicenseNames) == 0 { 145 if lib.LicensePath != UNKNOWN { 146 klog.Errorf("Error identifying license in %q: %v", lib.LicensePath, fmt.Errorf("no license found")) 147 } else if lib.Version != UNKNOWN { 148 klog.Errorf("Error identifying license for version %q of %q: %v", lib.Version, lib.Name, fmt.Errorf("no license found")) 149 } else { 150 klog.Errorf("Error identifying license for %q: %v", lib.Name, fmt.Errorf("no license found")) 151 } 152 reportDataFlat = append(reportDataFlat, libraryDataFlat{ 153 Name: lib.Name, 154 Version: lib.Version, 155 LicensePath: lib.LicensePath, 156 LicenseURL: lib.LicenseURL, 157 LicenseName: UNKNOWN, 158 }) 159 } else { 160 for _, licenseName := range lib.LicenseNames { 161 reportDataFlat = append(reportDataFlat, libraryDataFlat{ 162 Name: lib.Name, 163 Version: lib.Version, 164 LicensePath: lib.LicensePath, 165 LicenseURL: lib.LicenseURL, 166 LicenseName: licenseName, 167 }) 168 } 169 } 170 } 171 172 if templateFile == "" { 173 return reportCSV(reportDataFlat) 174 } else { 175 return reportTemplate(reportDataFlat) 176 } 177 } 178 179 func reportCSV(libs []libraryDataFlat) error { 180 writer := csv.NewWriter(os.Stdout) 181 for _, lib := range libs { 182 if err := writer.Write([]string{lib.Name, lib.LicenseURL, lib.LicenseName}); err != nil { 183 return err 184 } 185 } 186 writer.Flush() 187 return writer.Error() 188 } 189 190 func reportTemplate(libs []libraryDataFlat) error { 191 templateBytes, err := os.ReadFile(templateFile) 192 if err != nil { 193 return err 194 } 195 tmpl, err := template.New("").Parse(string(templateBytes)) 196 if err != nil { 197 return err 198 } 199 return tmpl.Execute(os.Stdout, libs) 200 }