@ -249,22 +249,24 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
destPath := filepath . Join ( m . ChartPath , "charts" )
tmpPath := filepath . Join ( m . ChartPath , "tmpcharts" )
// Create 'charts' directory if it doesn't already exist.
if fi , err := os . Stat ( destPath ) ; err != nil {
// Check if 'charts' directory is not actally a directory. If it does not exist, create it.
if fi , err := os . Stat ( destPath ) ; err == nil {
if ! fi . IsDir ( ) {
return errors . Errorf ( "%q is not a directory" , destPath )
}
} else if os . IsNotExist ( err ) {
if err := os . MkdirAll ( destPath , 0755 ) ; err != nil {
return err
}
} else if ! fi . IsDir ( ) {
return errors . Errorf ( "%q is not a directory" , destPath )
}
if err := fs . RenameWithFallback ( destPath , tmpPath ) ; err != nil {
return errors . Wrap ( err , "unable to move current charts to tmp dir" )
} else {
return fmt . Errorf ( "unable to retrieve file info for '%s': %v" , destPath , err )
}
if err := os . MkdirAll ( destPath , 0755 ) ; err != nil {
// Prepare tmpPath
if err := os . MkdirAll ( tmpPath , 0755 ) ; err != nil {
return err
}
defer os . RemoveAll ( tmpPath )
fmt . Fprintf ( m . Out , "Saving %d charts\n" , len ( deps ) )
var saveError error
@ -273,10 +275,11 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
// No repository means the chart is in charts directory
if dep . Repository == "" {
fmt . Fprintf ( m . Out , "Dependency %s did not declare a repository. Assuming it exists in the charts directory\n" , dep . Name )
chartPath := filepath . Join ( tmpPath , dep . Name )
// NOTE: we are only validating the local dependency conforms to the constraints. No copying to tmpPath is necessary.
chartPath := filepath . Join ( destPath , dep . Name )
ch , err := loader . LoadDir ( chartPath )
if err != nil {
return fmt . Errorf ( "unable to load chart : %v", err )
return fmt . Errorf ( "unable to load chart '%s' : %v", chartPath , err )
}
constraint , err := semver . NewConstraint ( dep . Version )
@ -354,8 +357,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
getter . WithTagName ( version ) )
}
_ , _ , err = dl . DownloadTo ( churl , version , destPath )
if err != nil {
if _ , _ , err = dl . DownloadTo ( churl , version , tmpPath ) ; err != nil {
saveError = errors . Wrapf ( err , "could not download %s" , churl )
break
}
@ -363,36 +365,14 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
churls [ churl ] = struct { } { }
}
// TODO: this should probably be refactored to be a []error, so we can capture and provide more information rather than "last error wins".
if saveError == nil {
fmt . Fprintln ( m . Out , "Deleting outdated charts" )
for _ , dep := range deps {
// Chart from local charts directory stays in place
if dep . Repository != "" {
if err := m . safeDeleteDep ( dep . Name , tmpPath ) ; err != nil {
return err
}
}
}
if err := move ( tmpPath , destPath ) ; err != nil {
// now we can move all downloaded charts to destPath and delete outdated dependencies
if err := m . safeMoveDeps ( deps , tmpPath , destPath ) ; err != nil {
return err
}
if err := os . RemoveAll ( tmpPath ) ; err != nil {
return errors . Wrapf ( err , "failed to remove %v" , tmpPath )
}
} else {
fmt . Fprintln ( m . Out , "Save error occurred: " , saveError )
fmt . Fprintln ( m . Out , "Deleting newly downloaded charts, restoring pre-update state" )
for _ , dep := range deps {
if err := m . safeDeleteDep ( dep . Name , destPath ) ; err != nil {
return err
}
}
if err := os . RemoveAll ( destPath ) ; err != nil {
return errors . Wrapf ( err , "failed to remove %v" , destPath )
}
if err := fs . RenameWithFallback ( tmpPath , destPath ) ; err != nil {
return errors . Wrap ( err , "unable to move current charts to tmp dir" )
}
return saveError
}
return nil
@ -410,36 +390,75 @@ func parseOCIRef(chartRef string) (string, string, error) {
return chartRef , tag , nil
}
// safe DeleteDep deletes any versions of the given dependency in the given directory .
// safe MoveDep moves all dependencies in the source and moves them into dest .
//
// It does this by first matching the file name to an expected pattern, then loading
// the file to verify that it is a chart with the same name as the given name .
// the file to verify that it is a chart .
//
// Because it requires tar file introspection, it is more intensive than a basic delete.
// Any charts in dest that do not exist in source are removed (barring local dependencies)
//
// Because it requires tar file introspection, it is more intensive than a basic move.
//
// This will only return errors that should stop processing entirely. Other errors
// will emit log messages or be ignored.
func ( m * Manager ) safeDeleteDep ( name , dir string ) error {
files , err := filepath . Glob ( filepath . Join ( dir , name + "-*.tgz" ) )
func ( m * Manager ) safeMoveDeps ( deps [ ] * chart . Dependency , source , dest string ) error {
existsInSourceDirectory := map [ string ] bool { }
isLocalDependency := map [ string ] bool { }
sourceFiles , err := ioutil . ReadDir ( source )
if err != nil {
// Only for ErrBadPattern
return err
}
for _ , fname := range files {
ch , err := loader . LoadFile ( fname )
if err != nil {
fmt . Fprintf ( m . Out , "Could not verify %s for deletion: %s (Skipping)" , fname , err )
// attempt to read destFiles; fail fast if we can't
destFiles , err := ioutil . ReadDir ( dest )
if err != nil {
return err
}
for _ , dep := range deps {
if dep . Repository == "" {
isLocalDependency [ dep . Name ] = true
}
}
for _ , file := range sourceFiles {
if file . IsDir ( ) {
continue
}
if ch . Name ( ) != name {
// This is not the file you are looking for.
filename := file . Name ( )
sourcefile := filepath . Join ( source , filename )
destfile := filepath . Join ( dest , filename )
existsInSourceDirectory [ filename ] = true
if _ , err := loader . LoadFile ( sourcefile ) ; err != nil {
fmt . Fprintf ( m . Out , "Could not verify %s for moving: %s (Skipping)" , sourcefile , err )
continue
}
if err := os . Remove ( fname ) ; err != nil {
fmt . Fprintf ( m . Out , "Could not delete %s: %s (Skipping)" , fname , err )
// NOTE: no need to delete the dest; os.Rename replaces it.
if err := fs . RenameWithFallback ( sourcefile , destfile ) ; err != nil {
fmt . Fprintf ( m . Out , "Unable to move %s to charts dir %s (Skipping)" , sourcefile , err )
continue
}
}
fmt . Fprintln ( m . Out , "Deleting outdated charts" )
// find all files that exist in dest that do not exist in source; delete them (outdated dependendencies)
for _ , file := range destFiles {
if ! file . IsDir ( ) && ! existsInSourceDirectory [ file . Name ( ) ] {
fname := filepath . Join ( dest , file . Name ( ) )
ch , err := loader . LoadFile ( fname )
if err != nil {
fmt . Fprintf ( m . Out , "Could not verify %s for deletion: %s (Skipping)" , fname , err )
}
// local dependency - skip
if isLocalDependency [ ch . Name ( ) ] {
continue
}
if err := os . Remove ( fname ) ; err != nil {
fmt . Fprintf ( m . Out , "Could not delete %s: %s (Skipping)" , fname , err )
continue
}
}
}
return nil
}
@ -868,20 +887,6 @@ func tarFromLocalDir(chartpath, name, repo, version string) (string, error) {
return "" , errors . Errorf ( "can't get a valid version for dependency %s" , name )
}
// move files from tmppath to destpath
func move ( tmpPath , destPath string ) error {
files , _ := os . ReadDir ( tmpPath )
for _ , file := range files {
filename := file . Name ( )
tmpfile := filepath . Join ( tmpPath , filename )
destfile := filepath . Join ( destPath , filename )
if err := fs . RenameWithFallback ( tmpfile , destfile ) ; err != nil {
return errors . Wrap ( err , "unable to move local charts to charts dir" )
}
}
return nil
}
// The prefix to use for cache keys created by the manager for repo names
const managerKeyPrefix = "helm-manager-"