github.com/docker/buildx@v0.14.1-0.20240514123050-afcb609966dc/store/store.go (about)

     1  package store
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  	"time"
     9  
    10  	"github.com/docker/buildx/localstate"
    11  	"github.com/docker/docker/pkg/ioutils"
    12  	"github.com/gofrs/flock"
    13  	"github.com/opencontainers/go-digest"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  const (
    18  	instanceDir = "instances"
    19  	defaultsDir = "defaults"
    20  	activityDir = "activity"
    21  )
    22  
    23  func New(root string) (*Store, error) {
    24  	if err := os.MkdirAll(filepath.Join(root, instanceDir), 0700); err != nil {
    25  		return nil, err
    26  	}
    27  	if err := os.MkdirAll(filepath.Join(root, defaultsDir), 0700); err != nil {
    28  		return nil, err
    29  	}
    30  	if err := os.MkdirAll(filepath.Join(root, activityDir), 0700); err != nil {
    31  		return nil, err
    32  	}
    33  	return &Store{root: root}, nil
    34  }
    35  
    36  type Store struct {
    37  	root string
    38  }
    39  
    40  func (s *Store) Txn() (*Txn, func(), error) {
    41  	l := flock.New(filepath.Join(s.root, ".lock"))
    42  	if err := l.Lock(); err != nil {
    43  		return nil, nil, err
    44  	}
    45  	return &Txn{
    46  			s: s,
    47  		}, func() {
    48  			l.Close()
    49  		}, nil
    50  }
    51  
    52  type Txn struct {
    53  	s *Store
    54  }
    55  
    56  func (t *Txn) List() ([]*NodeGroup, error) {
    57  	pp := filepath.Join(t.s.root, instanceDir)
    58  	fis, err := os.ReadDir(pp)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	ngs := make([]*NodeGroup, 0, len(fis))
    63  	for _, fi := range fis {
    64  		ng, err := t.NodeGroupByName(fi.Name())
    65  		if err != nil {
    66  			if os.IsNotExist(errors.Cause(err)) {
    67  				os.RemoveAll(filepath.Join(pp, fi.Name()))
    68  				continue
    69  			}
    70  			return nil, err
    71  		}
    72  		ngs = append(ngs, ng)
    73  	}
    74  
    75  	sort.Slice(ngs, func(i, j int) bool {
    76  		return ngs[i].Name < ngs[j].Name
    77  	})
    78  
    79  	return ngs, nil
    80  }
    81  
    82  func (t *Txn) NodeGroupByName(name string) (*NodeGroup, error) {
    83  	name, err := ValidateName(name)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	dt, err := os.ReadFile(filepath.Join(t.s.root, instanceDir, name))
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	var ng NodeGroup
    92  	if err := json.Unmarshal(dt, &ng); err != nil {
    93  		return nil, err
    94  	}
    95  	if ng.LastActivity, err = t.GetLastActivity(&ng); err != nil {
    96  		return nil, err
    97  	}
    98  	return &ng, nil
    99  }
   100  
   101  func (t *Txn) Save(ng *NodeGroup) error {
   102  	name, err := ValidateName(ng.Name)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	if err := t.UpdateLastActivity(ng); err != nil {
   107  		return err
   108  	}
   109  	dt, err := json.Marshal(ng)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	return ioutils.AtomicWriteFile(filepath.Join(t.s.root, instanceDir, name), dt, 0600)
   114  }
   115  
   116  func (t *Txn) Remove(name string) error {
   117  	name, err := ValidateName(name)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	if err := t.RemoveLastActivity(name); err != nil {
   122  		return err
   123  	}
   124  	ls, err := localstate.New(t.s.root)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	if err := ls.RemoveBuilder(name); err != nil {
   129  		return err
   130  	}
   131  	return os.RemoveAll(filepath.Join(t.s.root, instanceDir, name))
   132  }
   133  
   134  func (t *Txn) SetCurrent(key, name string, global, def bool) error {
   135  	c := current{
   136  		Key:    key,
   137  		Name:   name,
   138  		Global: global,
   139  	}
   140  	dt, err := json.Marshal(c)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	if err := ioutils.AtomicWriteFile(filepath.Join(t.s.root, "current"), dt, 0600); err != nil {
   145  		return err
   146  	}
   147  
   148  	h := toHash(key)
   149  
   150  	if def {
   151  		if err := ioutils.AtomicWriteFile(filepath.Join(t.s.root, defaultsDir, h), []byte(name), 0600); err != nil {
   152  			return err
   153  		}
   154  	} else {
   155  		os.RemoveAll(filepath.Join(t.s.root, defaultsDir, h)) // ignore error
   156  	}
   157  	return nil
   158  }
   159  
   160  func (t *Txn) UpdateLastActivity(ng *NodeGroup) error {
   161  	return ioutils.AtomicWriteFile(filepath.Join(t.s.root, activityDir, ng.Name), []byte(time.Now().UTC().Format(time.RFC3339)), 0600)
   162  }
   163  
   164  func (t *Txn) GetLastActivity(ng *NodeGroup) (la time.Time, _ error) {
   165  	dt, err := os.ReadFile(filepath.Join(t.s.root, activityDir, ng.Name))
   166  	if err != nil {
   167  		if os.IsNotExist(errors.Cause(err)) {
   168  			return la, nil
   169  		}
   170  		return la, err
   171  	}
   172  	return time.Parse(time.RFC3339, string(dt))
   173  }
   174  
   175  func (t *Txn) RemoveLastActivity(name string) error {
   176  	name, err := ValidateName(name)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	return os.RemoveAll(filepath.Join(t.s.root, activityDir, name))
   181  }
   182  
   183  func (t *Txn) reset(key string) error {
   184  	dt, err := json.Marshal(current{Key: key})
   185  	if err != nil {
   186  		return err
   187  	}
   188  	return ioutils.AtomicWriteFile(filepath.Join(t.s.root, "current"), dt, 0600)
   189  }
   190  
   191  func (t *Txn) Current(key string) (*NodeGroup, error) {
   192  	dt, err := os.ReadFile(filepath.Join(t.s.root, "current"))
   193  	if err != nil {
   194  		if !os.IsNotExist(err) {
   195  			return nil, err
   196  		}
   197  	}
   198  	if err == nil {
   199  		var c current
   200  		if err := json.Unmarshal(dt, &c); err != nil {
   201  			return nil, err
   202  		}
   203  		if c.Name != "" {
   204  			if c.Global {
   205  				ng, err := t.NodeGroupByName(c.Name)
   206  				if err == nil {
   207  					return ng, nil
   208  				}
   209  			}
   210  
   211  			if c.Key == key {
   212  				ng, err := t.NodeGroupByName(c.Name)
   213  				if err == nil {
   214  					return ng, nil
   215  				}
   216  				return nil, nil
   217  			}
   218  		}
   219  	}
   220  
   221  	h := toHash(key)
   222  
   223  	dt, err = os.ReadFile(filepath.Join(t.s.root, defaultsDir, h))
   224  	if err != nil {
   225  		if os.IsNotExist(err) {
   226  			t.reset(key)
   227  			return nil, nil
   228  		}
   229  		return nil, err
   230  	}
   231  
   232  	ng, err := t.NodeGroupByName(string(dt))
   233  	if err != nil {
   234  		t.reset(key)
   235  	}
   236  	if err := t.SetCurrent(key, string(dt), false, true); err != nil {
   237  		return nil, err
   238  	}
   239  	return ng, nil
   240  }
   241  
   242  type current struct {
   243  	Key    string
   244  	Name   string
   245  	Global bool
   246  }
   247  
   248  func toHash(in string) string {
   249  	return digest.FromBytes([]byte(in)).Hex()[:20]
   250  }