github.com/jenkins-x/jx/v2@v2.1.155/pkg/gits/operations/pull_request_op.go (about) 1 package operations 2 3 import ( 4 "bytes" 5 "fmt" 6 "time" 7 8 "github.com/jenkins-x/jx/v2/pkg/helm" 9 "github.com/jenkins-x/jx/v2/pkg/secreturl" 10 11 "github.com/jenkins-x/jx/v2/pkg/cmd/opts" 12 13 "io" 14 "io/ioutil" 15 "net/http" 16 "os" 17 "path/filepath" 18 "regexp" 19 "sort" 20 "strings" 21 22 "github.com/jenkins-x/jx-logging/pkg/log" 23 "github.com/jenkins-x/jx/v2/pkg/dependencymatrix" 24 "github.com/jenkins-x/jx/v2/pkg/gits" 25 "github.com/jenkins-x/jx/v2/pkg/gits/releases" 26 27 "github.com/jenkins-x/jx/v2/pkg/versionstream" 28 29 "github.com/blang/semver" 30 "github.com/ghodss/yaml" 31 v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1" 32 33 "github.com/jenkins-x/jx/v2/pkg/util" 34 "github.com/pkg/errors" 35 "k8s.io/apimachinery/pkg/util/uuid" 36 ) 37 38 const ( 39 // assetRetrievalTimeout controls the amount of time to wait when attempting to retrieve dependency information 40 assetRetrievalTimeout = 3 * time.Minute 41 ) 42 43 // PullRequestOperation provides a way to execute a PullRequest operation using Git 44 type PullRequestOperation struct { 45 *opts.CommonOptions 46 GitURLs []string 47 SrcGitURL string 48 Base string 49 Component string 50 BranchName string 51 Version string 52 DryRun bool 53 SkipCommit bool 54 AuthorName string 55 AuthorEmail string 56 SkipAutoMerge bool 57 Labels []string 58 } 59 60 // ChangeFilesFn is the function called to create the pull request 61 type ChangeFilesFn func(dir string, gitInfo *gits.GitRepository) ([]string, error) 62 63 // CreatePullRequest will fork (if needed) and pull a git repo, then perform the update, and finally create or update a 64 // PR for the change. Any open PR on the repo with the `updatebot` label will be updated. 65 func (o *PullRequestOperation) CreatePullRequest(kind string, update ChangeFilesFn) (*gits.PullRequestInfo, error) { 66 var result *gits.PullRequestInfo 67 for _, gitURL := range o.GitURLs { 68 dir, err := ioutil.TempDir("", "create-pr") 69 if err != nil { 70 return nil, err 71 } 72 provider, _, err := o.CreateGitProviderForURLWithoutKind(gitURL) 73 if err != nil { 74 return nil, errors.Wrapf(err, "creating git provider for directory %s", dir) 75 } 76 77 dir, _, upstreamInfo, forkInfo, err := gits.ForkAndPullRepo(gitURL, dir, o.Base, o.BranchName, provider, o.Git(), "") 78 if err != nil { 79 return nil, errors.Wrapf(err, "failed to fork and pull %s", o.GitURLs) 80 } 81 82 gitInfo := upstreamInfo 83 if forkInfo != nil { 84 gitInfo = forkInfo 85 } 86 commitMessage, details, err := o.updateAndGenerateMessagesAndDependencyMatrix(dir, kind, upstreamInfo.Host, gitInfo, update) 87 if err != nil { 88 return nil, errors.WithStack(err) 89 } 90 91 labels := []string{} 92 if !o.SkipAutoMerge { 93 labels = append(labels, "updatebot") 94 } 95 if len(o.Labels) > 0 { 96 labels = append(labels, o.Labels...) 97 } 98 filter := &gits.PullRequestFilter{ 99 Labels: labels, 100 } 101 102 details.Labels = labels 103 result, err = gits.PushRepoAndCreatePullRequest(dir, upstreamInfo, forkInfo, o.Base, details, filter, !o.SkipCommit, commitMessage, true, o.DryRun, o.Git(), provider) 104 if err != nil { 105 return nil, errors.Wrapf(err, "failed to create PR for base %s and head branch %s from temp dir %s", o.Base, details.BranchName, dir) 106 } 107 } 108 return result, nil 109 } 110 111 // WrapChangeFilesWithCommitFn wraps the passed ChangeFilesFn in a commit. This is useful for creating multiple commits 112 // to batch e.g. in a single PR push/creation 113 func (o *PullRequestOperation) WrapChangeFilesWithCommitFn(kind string, fn ChangeFilesFn) ChangeFilesFn { 114 return func(dir string, gitInfo *gits.GitRepository) ([]string, error) { 115 commitMessage, prDetails, err := o.updateAndGenerateMessagesAndDependencyMatrix(dir, kind, gitInfo.Host, gitInfo, fn) 116 if err != nil { 117 return nil, errors.WithStack(err) 118 } 119 err = o.Git().Add(dir, "-A") 120 if err != nil { 121 return nil, errors.WithStack(err) 122 } 123 changed, err := o.Git().HasChanges(dir) 124 if err != nil { 125 return nil, errors.WithStack(err) 126 } 127 if !changed { 128 log.Logger().Warnf("No changes made to the source code in %s. Code must be up to date!", dir) 129 return nil, nil 130 } 131 if commitMessage == "" { 132 commitMessage = prDetails.Message 133 } 134 err = o.Git().CommitDir(dir, commitMessage) 135 if err != nil { 136 return nil, errors.WithStack(err) 137 } 138 return nil, nil 139 } 140 } 141 142 func (o *PullRequestOperation) updateAndGenerateMessagesAndDependencyMatrix(dir string, kind string, destHost string, gitInfo *gits.GitRepository, update ChangeFilesFn) (string, *gits.PullRequestDetails, error) { 143 144 oldVersions, err := update(dir, gitInfo) 145 if err != nil { 146 return "", nil, errors.WithStack(err) 147 } 148 nonSemantic := make([]string, 0) 149 semantic := make([]semver.Version, 0) 150 for _, v := range oldVersions { 151 sv, err := semver.Parse(v) 152 if err != nil { 153 nonSemantic = append(nonSemantic, v) 154 } else { 155 semantic = append(semantic, sv) 156 } 157 } 158 semver.Sort(semantic) 159 sort.Strings(nonSemantic) 160 dedupedSemantic := make([]string, 0) 161 dedupedNonSemantic := make([]string, 0) 162 previous := "" 163 for _, v := range nonSemantic { 164 if v != previous { 165 dedupedNonSemantic = append(dedupedNonSemantic, v) 166 } 167 previous = v 168 } 169 previous = "" 170 for _, v := range semantic { 171 vStr := v.String() 172 if vStr != previous { 173 dedupedSemantic = append(dedupedSemantic, vStr) 174 } 175 previous = vStr 176 } 177 178 oldVersionsStr := strings.Join(dedupedSemantic, ", ") 179 if len(dedupedNonSemantic) > 0 { 180 if oldVersionsStr != "" { 181 oldVersionsStr += " and " 182 } 183 oldVersionsStr = oldVersionsStr + strings.Join(dedupedNonSemantic, ", ") 184 } 185 186 // remove the v prefix if we are using a v tag 187 version := strings.TrimPrefix(o.Version, "v") 188 commitMessage, details, updateDependency, assets, err := o.CreateDependencyUpdatePRDetails(kind, o.SrcGitURL, destHost, oldVersionsStr, version, o.Component) 189 if err != nil { 190 return "", nil, errors.WithStack(err) 191 } 192 193 var upstreamDependencyAsset *gits.GitReleaseAsset 194 195 for _, a := range assets { 196 asset := a 197 if asset.Name == dependencymatrix.DependencyUpdatesAssetName { 198 upstreamDependencyAsset = &asset 199 break 200 } 201 } 202 203 if updateDependency != nil { 204 err = dependencymatrix.UpdateDependencyMatrix(dir, updateDependency) 205 if err != nil { 206 return "", nil, errors.WithStack(err) 207 } 208 } 209 210 if upstreamDependencyAsset != nil { 211 updatedPaths, err := AddDependencyMatrixUpdatePaths(upstreamDependencyAsset, updateDependency) 212 if err != nil { 213 log.Logger().Warnf("adding dependency updates: %q", err) 214 } else { 215 for _, d := range updatedPaths { 216 err = dependencymatrix.UpdateDependencyMatrix(dir, d) 217 if err != nil { 218 return "", nil, errors.Wrapf(err, "updating dependency matrix with upstream dependency %+v", d) 219 } 220 } 221 } 222 } 223 return commitMessage, details, nil 224 } 225 226 // AddDependencyMatrixUpdatePaths retrieves the upstreamDependencyAsset and converts it to a slice of DependencyUpdates, prepending the updateDependency to the path 227 func AddDependencyMatrixUpdatePaths(upstreamDependencyAsset *gits.GitReleaseAsset, updateDependency *v1.DependencyUpdate) ([]*v1.DependencyUpdate, error) { 228 var upstreamUpdates dependencymatrix.DependencyUpdates 229 var updates []*v1.DependencyUpdate 230 231 log.Logger().Infof("Attempting to retrieve dependency asset information from %q", upstreamDependencyAsset.BrowserDownloadURL) 232 233 err := util.Retry(assetRetrievalTimeout, func() error { 234 resp, err := http.Get(upstreamDependencyAsset.BrowserDownloadURL) 235 if err != nil { 236 return errors.Wrapf(err, "retrieving dependency updates from %s", upstreamDependencyAsset.BrowserDownloadURL) 237 } 238 defer resp.Body.Close() 239 // Write the body 240 var b bytes.Buffer 241 _, err = io.Copy(&b, resp.Body) 242 if err != nil { 243 return errors.Wrapf(err, "copying response body after retrieving dependency updates from %s", upstreamDependencyAsset.BrowserDownloadURL) 244 } 245 bytes := b.Bytes() 246 err = yaml.Unmarshal(bytes, &upstreamUpdates) 247 if err != nil { 248 log.Logger().Errorf("unable to unmarshall from %s", string(bytes)) 249 return errors.Wrapf(err, "unmarshaling dependency updates from %s", upstreamDependencyAsset.BrowserDownloadURL) 250 } 251 return nil 252 }) 253 254 if err != nil { 255 return nil, errors.Wrapf(err, "getting dependency updates from %s", upstreamDependencyAsset.BrowserDownloadURL) 256 } 257 258 for _, d := range upstreamUpdates.Updates { 259 // Need to prepend a path element 260 if d.Paths == nil { 261 d.Paths = []v1.DependencyUpdatePath{ 262 { 263 { 264 Host: updateDependency.Host, 265 Owner: updateDependency.Owner, 266 Repo: updateDependency.Repo, 267 URL: updateDependency.URL, 268 Component: updateDependency.Component, 269 ToReleaseHTMLURL: updateDependency.ToReleaseHTMLURL, 270 ToVersion: updateDependency.ToVersion, 271 FromVersion: updateDependency.FromVersion, 272 FromReleaseName: updateDependency.FromReleaseName, 273 FromReleaseHTMLURL: updateDependency.FromReleaseHTMLURL, 274 ToReleaseName: updateDependency.ToReleaseName, 275 }, 276 }, 277 } 278 } else { 279 for j, e := range d.Paths { 280 if e == nil { 281 d.Paths[j] = v1.DependencyUpdatePath{ 282 { 283 Host: updateDependency.Host, 284 Owner: updateDependency.Owner, 285 Repo: updateDependency.Repo, 286 URL: updateDependency.URL, 287 Component: updateDependency.Component, 288 ToReleaseHTMLURL: updateDependency.ToReleaseHTMLURL, 289 ToVersion: updateDependency.ToVersion, 290 FromVersion: updateDependency.FromVersion, 291 FromReleaseName: updateDependency.FromReleaseName, 292 FromReleaseHTMLURL: updateDependency.FromReleaseHTMLURL, 293 ToReleaseName: updateDependency.ToReleaseName, 294 }, 295 } 296 } 297 d.Paths[j] = append([]v1.DependencyUpdateDetails{ 298 { 299 Host: updateDependency.Host, 300 Owner: updateDependency.Owner, 301 Repo: updateDependency.Repo, 302 URL: updateDependency.URL, 303 Component: updateDependency.Component, 304 ToReleaseHTMLURL: updateDependency.ToReleaseHTMLURL, 305 ToVersion: updateDependency.ToVersion, 306 FromVersion: updateDependency.FromVersion, 307 FromReleaseName: updateDependency.FromReleaseName, 308 FromReleaseHTMLURL: updateDependency.FromReleaseHTMLURL, 309 ToReleaseName: updateDependency.ToReleaseName, 310 }, 311 }, e...) 312 } 313 } 314 update := d 315 updates = append(updates, &update) 316 } 317 return updates, nil 318 } 319 320 // CreateDependencyUpdatePRDetails creates the PullRequestDetails for a pull request, taking the kind of change it is (an id) 321 // the srcRepoUrl for the repo that caused the change, the destRepo for the repo receiving the change, the fromVersion and the toVersion 322 func (o PullRequestOperation) CreateDependencyUpdatePRDetails(kind string, srcRepoURL string, destHost string, fromVersion string, toVersion string, component string) (string, *gits.PullRequestDetails, *v1.DependencyUpdate, []gits.GitReleaseAsset, error) { 323 var commitMessage, title, message strings.Builder 324 commitMessage.WriteString("chore(deps): bump ") 325 title.WriteString("chore(deps): bump ") 326 message.WriteString("Update ") 327 var update *v1.DependencyUpdate 328 var assets []gits.GitReleaseAsset 329 330 if srcRepoURL != "" { 331 provider, srcRepo, err := o.CreateGitProviderForURLWithoutKind(srcRepoURL) 332 if err != nil { 333 return "", nil, nil, nil, errors.Wrapf(err, "creating git provider for %s", srcRepoURL) 334 } 335 update = &v1.DependencyUpdate{ 336 DependencyUpdateDetails: v1.DependencyUpdateDetails{ 337 Owner: srcRepo.Organisation, 338 Repo: srcRepo.Name, 339 URL: srcRepoURL, 340 }, 341 } 342 if srcRepo.Host != destHost { 343 commitMessage.WriteString(srcRepoURL) 344 title.WriteString(srcRepoURL) 345 update.Host = srcRepo.Host 346 } else { 347 titleStr := fmt.Sprintf("%s/%s", srcRepo.Organisation, srcRepo.Name) 348 commitMessage.WriteString(titleStr) 349 title.WriteString(titleStr) 350 update.Host = destHost 351 } 352 repoStr := fmt.Sprintf("[%s/%s](%s)", srcRepo.Organisation, srcRepo.Name, srcRepoURL) 353 message.WriteString(repoStr) 354 355 if component != "" { 356 componentStr := fmt.Sprintf(":%s", component) 357 commitMessage.WriteString(componentStr) 358 title.WriteString(componentStr) 359 message.WriteString(componentStr) 360 update.Component = component 361 } 362 commitMessage.WriteString(" ") 363 title.WriteString(" ") 364 message.WriteString(" ") 365 366 if fromVersion != "" { 367 fromText := fmt.Sprintf("from %s ", fromVersion) 368 commitMessage.WriteString(fromText) 369 title.WriteString(fromText) 370 update.FromVersion = fromVersion 371 release, err := releases.GetRelease(fromVersion, srcRepo.Organisation, srcRepo.Name, provider) 372 if err != nil { 373 return "", nil, nil, nil, errors.Wrapf(err, "getting release from %s/%s", srcRepo.Organisation, srcRepo.Name) 374 } 375 if release != nil { 376 message.WriteString(fmt.Sprintf("from [%s](%s) ", fromVersion, release.HTMLURL)) 377 update.FromReleaseName = release.Name 378 update.FromReleaseHTMLURL = release.HTMLURL 379 } else { 380 message.WriteString(fmt.Sprintf("from %s ", fromVersion)) 381 } 382 } 383 if toVersion != "" { 384 toText := fmt.Sprintf("to %s", toVersion) 385 commitMessage.WriteString(toText) 386 title.WriteString(toText) 387 update.ToVersion = toVersion 388 release, err := releases.GetRelease(toVersion, srcRepo.Organisation, srcRepo.Name, provider) 389 if err != nil { 390 return "", nil, nil, nil, errors.Wrapf(err, "getting release from %s/%s", srcRepo.Organisation, srcRepo.Name) 391 } 392 if release != nil { 393 message.WriteString(fmt.Sprintf("to [%s](%s)", toVersion, release.HTMLURL)) 394 update.ToReleaseHTMLURL = release.HTMLURL 395 update.ToReleaseName = release.Name 396 if release.Assets != nil { 397 assets = *release.Assets 398 } 399 } else { 400 message.WriteString(fmt.Sprintf("to %s", toVersion)) 401 } 402 } 403 } else { 404 commitMessage.WriteString(" versions") 405 title.WriteString(" versions") 406 message.WriteString(" versions") 407 } 408 message.WriteString(fmt.Sprintf("\n\nCommand run was `%s`", strings.Join(os.Args, " "))) 409 commitMessage.WriteString(fmt.Sprintf("\n\nCommand run was `%s`", strings.Join(os.Args, " "))) 410 if o.AuthorEmail != "" && o.AuthorName != "" { 411 commitMessage.WriteString(fmt.Sprintf("\n\nSigned-off-by: %s <%s>", o.AuthorName, o.AuthorEmail)) 412 } 413 return commitMessage.String(), &gits.PullRequestDetails{ 414 BranchName: fmt.Sprintf("bump-%s-version-%s", kind, string(uuid.NewUUID())), 415 Title: title.String(), 416 Message: message.String(), 417 }, update, assets, nil 418 } 419 420 // CreatePullRequestRegexFn creates the ChangeFilesFn that will apply the regex, updating the matches to version over the files 421 func CreatePullRequestRegexFn(version string, regex string, files ...string) (ChangeFilesFn, error) { 422 r, err := regexp.Compile(regex) 423 if err != nil { 424 return nil, errors.Wrapf(err, "%s does not compile", regex) 425 } 426 namedCaptures := make([]bool, 0) 427 namedCapture := false 428 for i, n := range r.SubexpNames() { 429 if i == 0 { 430 continue 431 } else if n == "version" { 432 namedCaptures = append(namedCaptures, true) 433 namedCapture = true 434 } else { 435 namedCaptures = append(namedCaptures, false) 436 } 437 } 438 return func(dir string, gitInfo *gits.GitRepository) ([]string, error) { 439 oldVersions := make([]string, 0) 440 for _, glob := range files { 441 442 matches, err := filepath.Glob(filepath.Join(dir, glob)) 443 if err != nil { 444 return nil, errors.Wrapf(err, "applying glob %s", glob) 445 } 446 447 // iterate over the glob matches 448 for _, path := range matches { 449 450 data, err := ioutil.ReadFile(path) 451 if err != nil { 452 return nil, errors.Wrapf(err, "reading %s", path) 453 } 454 info, err := os.Stat(path) 455 if err != nil { 456 return nil, errors.WithStack(err) 457 } 458 s := string(data) 459 for _, b := range namedCaptures { 460 if b { 461 namedCapture = true 462 } 463 } 464 answer := util.ReplaceAllStringSubmatchFunc(r, s, func(groups []util.Group) []string { 465 answer := make([]string, 0) 466 for i, group := range groups { 467 if namedCapture { 468 // If we are using named capture, then replace only the named captures that have the right name 469 if namedCaptures[i] { 470 oldVersions = append(oldVersions, group.Value) 471 answer = append(answer, version) 472 } else { 473 answer = append(answer, group.Value) 474 } 475 } else { 476 oldVersions = append(oldVersions, group.Value) 477 answer = append(answer, version) 478 } 479 480 } 481 return answer 482 }) 483 err = ioutil.WriteFile(path, []byte(answer), info.Mode()) 484 if err != nil { 485 return nil, errors.Wrapf(err, "writing %s", path) 486 } 487 } 488 if err != nil { 489 return nil, errors.WithStack(err) 490 } 491 } 492 return oldVersions, nil 493 }, nil 494 } 495 496 // CreatePullRequestBuildersFn creates the ChangeFilesFn that will update the gcr.io/jenkinsxio/builder-*.yml images 497 func CreatePullRequestBuildersFn(version string) ChangeFilesFn { 498 return func(dir string, gitInfo *gits.GitRepository) ([]string, error) { 499 answer, err := versionstream.UpdateStableVersionFiles(filepath.Join(dir, string(versionstream.KindDocker), "gcr.io", "jenkinsxio", "builder-*.yml"), version, "builder-base.yml", "builder-machine-learning.yml", "builder-machine-learning-gpu.yml") 500 if err != nil { 501 return nil, errors.Wrap(err, "modifying the builder-*.yml image versions") 502 } 503 return answer, nil 504 } 505 } 506 507 // CreatePullRequestMLBuildersFn creates the ChangeFilesFn that will update the gcr.io/jenkinsxio/builder-machine-learning*.yml images 508 func CreatePullRequestMLBuildersFn(version string) ChangeFilesFn { 509 return func(dir string, gitInfo *gits.GitRepository) ([]string, error) { 510 answer, err := versionstream.UpdateStableVersionFiles(filepath.Join(dir, string(versionstream.KindDocker), "gcr.io", "jenkinsxio", "builder-machine-learning*.yml"), version) 511 if err != nil { 512 return nil, errors.Wrap(err, "modifying the builder-machine-learning*.yml image versions") 513 } 514 return answer, nil 515 } 516 } 517 518 // CreatePullRequestGitReleasesFn creates the ChangeFilesFn that will update the git/ directory in the versions repo, using the git provider release api 519 func (o *PullRequestOperation) CreatePullRequestGitReleasesFn(name string) ChangeFilesFn { 520 return func(dir string, gitInfo *gits.GitRepository) ([]string, error) { 521 u := fmt.Sprintf("https://%s.git", name) 522 provider, gitInfo, err := o.CreateGitProviderForURLWithoutKind(u) 523 if err != nil { 524 return nil, errors.Wrapf(err, "creating git provider for %s", u) 525 } 526 release, err := provider.GetLatestRelease(gitInfo.Organisation, gitInfo.Name) 527 if err != nil { 528 return nil, errors.Wrapf(err, "failed to find latest version for %s", u) 529 } 530 531 version := strings.TrimPrefix(release.Name, "v") 532 log.Logger().Infof("found latest version %s for git repo %s", util.ColorInfo(version), util.ColorInfo(u)) 533 o.Version = version 534 if o.SrcGitURL == "" { 535 sv, err := versionstream.LoadStableVersion(dir, versionstream.KindGit, name) 536 if err != nil { 537 return nil, errors.Wrapf(err, "loading stable version") 538 } 539 o.SrcGitURL = sv.GitURL 540 if sv.Component != "" { 541 o.Component = sv.Component 542 } 543 } 544 oldVersions, err := versionstream.UpdateStableVersion(dir, string(versionstream.KindGit), name, version) 545 if err != nil { 546 return nil, errors.Wrapf(err, "updating version %s to %s", u, version) 547 } 548 return oldVersions, nil 549 550 } 551 } 552 553 // CreateChartChangeFilesFn creates the ChangeFilesFn for updating the chart with name to version. If the version is 554 // empty it will fetch the latest version using helmer, using the vaultClient to get the repo creds or prompting using 555 // in, out and outErr 556 func CreateChartChangeFilesFn(name string, version string, kind string, pro *PullRequestOperation, helmer helm.Helmer, 557 vaultClient secreturl.Client, handles util.IOFileHandles) ChangeFilesFn { 558 return func(dir string, gitInfo *gits.GitRepository) ([]string, error) { 559 if version == "" && kind == string(versionstream.KindChart) { 560 parts := strings.Split(name, "/") 561 searchName := name 562 if len(parts) == 2 { 563 prefixes, err := versionstream.GetRepositoryPrefixes(dir) 564 if err != nil { 565 return nil, errors.Wrapf(err, "getting repository prefixes") 566 } 567 prefix := parts[0] 568 urls := prefixes.URLsForPrefix(prefix) 569 if len(urls) > 0 { 570 if len(urls) > 1 { 571 log.Logger().Warnf("helm repo %s has more than one url %+v, using first declared (%s)", prefix, urls, urls[0]) 572 } else if len(urls) == 0 { 573 log.Logger().Warnf("helm repo %s has more than no urls, not adding", prefix) 574 } 575 prefix, err = helm.AddHelmRepoIfMissing(urls[0], prefix, "", "", helmer, vaultClient, handles) 576 if err != nil { 577 return nil, errors.Wrapf(err, "adding repository %s with url %s", prefix, urls[0]) 578 } 579 } 580 searchName = fmt.Sprintf("%s/%s", prefix, parts[1]) 581 } 582 c, err := helm.FindLatestChart(searchName, helmer) 583 if err != nil { 584 return nil, errors.Wrapf(err, "failed to find latest chart version for %s", name) 585 } 586 version = c.ChartVersion 587 log.Logger().Infof("found latest version %s for chart %s\n", util.ColorInfo(version), util.ColorInfo(name)) 588 } 589 pro.Version = version 590 if pro.SrcGitURL == "" { 591 sv, err := versionstream.LoadStableVersion(dir, versionstream.VersionKind(kind), name) 592 if err != nil { 593 return nil, errors.Wrapf(err, "loading stable version") 594 } 595 pro.SrcGitURL = sv.GitURL 596 if sv.Component != "" { 597 pro.Component = sv.Component 598 } 599 } 600 if pro.SrcGitURL == "" { 601 err := helm.InspectChart(name, version, "", "", "", helmer, func(dir string) error { 602 fileName, err := helm.FindChartFileName(dir) 603 if err != nil { 604 return errors.Wrapf(err, "find chart file") 605 } 606 chart, err := helm.LoadChartFile(fileName) 607 if err != nil { 608 return errors.Wrapf(err, "loading chart file") 609 } 610 if len(chart.Sources) > 0 { 611 pro.SrcGitURL = chart.Sources[0] 612 } else { 613 return errors.Errorf("chart %s %s has no sources in Chart.yaml", name, version) 614 } 615 return nil 616 }) 617 if err != nil { 618 return nil, errors.Wrapf(err, "failed to find source repo for %s", name) 619 } 620 } 621 if pro.SrcGitURL == "" { 622 return nil, errors.Errorf("Unable to determine git url for dependency %s", name) 623 } 624 answer, err := versionstream.UpdateStableVersion(dir, kind, name, version) 625 if err != nil { 626 return nil, errors.WithStack(err) 627 } 628 return answer, nil 629 } 630 }