github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/context/store/metadatastore.go (about) 1 // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: 2 //go:build go1.19 3 4 package store 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "os" 10 "path/filepath" 11 "reflect" 12 "sort" 13 14 "github.com/docker/docker/errdefs" 15 "github.com/docker/docker/pkg/ioutils" 16 "github.com/fvbommel/sortorder" 17 "github.com/pkg/errors" 18 ) 19 20 const ( 21 metadataDir = "meta" 22 metaFile = "meta.json" 23 ) 24 25 type metadataStore struct { 26 root string 27 config Config 28 } 29 30 func (s *metadataStore) contextDir(id contextdir) string { 31 return filepath.Join(s.root, string(id)) 32 } 33 34 func (s *metadataStore) createOrUpdate(meta Metadata) error { 35 contextDir := s.contextDir(contextdirOf(meta.Name)) 36 if err := os.MkdirAll(contextDir, 0o755); err != nil { 37 return err 38 } 39 bytes, err := json.Marshal(&meta) 40 if err != nil { 41 return err 42 } 43 return ioutils.AtomicWriteFile(filepath.Join(contextDir, metaFile), bytes, 0o644) 44 } 45 46 func parseTypedOrMap(payload []byte, getter TypeGetter) (any, error) { 47 if len(payload) == 0 || string(payload) == "null" { 48 return nil, nil 49 } 50 if getter == nil { 51 var res map[string]any 52 if err := json.Unmarshal(payload, &res); err != nil { 53 return nil, err 54 } 55 return res, nil 56 } 57 typed := getter() 58 if err := json.Unmarshal(payload, typed); err != nil { 59 return nil, err 60 } 61 return reflect.ValueOf(typed).Elem().Interface(), nil 62 } 63 64 func (s *metadataStore) get(name string) (Metadata, error) { 65 m, err := s.getByID(contextdirOf(name)) 66 if err != nil { 67 return m, errors.Wrapf(err, "context %q", name) 68 } 69 return m, nil 70 } 71 72 func (s *metadataStore) getByID(id contextdir) (Metadata, error) { 73 fileName := filepath.Join(s.contextDir(id), metaFile) 74 bytes, err := os.ReadFile(fileName) 75 if err != nil { 76 if errors.Is(err, os.ErrNotExist) { 77 return Metadata{}, errdefs.NotFound(errors.Wrap(err, "context not found")) 78 } 79 return Metadata{}, err 80 } 81 var untyped untypedContextMetadata 82 r := Metadata{ 83 Endpoints: make(map[string]any), 84 } 85 if err := json.Unmarshal(bytes, &untyped); err != nil { 86 return Metadata{}, fmt.Errorf("parsing %s: %v", fileName, err) 87 } 88 r.Name = untyped.Name 89 if r.Metadata, err = parseTypedOrMap(untyped.Metadata, s.config.contextType); err != nil { 90 return Metadata{}, fmt.Errorf("parsing %s: %v", fileName, err) 91 } 92 for k, v := range untyped.Endpoints { 93 if r.Endpoints[k], err = parseTypedOrMap(v, s.config.endpointTypes[k]); err != nil { 94 return Metadata{}, fmt.Errorf("parsing %s: %v", fileName, err) 95 } 96 } 97 return r, err 98 } 99 100 func (s *metadataStore) remove(name string) error { 101 if err := os.RemoveAll(s.contextDir(contextdirOf(name))); err != nil { 102 return errors.Wrapf(err, "failed to remove metadata") 103 } 104 return nil 105 } 106 107 func (s *metadataStore) list() ([]Metadata, error) { 108 ctxDirs, err := listRecursivelyMetadataDirs(s.root) 109 if err != nil { 110 if errors.Is(err, os.ErrNotExist) { 111 return nil, nil 112 } 113 return nil, err 114 } 115 res := make([]Metadata, 0, len(ctxDirs)) 116 for _, dir := range ctxDirs { 117 c, err := s.getByID(contextdir(dir)) 118 if err != nil { 119 if errors.Is(err, os.ErrNotExist) { 120 continue 121 } 122 return nil, errors.Wrap(err, "failed to read metadata") 123 } 124 res = append(res, c) 125 } 126 sort.Slice(res, func(i, j int) bool { 127 return sortorder.NaturalLess(res[i].Name, res[j].Name) 128 }) 129 return res, nil 130 } 131 132 func isContextDir(path string) bool { 133 s, err := os.Stat(filepath.Join(path, metaFile)) 134 if err != nil { 135 return false 136 } 137 return !s.IsDir() 138 } 139 140 func listRecursivelyMetadataDirs(root string) ([]string, error) { 141 fis, err := os.ReadDir(root) 142 if err != nil { 143 return nil, err 144 } 145 var result []string 146 for _, fi := range fis { 147 if fi.IsDir() { 148 if isContextDir(filepath.Join(root, fi.Name())) { 149 result = append(result, fi.Name()) 150 } 151 subs, err := listRecursivelyMetadataDirs(filepath.Join(root, fi.Name())) 152 if err != nil { 153 return nil, err 154 } 155 for _, s := range subs { 156 result = append(result, filepath.Join(fi.Name(), s)) 157 } 158 } 159 } 160 return result, nil 161 } 162 163 type untypedContextMetadata struct { 164 Metadata json.RawMessage `json:"metadata,omitempty"` 165 Endpoints map[string]json.RawMessage `json:"endpoints,omitempty"` 166 Name string `json:"name,omitempty"` 167 }