github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/context/store/store.go (about)

     1  package store
     2  
     3  import (
     4  	"archive/tar"
     5  	"archive/zip"
     6  	"bufio"
     7  	"bytes"
     8  	_ "crypto/sha256" // ensure ids can be computed
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"net/http"
    15  	"path"
    16  	"path/filepath"
    17  	"strings"
    18  
    19  	"github.com/docker/docker/errdefs"
    20  	digest "github.com/opencontainers/go-digest"
    21  )
    22  
    23  // Store provides a context store for easily remembering endpoints configuration
    24  type Store interface {
    25  	Reader
    26  	Lister
    27  	Writer
    28  	StorageInfoProvider
    29  }
    30  
    31  // Reader provides read-only (without list) access to context data
    32  type Reader interface {
    33  	GetMetadata(name string) (Metadata, error)
    34  	ListTLSFiles(name string) (map[string]EndpointFiles, error)
    35  	GetTLSData(contextName, endpointName, fileName string) ([]byte, error)
    36  }
    37  
    38  // Lister provides listing of contexts
    39  type Lister interface {
    40  	List() ([]Metadata, error)
    41  }
    42  
    43  // ReaderLister combines Reader and Lister interfaces
    44  type ReaderLister interface {
    45  	Reader
    46  	Lister
    47  }
    48  
    49  // StorageInfoProvider provides more information about storage details of contexts
    50  type StorageInfoProvider interface {
    51  	GetStorageInfo(contextName string) StorageInfo
    52  }
    53  
    54  // Writer provides write access to context data
    55  type Writer interface {
    56  	CreateOrUpdate(meta Metadata) error
    57  	Remove(name string) error
    58  	ResetTLSMaterial(name string, data *ContextTLSData) error
    59  	ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error
    60  }
    61  
    62  // ReaderWriter combines Reader and Writer interfaces
    63  type ReaderWriter interface {
    64  	Reader
    65  	Writer
    66  }
    67  
    68  // Metadata contains metadata about a context and its endpoints
    69  type Metadata struct {
    70  	Name      string                 `json:",omitempty"`
    71  	Metadata  interface{}            `json:",omitempty"`
    72  	Endpoints map[string]interface{} `json:",omitempty"`
    73  }
    74  
    75  // StorageInfo contains data about where a given context is stored
    76  type StorageInfo struct {
    77  	MetadataPath string
    78  	TLSPath      string
    79  }
    80  
    81  // EndpointTLSData represents tls data for a given endpoint
    82  type EndpointTLSData struct {
    83  	Files map[string][]byte
    84  }
    85  
    86  // ContextTLSData represents tls data for a whole context
    87  type ContextTLSData struct {
    88  	Endpoints map[string]EndpointTLSData
    89  }
    90  
    91  // New creates a store from a given directory.
    92  // If the directory does not exist or is empty, initialize it
    93  func New(dir string, cfg Config) Store {
    94  	metaRoot := filepath.Join(dir, metadataDir)
    95  	tlsRoot := filepath.Join(dir, tlsDir)
    96  
    97  	return &store{
    98  		meta: &metadataStore{
    99  			root:   metaRoot,
   100  			config: cfg,
   101  		},
   102  		tls: &tlsStore{
   103  			root: tlsRoot,
   104  		},
   105  	}
   106  }
   107  
   108  type store struct {
   109  	meta *metadataStore
   110  	tls  *tlsStore
   111  }
   112  
   113  func (s *store) List() ([]Metadata, error) {
   114  	return s.meta.list()
   115  }
   116  
   117  func (s *store) CreateOrUpdate(meta Metadata) error {
   118  	return s.meta.createOrUpdate(meta)
   119  }
   120  
   121  func (s *store) Remove(name string) error {
   122  	id := contextdirOf(name)
   123  	if err := s.meta.remove(id); err != nil {
   124  		return patchErrContextName(err, name)
   125  	}
   126  	return patchErrContextName(s.tls.removeAllContextData(id), name)
   127  }
   128  
   129  func (s *store) GetMetadata(name string) (Metadata, error) {
   130  	res, err := s.meta.get(contextdirOf(name))
   131  	patchErrContextName(err, name)
   132  	return res, err
   133  }
   134  
   135  func (s *store) ResetTLSMaterial(name string, data *ContextTLSData) error {
   136  	id := contextdirOf(name)
   137  	if err := s.tls.removeAllContextData(id); err != nil {
   138  		return patchErrContextName(err, name)
   139  	}
   140  	if data == nil {
   141  		return nil
   142  	}
   143  	for ep, files := range data.Endpoints {
   144  		for fileName, data := range files.Files {
   145  			if err := s.tls.createOrUpdate(id, ep, fileName, data); err != nil {
   146  				return patchErrContextName(err, name)
   147  			}
   148  		}
   149  	}
   150  	return nil
   151  }
   152  
   153  func (s *store) ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error {
   154  	id := contextdirOf(contextName)
   155  	if err := s.tls.removeAllEndpointData(id, endpointName); err != nil {
   156  		return patchErrContextName(err, contextName)
   157  	}
   158  	if data == nil {
   159  		return nil
   160  	}
   161  	for fileName, data := range data.Files {
   162  		if err := s.tls.createOrUpdate(id, endpointName, fileName, data); err != nil {
   163  			return patchErrContextName(err, contextName)
   164  		}
   165  	}
   166  	return nil
   167  }
   168  
   169  func (s *store) ListTLSFiles(name string) (map[string]EndpointFiles, error) {
   170  	res, err := s.tls.listContextData(contextdirOf(name))
   171  	return res, patchErrContextName(err, name)
   172  }
   173  
   174  func (s *store) GetTLSData(contextName, endpointName, fileName string) ([]byte, error) {
   175  	res, err := s.tls.getData(contextdirOf(contextName), endpointName, fileName)
   176  	return res, patchErrContextName(err, contextName)
   177  }
   178  
   179  func (s *store) GetStorageInfo(contextName string) StorageInfo {
   180  	dir := contextdirOf(contextName)
   181  	return StorageInfo{
   182  		MetadataPath: s.meta.contextDir(dir),
   183  		TLSPath:      s.tls.contextDir(dir),
   184  	}
   185  }
   186  
   187  // Export exports an existing namespace into an opaque data stream
   188  // This stream is actually a tarball containing context metadata and TLS materials, but it does
   189  // not map 1:1 the layout of the context store (don't try to restore it manually without calling store.Import)
   190  func Export(name string, s Reader) io.ReadCloser {
   191  	reader, writer := io.Pipe()
   192  	go func() {
   193  		tw := tar.NewWriter(writer)
   194  		defer tw.Close()
   195  		defer writer.Close()
   196  		meta, err := s.GetMetadata(name)
   197  		if err != nil {
   198  			writer.CloseWithError(err)
   199  			return
   200  		}
   201  		metaBytes, err := json.Marshal(&meta)
   202  		if err != nil {
   203  			writer.CloseWithError(err)
   204  			return
   205  		}
   206  		if err = tw.WriteHeader(&tar.Header{
   207  			Name: metaFile,
   208  			Mode: 0644,
   209  			Size: int64(len(metaBytes)),
   210  		}); err != nil {
   211  			writer.CloseWithError(err)
   212  			return
   213  		}
   214  		if _, err = tw.Write(metaBytes); err != nil {
   215  			writer.CloseWithError(err)
   216  			return
   217  		}
   218  		tlsFiles, err := s.ListTLSFiles(name)
   219  		if err != nil {
   220  			writer.CloseWithError(err)
   221  			return
   222  		}
   223  		if err = tw.WriteHeader(&tar.Header{
   224  			Name:     "tls",
   225  			Mode:     0700,
   226  			Size:     0,
   227  			Typeflag: tar.TypeDir,
   228  		}); err != nil {
   229  			writer.CloseWithError(err)
   230  			return
   231  		}
   232  		for endpointName, endpointFiles := range tlsFiles {
   233  			if err = tw.WriteHeader(&tar.Header{
   234  				Name:     path.Join("tls", endpointName),
   235  				Mode:     0700,
   236  				Size:     0,
   237  				Typeflag: tar.TypeDir,
   238  			}); err != nil {
   239  				writer.CloseWithError(err)
   240  				return
   241  			}
   242  			for _, fileName := range endpointFiles {
   243  				data, err := s.GetTLSData(name, endpointName, fileName)
   244  				if err != nil {
   245  					writer.CloseWithError(err)
   246  					return
   247  				}
   248  				if err = tw.WriteHeader(&tar.Header{
   249  					Name: path.Join("tls", endpointName, fileName),
   250  					Mode: 0600,
   251  					Size: int64(len(data)),
   252  				}); err != nil {
   253  					writer.CloseWithError(err)
   254  					return
   255  				}
   256  				if _, err = tw.Write(data); err != nil {
   257  					writer.CloseWithError(err)
   258  					return
   259  				}
   260  			}
   261  		}
   262  	}()
   263  	return reader
   264  }
   265  
   266  const (
   267  	maxAllowedFileSizeToImport int64  = 10 << 20
   268  	zipType                    string = "application/zip"
   269  )
   270  
   271  func getImportContentType(r *bufio.Reader) (string, error) {
   272  	head, err := r.Peek(512)
   273  	if err != nil && err != io.EOF {
   274  		return "", err
   275  	}
   276  
   277  	return http.DetectContentType(head), nil
   278  }
   279  
   280  // Import imports an exported context into a store
   281  func Import(name string, s Writer, reader io.Reader) error {
   282  	// Buffered reader will not advance the buffer, needed to determine content type
   283  	r := bufio.NewReader(reader)
   284  
   285  	importContentType, err := getImportContentType(r)
   286  	if err != nil {
   287  		return err
   288  	}
   289  	switch importContentType {
   290  	case zipType:
   291  		return importZip(name, s, r)
   292  	default:
   293  		// Assume it's a TAR (TAR does not have a "magic number")
   294  		return importTar(name, s, r)
   295  	}
   296  }
   297  
   298  func importTar(name string, s Writer, reader io.Reader) error {
   299  	tr := tar.NewReader(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport})
   300  	tlsData := ContextTLSData{
   301  		Endpoints: map[string]EndpointTLSData{},
   302  	}
   303  	var importedMetaFile bool
   304  	for {
   305  		hdr, err := tr.Next()
   306  		if err == io.EOF {
   307  			break
   308  		}
   309  		if err != nil {
   310  			return err
   311  		}
   312  		if hdr.Typeflag == tar.TypeDir {
   313  			// skip this entry, only taking files into account
   314  			continue
   315  		}
   316  		if hdr.Name == metaFile {
   317  			data, err := ioutil.ReadAll(tr)
   318  			if err != nil {
   319  				return err
   320  			}
   321  			meta, err := parseMetadata(data, name)
   322  			if err != nil {
   323  				return err
   324  			}
   325  			if err := s.CreateOrUpdate(meta); err != nil {
   326  				return err
   327  			}
   328  			importedMetaFile = true
   329  		} else if strings.HasPrefix(hdr.Name, "tls/") {
   330  			data, err := ioutil.ReadAll(tr)
   331  			if err != nil {
   332  				return err
   333  			}
   334  			if err := importEndpointTLS(&tlsData, hdr.Name, data); err != nil {
   335  				return err
   336  			}
   337  		}
   338  	}
   339  	if !importedMetaFile {
   340  		return errdefs.InvalidParameter(errors.New("invalid context: no metadata found"))
   341  	}
   342  	return s.ResetTLSMaterial(name, &tlsData)
   343  }
   344  
   345  func importZip(name string, s Writer, reader io.Reader) error {
   346  	body, err := ioutil.ReadAll(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport})
   347  	if err != nil {
   348  		return err
   349  	}
   350  	zr, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
   351  	if err != nil {
   352  		return err
   353  	}
   354  	tlsData := ContextTLSData{
   355  		Endpoints: map[string]EndpointTLSData{},
   356  	}
   357  
   358  	var importedMetaFile bool
   359  	for _, zf := range zr.File {
   360  		fi := zf.FileInfo()
   361  		if fi.IsDir() {
   362  			// skip this entry, only taking files into account
   363  			continue
   364  		}
   365  		if zf.Name == metaFile {
   366  			f, err := zf.Open()
   367  			if err != nil {
   368  				return err
   369  			}
   370  
   371  			data, err := ioutil.ReadAll(&LimitedReader{R: f, N: maxAllowedFileSizeToImport})
   372  			defer f.Close()
   373  			if err != nil {
   374  				return err
   375  			}
   376  			meta, err := parseMetadata(data, name)
   377  			if err != nil {
   378  				return err
   379  			}
   380  			if err := s.CreateOrUpdate(meta); err != nil {
   381  				return err
   382  			}
   383  			importedMetaFile = true
   384  		} else if strings.HasPrefix(zf.Name, "tls/") {
   385  			f, err := zf.Open()
   386  			if err != nil {
   387  				return err
   388  			}
   389  			data, err := ioutil.ReadAll(f)
   390  			defer f.Close()
   391  			if err != nil {
   392  				return err
   393  			}
   394  			err = importEndpointTLS(&tlsData, zf.Name, data)
   395  			if err != nil {
   396  				return err
   397  			}
   398  		}
   399  	}
   400  	if !importedMetaFile {
   401  		return errdefs.InvalidParameter(errors.New("invalid context: no metadata found"))
   402  	}
   403  	return s.ResetTLSMaterial(name, &tlsData)
   404  }
   405  
   406  func parseMetadata(data []byte, name string) (Metadata, error) {
   407  	var meta Metadata
   408  	if err := json.Unmarshal(data, &meta); err != nil {
   409  		return meta, err
   410  	}
   411  	meta.Name = name
   412  	return meta, nil
   413  }
   414  
   415  func importEndpointTLS(tlsData *ContextTLSData, path string, data []byte) error {
   416  	parts := strings.SplitN(strings.TrimPrefix(path, "tls/"), "/", 2)
   417  	if len(parts) != 2 {
   418  		// TLS endpoints require archived file directory with 2 layers
   419  		// i.e. tls/{endpointName}/{fileName}
   420  		return errors.New("archive format is invalid")
   421  	}
   422  
   423  	epName := parts[0]
   424  	fileName := parts[1]
   425  	if _, ok := tlsData.Endpoints[epName]; !ok {
   426  		tlsData.Endpoints[epName] = EndpointTLSData{
   427  			Files: map[string][]byte{},
   428  		}
   429  	}
   430  	tlsData.Endpoints[epName].Files[fileName] = data
   431  	return nil
   432  }
   433  
   434  type setContextName interface {
   435  	setContext(name string)
   436  }
   437  
   438  type contextDoesNotExistError struct {
   439  	name string
   440  }
   441  
   442  func (e *contextDoesNotExistError) Error() string {
   443  	return fmt.Sprintf("context %q does not exist", e.name)
   444  }
   445  
   446  func (e *contextDoesNotExistError) setContext(name string) {
   447  	e.name = name
   448  }
   449  
   450  // NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound
   451  func (e *contextDoesNotExistError) NotFound() {}
   452  
   453  type tlsDataDoesNotExist interface {
   454  	errdefs.ErrNotFound
   455  	IsTLSDataDoesNotExist()
   456  }
   457  
   458  type tlsDataDoesNotExistError struct {
   459  	context, endpoint, file string
   460  }
   461  
   462  func (e *tlsDataDoesNotExistError) Error() string {
   463  	return fmt.Sprintf("tls data for %s/%s/%s does not exist", e.context, e.endpoint, e.file)
   464  }
   465  
   466  func (e *tlsDataDoesNotExistError) setContext(name string) {
   467  	e.context = name
   468  }
   469  
   470  // NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound
   471  func (e *tlsDataDoesNotExistError) NotFound() {}
   472  
   473  // IsTLSDataDoesNotExist satisfies tlsDataDoesNotExist
   474  func (e *tlsDataDoesNotExistError) IsTLSDataDoesNotExist() {}
   475  
   476  // IsErrContextDoesNotExist checks if the given error is a "context does not exist" condition
   477  func IsErrContextDoesNotExist(err error) bool {
   478  	_, ok := err.(*contextDoesNotExistError)
   479  	return ok
   480  }
   481  
   482  // IsErrTLSDataDoesNotExist checks if the given error is a "context does not exist" condition
   483  func IsErrTLSDataDoesNotExist(err error) bool {
   484  	_, ok := err.(tlsDataDoesNotExist)
   485  	return ok
   486  }
   487  
   488  type contextdir string
   489  
   490  func contextdirOf(name string) contextdir {
   491  	return contextdir(digest.FromString(name).Encoded())
   492  }
   493  
   494  func patchErrContextName(err error, name string) error {
   495  	if typed, ok := err.(setContextName); ok {
   496  		typed.setContext(name)
   497  	}
   498  	return err
   499  }