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