github.com/jfrog/jfrog-cli-go@v1.22.1-0.20200318093948-4826ef344ffd/artifactory/utils/nuget/dependencies/packagesconfig.go (about) 1 package dependencies 2 3 import ( 4 "encoding/xml" 5 "fmt" 6 gofrogcmd "github.com/jfrog/gofrog/io" 7 "github.com/jfrog/jfrog-cli-go/artifactory/utils/nuget" 8 "github.com/jfrog/jfrog-client-go/artifactory/buildinfo" 9 "github.com/jfrog/jfrog-client-go/utils/errorutils" 10 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 11 "github.com/jfrog/jfrog-client-go/utils/log" 12 "io/ioutil" 13 "path/filepath" 14 "strings" 15 ) 16 17 var packagesFilePath = "packages.config" 18 19 // Register packages.config extractor 20 func init() { 21 register(&packagesExtractor{}) 22 } 23 24 // packages.config dependency extractor 25 type packagesExtractor struct { 26 allDependencies map[string]*buildinfo.Dependency 27 childrenMap map[string][]string 28 rootDependencies []string 29 } 30 31 func (extractor *packagesExtractor) IsCompatible(projectName, projectRoot string) (bool, error) { 32 packagesConfigPath := filepath.Join(projectRoot, packagesFilePath) 33 exists, err := fileutils.IsFileExists(packagesConfigPath, false) 34 if exists { 35 log.Debug("Found", packagesConfigPath, "file for project:", projectName) 36 return true, err 37 } 38 return false, err 39 } 40 41 func (extractor *packagesExtractor) DirectDependencies() ([]string, error) { 42 return getDirectDependencies(extractor.allDependencies, extractor.childrenMap), nil 43 } 44 45 func (extractor *packagesExtractor) AllDependencies() (map[string]*buildinfo.Dependency, error) { 46 return extractor.allDependencies, nil 47 } 48 49 func (extractor *packagesExtractor) ChildrenMap() (map[string][]string, error) { 50 return extractor.childrenMap, nil 51 } 52 53 // Create new packages.config extractor 54 func (extractor *packagesExtractor) new(projectName, projectRoot string) (Extractor, error) { 55 newExtractor := &packagesExtractor{allDependencies: map[string]*buildinfo.Dependency{}, childrenMap: map[string][]string{}} 56 packagesConfig, err := newExtractor.loadPackagesConfig(projectRoot) 57 if err != nil { 58 return nil, err 59 } 60 61 globalPackagesCache, err := newExtractor.getGlobalPackagesCache() 62 if err != nil { 63 return nil, err 64 } 65 66 err = newExtractor.extract(packagesConfig, globalPackagesCache) 67 return newExtractor, err 68 } 69 70 func (extractor *packagesExtractor) extract(packagesConfig *packagesConfig, globalPackagesCache string) error { 71 for _, nuget := range packagesConfig.XmlPackages { 72 id := strings.ToLower(nuget.Id) 73 nPackage := &nugetPackage{id: id, version: nuget.Version, dependencies: map[string]bool{}} 74 // First lets check if the original version exists within the file system: 75 pack, err := createNugetPackage(globalPackagesCache, nuget, nPackage) 76 if err != nil { 77 return err 78 } 79 if pack == nil { 80 // If doesn't exists lets build the array of alternative versions. 81 alternativeVersions := createAlternativeVersionForms(nuget.Version) 82 // Now lets do a loop to run over the alternative possibilities 83 for i := 0; i < len(alternativeVersions); i++ { 84 nPackage.version = alternativeVersions[i] 85 pack, err = createNugetPackage(globalPackagesCache, nuget, nPackage) 86 if err != nil { 87 return err 88 } 89 if pack != nil { 90 break 91 } 92 } 93 } 94 if pack != nil { 95 extractor.allDependencies[id] = pack.dependency 96 extractor.childrenMap[id] = pack.getDependencies() 97 } else { 98 log.Warn(fmt.Sprintf("The following NuGet package %s with version %s was not found in the NuGet cache %s and therefore not"+ 99 " added to the dependecy tree. This might be because the package already exists in a different NuGet cache,"+ 100 " possibly the SDK cache. Removing the package from this cache may resolve the issue", nuget.Id, nuget.Version, globalPackagesCache)) 101 } 102 } 103 return nil 104 } 105 106 // NuGet allows the version will be with missing or unnecessary zeros 107 // This method will return a list of the possible alternative versions 108 // "1.0" --> []string{"1.0.0.0", "1.0.0", "1"} 109 // "1" --> []string{"1.0.0.0", "1.0.0", "1.0"} 110 // "1.2" --> []string{"1.2.0.0", "1.2.0"} 111 // "1.22.33" --> []string{"1.22.33.0"} 112 // "1.22.33.44" --> []string{} 113 // "1.0.2" --> []string{"1.0.2.0"} 114 func createAlternativeVersionForms(originalVersion string) []string { 115 versionSlice := strings.Split(originalVersion, ".") 116 versionSliceSize := len(versionSlice) 117 for i := 4; i > versionSliceSize; i-- { 118 versionSlice = append(versionSlice, "0") 119 } 120 121 var alternativeVersions []string 122 123 for i := 4; i > 0; i-- { 124 version := strings.Join(versionSlice[:i], ".") 125 if version != originalVersion { 126 alternativeVersions = append(alternativeVersions, version) 127 } 128 if !strings.HasSuffix(version, ".0") { 129 return alternativeVersions 130 } 131 } 132 return alternativeVersions 133 } 134 135 func (extractor *packagesExtractor) loadPackagesConfig(rootPath string) (*packagesConfig, error) { 136 packagesFilePath := filepath.Join(rootPath, packagesFilePath) 137 content, err := ioutil.ReadFile(packagesFilePath) 138 if err != nil { 139 return nil, err 140 } 141 142 config := &packagesConfig{} 143 err = xml.Unmarshal(content, config) 144 if err != nil { 145 return nil, errorutils.CheckError(err) 146 } 147 return config, nil 148 } 149 150 type dfsHelper struct { 151 visited bool 152 notRoot bool 153 circular bool 154 } 155 156 func getDirectDependencies(allDependencies map[string]*buildinfo.Dependency, childrenMap map[string][]string) []string { 157 helper := map[string]*dfsHelper{} 158 for id := range allDependencies { 159 helper[id] = &dfsHelper{} 160 } 161 162 for id := range allDependencies { 163 if helper[id].visited { 164 continue 165 } 166 searchRootDependencies(helper, id, allDependencies, childrenMap, map[string]bool{id: true}) 167 } 168 var rootDependencies []string 169 for id, nodeData := range helper { 170 if !nodeData.notRoot || nodeData.circular { 171 rootDependencies = append(rootDependencies, id) 172 } 173 } 174 175 return rootDependencies 176 } 177 178 func searchRootDependencies(dfsHelper map[string]*dfsHelper, currentId string, allDependencies map[string]*buildinfo.Dependency, childrenMap map[string][]string, traversePath map[string]bool) { 179 if dfsHelper[currentId].visited { 180 return 181 } 182 for _, next := range childrenMap[currentId] { 183 if _, ok := allDependencies[next]; !ok { 184 // No such dependency 185 continue 186 } 187 if traversePath[next] { 188 for circular := range traversePath { 189 dfsHelper[circular].circular = true 190 } 191 continue 192 } 193 194 // Not root dependency 195 dfsHelper[next].notRoot = true 196 traversePath[next] = true 197 searchRootDependencies(dfsHelper, next, allDependencies, childrenMap, traversePath) 198 delete(traversePath, next) 199 } 200 dfsHelper[currentId].visited = true 201 } 202 203 func createNugetPackage(packagesPath string, nuget xmlPackage, nPackage *nugetPackage) (*nugetPackage, error) { 204 nupkgPath := filepath.Join(packagesPath, nPackage.id, nPackage.version, strings.Join([]string{nPackage.id, nPackage.version, "nupkg"}, ".")) 205 206 exists, err := fileutils.IsFileExists(nupkgPath, false) 207 208 if err != nil { 209 return nil, err 210 } 211 212 if !exists { 213 return nil, nil 214 } 215 216 fileDetails, err := fileutils.GetFileDetails(nupkgPath) 217 if err != nil { 218 return nil, err 219 } 220 nPackage.dependency = &buildinfo.Dependency{Id: nuget.Id + ":" + nuget.Version, Checksum: &buildinfo.Checksum{Sha1: fileDetails.Checksum.Sha1, Md5: fileDetails.Checksum.Md5}} 221 222 // Nuspec file that holds the metadata for the package. 223 nuspecPath := filepath.Join(packagesPath, nPackage.id, nPackage.version, strings.Join([]string{nPackage.id, "nuspec"}, ".")) 224 nuspecContent, err := ioutil.ReadFile(nuspecPath) 225 if err != nil { 226 return nil, errorutils.CheckError(err) 227 } 228 229 nuspec := &nuspec{} 230 err = xml.Unmarshal(nuspecContent, nuspec) 231 if err != nil { 232 pack := nPackage.id + ":" + nPackage.version 233 log.Warn("Package:", pack, "couldn't be parsed due to:", err.Error(), ". Skipping the package dependency.") 234 return nPackage, nil 235 } 236 237 for _, dependency := range nuspec.Metadata.Dependencies.Dependencies { 238 nPackage.dependencies[strings.ToLower(dependency.Id)] = true 239 } 240 241 for _, group := range nuspec.Metadata.Dependencies.Groups { 242 for _, dependency := range group.Dependencies { 243 nPackage.dependencies[strings.ToLower(dependency.Id)] = true 244 } 245 } 246 247 return nPackage, nil 248 } 249 250 type nugetPackage struct { 251 id string 252 version string 253 dependency *buildinfo.Dependency 254 dependencies map[string]bool // Set of dependencies 255 } 256 257 func (nugetPackage *nugetPackage) getDependencies() []string { 258 var dependencies []string 259 for key := range nugetPackage.dependencies { 260 dependencies = append(dependencies, key) 261 } 262 263 return dependencies 264 } 265 266 func (extractor *packagesExtractor) getGlobalPackagesCache() (string, error) { 267 localsCmd, err := nuget.NewNugetCmd() 268 if err != nil { 269 return "", err 270 } 271 //nuget locals global-packages -list 272 localsCmd.Command = []string{"locals", "global-packages"} 273 localsCmd.CommandFlags = []string{"-list"} 274 275 output, err := gofrogcmd.RunCmdOutput(localsCmd) 276 if err != nil { 277 return "", err 278 } 279 280 globalPackagesPath := strings.TrimSpace(strings.TrimPrefix(output, "global-packages:")) 281 exists, err := fileutils.IsDirExists(globalPackagesPath, false) 282 if err != nil { 283 return "", err 284 } 285 if !exists { 286 return "", errorutils.CheckError(fmt.Errorf("Could not find global packages path at: %s", globalPackagesPath)) 287 } 288 return globalPackagesPath, nil 289 } 290 291 // packages.config xml objects for unmarshalling 292 type packagesConfig struct { 293 XMLName xml.Name `xml:"packages"` 294 XmlPackages []xmlPackage `xml:"package"` 295 } 296 297 type xmlPackage struct { 298 Id string `xml:"id,attr"` 299 Version string `xml:"version,attr"` 300 } 301 302 type nuspec struct { 303 XMLName xml.Name `xml:"package"` 304 Metadata metadata `xml:"metadata"` 305 } 306 307 type metadata struct { 308 Dependencies xmlDependencies `xml:"dependencies"` 309 } 310 311 type xmlDependencies struct { 312 Groups []group `xml:"group"` 313 Dependencies []xmlPackage `xml:"dependency"` 314 } 315 316 type group struct { 317 TargetFramework string `xml:"targetFramework,attr"` 318 Dependencies []xmlPackage `xml:"dependency"` 319 }