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 }