From b864fcd0f4a86430b867ad3c48544944197d3b29 Mon Sep 17 00:00:00 2001 From: Mikhail Gusarov Date: Mon, 2 Mar 2020 19:05:14 +0100 Subject: [PATCH] Make the charts cache safe in presence of several Helm instances If several instances of Helm are run at the same moment and try to download the same chart, some of them might see an empty or incomplete file in cache. Prevent that by saving the dowloaded file atomically. Closes #7600 Signed-off-by: Mikhail Gusarov --- pkg/downloader/chart_downloader.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index f3d4321c5..a9956f477 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -72,6 +72,31 @@ type ChartDownloader struct { RepositoryCache string } +// atomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a +// disk. +func atomicWriteFile(filename string, body io.Reader, mode os.FileMode) error { + tempFile, err := ioutil.TempFile(filepath.Split(filename)) + if err != nil { + return err + } + tempName := tempFile.Name() + + if _, err := io.Copy(tempFile, body); err != nil { + tempFile.Close() // return value is ignored as we are already on error path + return err + } + + if err := tempFile.Close(); err != nil { + return err + } + + if err := os.Chmod(tempName, mode); err != nil { + return err + } + + return os.Rename(tempName, filename) +} + // DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. // // If Verify is set to VerifyNever, the verification will be nil. @@ -101,7 +126,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven name := filepath.Base(u.Path) destfile := filepath.Join(dest, name) - if err := ioutil.WriteFile(destfile, data.Bytes(), 0644); err != nil { + if err := atomicWriteFile(destfile, data, 0644); err != nil { return destfile, nil, err } @@ -117,7 +142,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven return destfile, ver, nil } provfile := destfile + ".prov" - if err := ioutil.WriteFile(provfile, body.Bytes(), 0644); err != nil { + if err := atomicWriteFile(provfile, body, 0644); err != nil { return destfile, nil, err }