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  }