Add locking, so that helm repo update run parallely doesn't break index file.

Signed-off-by: Lukasz Leszczuk <lleszczuk@gmail.com>
pull/12753/head
Lukasz Leszczuk 10 months ago
parent e81f6140dd
commit 3fcc625711

@ -17,16 +17,19 @@ limitations under the License.
package repo // import "helm.sh/helm/v3/pkg/repo" package repo // import "helm.sh/helm/v3/pkg/repo"
import ( import (
"context"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gofrs/flock"
"io" "io"
"log" "log"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -116,6 +119,8 @@ func (r *ChartRepository) Load() error {
// DownloadIndexFile fetches the index from a repository. // DownloadIndexFile fetches the index from a repository.
func (r *ChartRepository) DownloadIndexFile() (string, error) { func (r *ChartRepository) DownloadIndexFile() (string, error) {
indexURL, err := ResolveReferenceURL(r.Config.URL, "index.yaml") indexURL, err := ResolveReferenceURL(r.Config.URL, "index.yaml")
chartsFName := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name))
idxFName := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -146,14 +151,36 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) {
for name := range indexFile.Entries { for name := range indexFile.Entries {
fmt.Fprintln(&charts, name) fmt.Fprintln(&charts, name)
} }
chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name))
os.MkdirAll(filepath.Dir(chartsFile), 0755) // Lock index file, so that nobody can ready index during update operation
os.WriteFile(chartsFile, []byte(charts.String()), 0644) idxExclLock := flock.New(idxFName + ".lock")
idxCtxt, idxCncl := context.WithTimeout(context.Background(), 30*time.Second)
defer idxCncl()
idxExclLockSuccess, err := idxExclLock.TryLockContext(idxCtxt, 2*time.Second)
if err != nil || !idxExclLockSuccess {
return idxFName, err
}
defer func(indexFlock *flock.Flock) {
_ = indexFlock.Unlock()
}(idxExclLock)
chartsExclLock := flock.New(chartsFName + ".lock")
chartsCtxt, chartsCncl := context.WithTimeout(context.Background(), 30*time.Second)
defer chartsCncl()
chartsExclLockSuccess, err := chartsExclLock.TryLockContext(chartsCtxt, 2*time.Second)
if err != nil || !chartsExclLockSuccess {
return idxFName, err
}
defer func(chartsFlock *flock.Flock) {
_ = chartsFlock.Unlock()
}(chartsExclLock)
os.MkdirAll(filepath.Dir(filepath.Dir(chartsFName)), 0755)
os.WriteFile(chartsFName, []byte(charts.String()), 0644)
// Create the index file in the cache directory // Create the index file in the cache directory
fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) //fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name))
os.MkdirAll(filepath.Dir(fname), 0755) os.MkdirAll(filepath.Dir(idxFName), 0755)
return fname, os.WriteFile(fname, index, 0644) return idxFName, os.WriteFile(idxFName, index, 0644)
} }
// Index generates an index for the chart repository and writes an index.yaml file. // Index generates an index for the chart repository and writes an index.yaml file.

@ -18,7 +18,9 @@ package repo
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"github.com/gofrs/flock"
"log" "log"
"os" "os"
"path" "path"
@ -104,6 +106,15 @@ func NewIndexFile() *IndexFile {
// LoadIndexFile takes a file at the given path and returns an IndexFile object // LoadIndexFile takes a file at the given path and returns an IndexFile object
func LoadIndexFile(path string) (*IndexFile, error) { func LoadIndexFile(path string) (*IndexFile, error) {
// Read-Lock file so that it's not updated by other helm process during repo update process
rLock := flock.New(path + ".lock")
lCtx, lCncl := context.WithTimeout(context.Background(), 30*time.Second)
defer lCncl()
rLockSuccess, err := rLock.TryRLockContext(lCtx, 2*time.Second)
if err != nil || !rLockSuccess {
return nil, err
}
defer rLock.Unlock()
b, err := os.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err

Loading…
Cancel
Save