2016-09-13 15:29:58 -04:00
/ *
2018-08-24 15:03:55 -04:00
Copyright The Helm Authors .
2016-09-13 15:29:58 -04:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package resolver
import (
"bytes"
"encoding/json"
2022-01-11 22:05:28 -05:00
"fmt"
2017-02-09 14:43:18 -05:00
"os"
"path/filepath"
2016-11-15 18:50:19 -05:00
"strings"
2016-09-13 15:29:58 -04:00
"time"
2020-10-05 13:20:04 -04:00
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
2019-10-03 14:27:05 -04:00
"helm.sh/helm/v3/pkg/chart"
2020-07-16 11:03:08 -04:00
"helm.sh/helm/v3/pkg/chart/loader"
2019-10-03 14:27:05 -04:00
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance"
2021-12-08 06:23:04 -05:00
"helm.sh/helm/v3/pkg/registry"
2019-10-03 14:27:05 -04:00
"helm.sh/helm/v3/pkg/repo"
2016-09-13 15:29:58 -04:00
)
// Resolver resolves dependencies from semantic version ranges to a particular version.
type Resolver struct {
2022-01-08 23:10:21 -05:00
chartpath string
cachepath string
registryClient * registry . Client
2016-09-13 15:29:58 -04:00
}
2022-01-08 23:10:21 -05:00
// New creates a new resolver for a given chart, helm home and registry client.
func New ( chartpath , cachepath string , registryClient * registry . Client ) * Resolver {
2016-09-13 15:29:58 -04:00
return & Resolver {
2022-01-08 23:10:21 -05:00
chartpath : chartpath ,
cachepath : cachepath ,
registryClient : registryClient ,
2016-09-13 15:29:58 -04:00
}
}
// Resolve resolves dependencies and returns a lock file with the resolution.
2024-09-12 10:21:14 -04:00
func ( r * Resolver ) Resolve ( reqs [ ] * chart . Dependency , repoNames map [ string ] string ) ( * chart . Lock , error ) {
2016-09-13 15:29:58 -04:00
// Now we clone the dependencies, locking as we go.
2018-08-29 17:05:37 -04:00
locked := make ( [ ] * chart . Dependency , len ( reqs ) )
2016-11-15 18:50:19 -05:00
missing := [ ] string { }
2018-08-29 17:05:37 -04:00
for i , d := range reqs {
2021-05-25 09:37:30 -04:00
constraint , err := semver . NewConstraint ( d . Version )
if err != nil {
2024-09-12 10:21:14 -04:00
return nil , errors . Wrapf ( err , "dependency %q has an invalid version/constraint format" , d . Name )
2021-05-25 09:37:30 -04:00
}
2019-10-09 11:35:55 -04:00
if d . Repository == "" {
// Local chart subfolder
if _ , err := GetLocalPath ( filepath . Join ( "charts" , d . Name ) , r . chartpath ) ; err != nil {
2024-09-12 10:21:14 -04:00
return nil , err
2019-10-09 11:35:55 -04:00
}
locked [ i ] = & chart . Dependency {
Name : d . Name ,
Repository : "" ,
Version : d . Version ,
}
continue
}
2017-02-09 14:43:18 -05:00
if strings . HasPrefix ( d . Repository , "file://" ) {
2020-07-16 11:03:08 -04:00
chartpath , err := GetLocalPath ( d . Repository , r . chartpath )
if err != nil {
2024-09-12 10:21:14 -04:00
return nil , err
2020-07-16 11:03:08 -04:00
}
ch , err := loader . LoadDir ( chartpath )
if err != nil {
2024-09-12 10:21:14 -04:00
return nil , err
2017-02-09 14:43:18 -05:00
}
2021-05-25 09:37:30 -04:00
v , err := semver . NewVersion ( ch . Metadata . Version )
if err != nil {
// Not a legit entry.
continue
}
if ! constraint . Check ( v ) {
2024-04-10 06:04:26 -04:00
missing = append ( missing , fmt . Sprintf ( "%q (repository %q, version %q)" , d . Name , d . Repository , d . Version ) )
2021-05-25 09:37:30 -04:00
continue
}
2018-08-24 14:28:29 -04:00
locked [ i ] = & chart . Dependency {
2017-02-09 14:43:18 -05:00
Name : d . Name ,
Repository : d . Repository ,
2020-07-16 11:03:08 -04:00
Version : ch . Metadata . Version ,
2017-02-09 14:43:18 -05:00
}
continue
}
2020-10-01 17:37:44 -04:00
2019-09-24 13:03:30 -04:00
repoName := repoNames [ d . Name ]
// if the repository was not defined, but the dependency defines a repository url, bypass the cache
if repoName == "" && d . Repository != "" {
locked [ i ] = & chart . Dependency {
Name : d . Name ,
Repository : d . Repository ,
Version : d . Version ,
}
continue
}
2019-08-23 02:31:50 -04:00
2020-10-01 17:37:44 -04:00
var vs repo . ChartVersions
var version string
var ok bool
found := true
2021-06-28 12:43:33 -04:00
if ! registry . IsOCI ( d . Repository ) {
2024-09-12 10:21:14 -04:00
repoIndex , err := repo . LoadIndexFile ( filepath . Join ( r . cachepath , helmpath . CacheIndexFile ( repoName ) ) )
if err != nil {
return nil , errors . Wrapf ( err , "no cached repository for %s found. (try 'helm repo update')" , repoName )
2020-10-01 17:37:44 -04:00
}
2016-11-15 18:50:19 -05:00
2020-10-01 17:37:44 -04:00
vs , ok = repoIndex . Entries [ d . Name ]
if ! ok {
2024-09-12 10:21:14 -04:00
return nil , errors . Errorf ( "%s chart not found in repo %s" , d . Name , d . Repository )
2020-10-01 17:37:44 -04:00
}
found = false
} else {
version = d . Version
2022-01-09 15:47:08 -05:00
2022-01-30 16:31:41 -05:00
// Check to see if an explicit version has been provided
_ , err := semver . NewVersion ( version )
// Use an explicit version, otherwise search for tags
if err == nil {
vs = [ ] * repo . ChartVersion { {
2022-01-07 03:51:52 -05:00
Metadata : & chart . Metadata {
2022-01-30 16:31:41 -05:00
Version : version ,
2022-01-07 03:51:52 -05:00
} ,
2022-01-30 16:31:41 -05:00
} }
} else {
// Retrieve list of tags for repository
ref := fmt . Sprintf ( "%s/%s" , strings . TrimPrefix ( d . Repository , fmt . Sprintf ( "%s://" , registry . OCIScheme ) ) , d . Name )
tags , err := r . registryClient . Tags ( ref )
if err != nil {
2024-09-12 10:21:14 -04:00
return nil , errors . Wrapf ( err , "could not retrieve list of tags for repository %s" , d . Repository )
2022-01-30 16:31:41 -05:00
}
vs = make ( repo . ChartVersions , len ( tags ) )
for ti , t := range tags {
// Mock chart version objects
version := & repo . ChartVersion {
Metadata : & chart . Metadata {
Version : t ,
} ,
}
vs [ ti ] = version
2022-01-07 03:51:52 -05:00
}
}
2016-11-15 18:50:19 -05:00
}
2018-08-24 14:28:29 -04:00
locked [ i ] = & chart . Dependency {
2016-09-13 15:29:58 -04:00
Name : d . Name ,
Repository : d . Repository ,
2020-10-01 17:37:44 -04:00
Version : version ,
2016-09-13 15:29:58 -04:00
}
2024-09-12 17:33:46 -04:00
// The versions are already sorted and hence the first one to satisfy the constraint is used
2016-11-15 18:50:19 -05:00
for _ , ver := range vs {
v , err := semver . NewVersion ( ver . Version )
2022-01-07 03:51:52 -05:00
// OCI does not need URLs
if err != nil || ( ! registry . IsOCI ( d . Repository ) && len ( ver . URLs ) == 0 ) {
2016-11-15 18:50:19 -05:00
// Not a legit entry.
continue
}
if constraint . Check ( v ) {
found = true
locked [ i ] . Version = v . Original ( )
break
}
}
2016-09-13 15:29:58 -04:00
2016-11-15 18:50:19 -05:00
if ! found {
2024-04-10 06:04:26 -04:00
missing = append ( missing , fmt . Sprintf ( "%q (repository %q, version %q)" , d . Name , d . Repository , d . Version ) )
2016-11-15 18:50:19 -05:00
}
}
if len ( missing ) > 0 {
2024-09-12 10:21:14 -04:00
return nil , errors . Errorf ( "can't get a valid version for %d subchart(s): %s. Make sure a matching chart version exists in the repo, or change the version constraint in Chart.yaml" , len ( missing ) , strings . Join ( missing , ", " ) )
2016-11-15 18:50:19 -05:00
}
2019-06-11 19:09:21 -04:00
2019-10-21 23:38:49 -04:00
digest , err := HashReq ( reqs , locked )
2019-06-11 19:09:21 -04:00
if err != nil {
2024-09-12 10:21:14 -04:00
return nil , err
2019-06-11 19:09:21 -04:00
}
2018-08-29 17:43:37 -04:00
return & chart . Lock {
2016-09-13 15:29:58 -04:00
Generated : time . Now ( ) ,
2019-06-11 19:09:21 -04:00
Digest : digest ,
2016-09-13 15:29:58 -04:00
Dependencies : locked ,
2024-09-12 10:21:14 -04:00
} , nil
2016-09-13 15:29:58 -04:00
}
2018-11-28 13:20:33 -05:00
// HashReq generates a hash of the dependencies.
2016-09-13 15:29:58 -04:00
//
// This should be used only to compare against another hash generated by this
// function.
2019-10-21 23:38:49 -04:00
func HashReq ( req , lock [ ] * chart . Dependency ) ( string , error ) {
data , err := json . Marshal ( [ 2 ] [ ] * chart . Dependency { req , lock } )
2016-09-13 15:29:58 -04:00
if err != nil {
return "" , err
}
s , err := provenance . Digest ( bytes . NewBuffer ( data ) )
return "sha256:" + s , err
}
2017-03-27 20:31:01 -04:00
2020-02-05 03:38:30 -05:00
// HashV2Req generates a hash of requirements generated in Helm v2.
//
// This should be used only to compare against another hash generated by the
// Helm v2 hash function. It is to handle issue:
// https://github.com/helm/helm/issues/7233
func HashV2Req ( req [ ] * chart . Dependency ) ( string , error ) {
dep := make ( map [ string ] [ ] * chart . Dependency )
dep [ "dependencies" ] = req
data , err := json . Marshal ( dep )
if err != nil {
return "" , err
}
s , err := provenance . Digest ( bytes . NewBuffer ( data ) )
return "sha256:" + s , err
}
2017-03-27 20:31:01 -04:00
// GetLocalPath generates absolute local path when use
2018-11-28 13:20:33 -05:00
// "file://" in repository of dependencies
2018-05-10 12:34:41 -04:00
func GetLocalPath ( repo , chartpath string ) ( string , error ) {
2017-03-27 20:31:01 -04:00
var depPath string
var err error
p := strings . TrimPrefix ( repo , "file://" )
// root path is absolute
if strings . HasPrefix ( p , "/" ) {
if depPath , err = filepath . Abs ( p ) ; err != nil {
return "" , err
}
} else {
depPath = filepath . Join ( chartpath , p )
}
if _ , err = os . Stat ( depPath ) ; os . IsNotExist ( err ) {
2018-05-10 12:34:41 -04:00
return "" , errors . Errorf ( "directory %s not found" , depPath )
2017-03-27 20:31:01 -04:00
} else if err != nil {
return "" , err
}
return depPath , nil
}