github.com/thanos-io/thanos@v0.32.5/pkg/extkingpin/path_content_reloader.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package extkingpin
     5  
     6  import (
     7  	"context"
     8  	"crypto/sha256"
     9  	"os"
    10  	"path/filepath"
    11  	"time"
    12  
    13  	"github.com/go-kit/log"
    14  	"github.com/go-kit/log/level"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  type fileContent interface {
    19  	Content() ([]byte, error)
    20  	Path() string
    21  }
    22  
    23  // PathContentReloader runs the reloadFunc when it detects that the contents of fileContent have changed.
    24  func PathContentReloader(ctx context.Context, fileContent fileContent, logger log.Logger, reloadFunc func(), debounceTime time.Duration) error {
    25  	filePath, err := filepath.Abs(fileContent.Path())
    26  	if err != nil {
    27  		return errors.Wrap(err, "getting absolute file path")
    28  	}
    29  
    30  	engine := &pollingEngine{
    31  		filePath:   filePath,
    32  		logger:     logger,
    33  		debounce:   debounceTime,
    34  		reloadFunc: reloadFunc,
    35  	}
    36  	return engine.start(ctx)
    37  }
    38  
    39  // pollingEngine keeps rereading the contents at filePath and when its checksum changes it runs the reloadFunc.
    40  type pollingEngine struct {
    41  	filePath         string
    42  	logger           log.Logger
    43  	debounce         time.Duration
    44  	reloadFunc       func()
    45  	previousChecksum [sha256.Size]byte
    46  }
    47  
    48  func (p *pollingEngine) start(ctx context.Context) error {
    49  	configReader := func() {
    50  		// check if file still exists
    51  		if _, err := os.Stat(p.filePath); os.IsNotExist(err) {
    52  			level.Error(p.logger).Log("msg", "file does not exist", "error", err)
    53  			return
    54  		}
    55  		file, err := os.ReadFile(p.filePath)
    56  		if err != nil {
    57  			level.Error(p.logger).Log("msg", "error opening file", "error", err)
    58  			return
    59  		}
    60  		checksum := sha256.Sum256(file)
    61  		if checksum == p.previousChecksum {
    62  			return
    63  		}
    64  		p.reloadFunc()
    65  		p.previousChecksum = checksum
    66  		level.Debug(p.logger).Log("msg", "configuration reloaded", "path", p.filePath)
    67  	}
    68  	go func() {
    69  		for {
    70  			select {
    71  			case <-ctx.Done():
    72  				return
    73  			case <-time.After(p.debounce):
    74  				configReader()
    75  			}
    76  		}
    77  	}()
    78  	return nil
    79  }
    80  
    81  type staticPathContent struct {
    82  	content []byte
    83  	path    string
    84  }
    85  
    86  var _ fileContent = (*staticPathContent)(nil)
    87  
    88  // Content returns the cached content.
    89  func (t *staticPathContent) Content() ([]byte, error) {
    90  	return t.content, nil
    91  }
    92  
    93  // Path returns the path to the file that contains the content.
    94  func (t *staticPathContent) Path() string {
    95  	return t.path
    96  }
    97  
    98  // NewStaticPathContent creates a new content that can be used to serve a static configuration. It copies the
    99  // configuration from `fromPath` into `destPath` to avoid confusion with file watchers.
   100  func NewStaticPathContent(fromPath string) (*staticPathContent, error) {
   101  	content, err := os.ReadFile(fromPath)
   102  	if err != nil {
   103  		return nil, errors.Wrapf(err, "could not load test content: %s", fromPath)
   104  	}
   105  	return &staticPathContent{content, fromPath}, nil
   106  }
   107  
   108  // Rewrite rewrites the file backing this staticPathContent and swaps the local content cache. The file writing
   109  // is needed to trigger the file system monitor.
   110  func (t *staticPathContent) Rewrite(newContent []byte) error {
   111  	t.content = newContent
   112  	// Write the file to ensure possible file watcher reloaders get triggered.
   113  	return os.WriteFile(t.path, newContent, 0666)
   114  }