fix(repo): write repositories.yaml atomically using AtomicWriteFile

File.WriteFile used os.WriteFile, which truncates the destination file before writing data. If the write fails (due to quota, full disk, etc.) or the process is interrupted, repositories.yaml is left empty.

Use fileutil.AtomicWriteFile to ensure updates to repositories.yaml are performed atomically via temporary file and rename, matching the behavior of index.go and chartrepo.go.

Signed-off-by: jdymitarai <o10040115@gmail.com>
pull/32716/head
jdymitarai 8 hours ago
parent 53dfa521e0
commit 09cda512f9

@ -17,12 +17,15 @@ limitations under the License.
package repo package repo
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/internal/fileutil"
) )
// File represents the repositories.yaml file // File represents the repositories.yaml file
@ -121,5 +124,5 @@ func (r *File) WriteFile(path string, perm os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err return err
} }
return os.WriteFile(path, data, perm) return fileutil.AtomicWriteFile(path, bytes.NewReader(data), perm)
} }

@ -18,6 +18,7 @@ package repo
import ( import (
"os" "os"
"path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -168,6 +169,7 @@ func TestWriteFile(t *testing.T) {
file, err := os.CreateTemp(t.TempDir(), "helm-repo") file, err := os.CreateTemp(t.TempDir(), "helm-repo")
require.NoErrorf(t, err, "failed to create test-file") require.NoErrorf(t, err, "failed to create test-file")
require.NoError(t, file.Close())
defer os.Remove(file.Name()) defer os.Remove(file.Name())
require.NoErrorf(t, sampleRepository.WriteFile(file.Name(), 0o600), "failed to write file") require.NoErrorf(t, sampleRepository.WriteFile(file.Name(), 0o600), "failed to write file")
@ -207,3 +209,32 @@ func TestRemoveRepositoryInvalidEntries(t *testing.T) {
assert.Truef(t, sampleRepository.Remove(removeRepository), "expected repository %s not found", removeRepository) assert.Truef(t, sampleRepository.Remove(removeRepository), "expected repository %s not found", removeRepository)
assert.Falsef(t, sampleRepository.Has(removeRepository), "repository %s not deleted", removeRepository) assert.Falsef(t, sampleRepository.Has(removeRepository), "repository %s not deleted", removeRepository)
} }
func TestWriteFile_Atomic(t *testing.T) {
dir := t.TempDir()
filePath := filepath.Join(dir, "repositories.yaml")
initialRepo := NewFile()
initialRepo.Add(&Entry{
Name: "initial",
URL: "https://example.com/initial",
})
require.NoError(t, initialRepo.WriteFile(filePath, 0o644))
initialData, err := os.ReadFile(filePath)
require.NoError(t, err)
require.NotEmpty(t, initialData)
updatedRepo := NewFile()
updatedRepo.Add(&Entry{
Name: "updated",
URL: "https://example.com/updated",
})
require.NoError(t, updatedRepo.WriteFile(filePath, 0o644))
loaded, err := LoadFile(filePath)
require.NoError(t, err)
assert.False(t, loaded.Has("initial"))
assert.True(t, loaded.Has("updated"))
}

Loading…
Cancel
Save