package store import ( "archive/tar" _ "crypto/sha256" // ensure ids can be computed "encoding/json" "errors" "fmt" "io" "io/ioutil" "path" "path/filepath" "strings" "github.com/docker/docker/errdefs" digest "github.com/opencontainers/go-digest" ) // Store provides a context store for easily remembering endpoints configuration type Store interface { ListContexts() ([]ContextMetadata, error) CreateOrUpdateContext(meta ContextMetadata) error RemoveContext(name string) error GetContextMetadata(name string) (ContextMetadata, error) ResetContextTLSMaterial(name string, data *ContextTLSData) error ResetContextEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error ListContextTLSFiles(name string) (map[string]EndpointFiles, error) GetContextTLSData(contextName, endpointName, fileName string) ([]byte, error) GetContextStorageInfo(contextName string) ContextStorageInfo } // ContextMetadata contains metadata about a context and its endpoints type ContextMetadata struct { Name string `json:",omitempty"` Metadata interface{} `json:",omitempty"` Endpoints map[string]interface{} `json:",omitempty"` } // ContextStorageInfo contains data about where a given context is stored type ContextStorageInfo struct { MetadataPath string TLSPath string } // EndpointTLSData represents tls data for a given endpoint type EndpointTLSData struct { Files map[string][]byte } // ContextTLSData represents tls data for a whole context type ContextTLSData struct { Endpoints map[string]EndpointTLSData } // New creates a store from a given directory. // If the directory does not exist or is empty, initialize it func New(dir string, cfg Config) Store { metaRoot := filepath.Join(dir, metadataDir) tlsRoot := filepath.Join(dir, tlsDir) return &store{ meta: &metadataStore{ root: metaRoot, config: cfg, }, tls: &tlsStore{ root: tlsRoot, }, } } type store struct { meta *metadataStore tls *tlsStore } func (s *store) ListContexts() ([]ContextMetadata, error) { return s.meta.list() } func (s *store) CreateOrUpdateContext(meta ContextMetadata) error { return s.meta.createOrUpdate(meta) } func (s *store) RemoveContext(name string) error { id := contextdirOf(name) if err := s.meta.remove(id); err != nil { return patchErrContextName(err, name) } return patchErrContextName(s.tls.removeAllContextData(id), name) } func (s *store) GetContextMetadata(name string) (ContextMetadata, error) { res, err := s.meta.get(contextdirOf(name)) patchErrContextName(err, name) return res, err } func (s *store) ResetContextTLSMaterial(name string, data *ContextTLSData) error { id := contextdirOf(name) if err := s.tls.removeAllContextData(id); err != nil { return patchErrContextName(err, name) } if data == nil { return nil } for ep, files := range data.Endpoints { for fileName, data := range files.Files { if err := s.tls.createOrUpdate(id, ep, fileName, data); err != nil { return patchErrContextName(err, name) } } } return nil } func (s *store) ResetContextEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error { id := contextdirOf(contextName) if err := s.tls.removeAllEndpointData(id, endpointName); err != nil { return patchErrContextName(err, contextName) } if data == nil { return nil } for fileName, data := range data.Files { if err := s.tls.createOrUpdate(id, endpointName, fileName, data); err != nil { return patchErrContextName(err, contextName) } } return nil } func (s *store) ListContextTLSFiles(name string) (map[string]EndpointFiles, error) { res, err := s.tls.listContextData(contextdirOf(name)) return res, patchErrContextName(err, name) } func (s *store) GetContextTLSData(contextName, endpointName, fileName string) ([]byte, error) { res, err := s.tls.getData(contextdirOf(contextName), endpointName, fileName) return res, patchErrContextName(err, contextName) } func (s *store) GetContextStorageInfo(contextName string) ContextStorageInfo { dir := contextdirOf(contextName) return ContextStorageInfo{ MetadataPath: s.meta.contextDir(dir), TLSPath: s.tls.contextDir(dir), } } // Export exports an existing namespace into an opaque data stream // This stream is actually a tarball containing context metadata and TLS materials, but it does // not map 1:1 the layout of the context store (don't try to restore it manually without calling store.Import) func Export(name string, s Store) io.ReadCloser { reader, writer := io.Pipe() go func() { tw := tar.NewWriter(writer) defer tw.Close() defer writer.Close() meta, err := s.GetContextMetadata(name) if err != nil { writer.CloseWithError(err) return } metaBytes, err := json.Marshal(&meta) if err != nil { writer.CloseWithError(err) return } if err = tw.WriteHeader(&tar.Header{ Name: metaFile, Mode: 0644, Size: int64(len(metaBytes)), }); err != nil { writer.CloseWithError(err) return } if _, err = tw.Write(metaBytes); err != nil { writer.CloseWithError(err) return } tlsFiles, err := s.ListContextTLSFiles(name) if err != nil { writer.CloseWithError(err) return } if err = tw.WriteHeader(&tar.Header{ Name: "tls", Mode: 0700, Size: 0, Typeflag: tar.TypeDir, }); err != nil { writer.CloseWithError(err) return } for endpointName, endpointFiles := range tlsFiles { if err = tw.WriteHeader(&tar.Header{ Name: path.Join("tls", endpointName), Mode: 0700, Size: 0, Typeflag: tar.TypeDir, }); err != nil { writer.CloseWithError(err) return } for _, fileName := range endpointFiles { data, err := s.GetContextTLSData(name, endpointName, fileName) if err != nil { writer.CloseWithError(err) return } if err = tw.WriteHeader(&tar.Header{ Name: path.Join("tls", endpointName, fileName), Mode: 0600, Size: int64(len(data)), }); err != nil { writer.CloseWithError(err) return } if _, err = tw.Write(data); err != nil { writer.CloseWithError(err) return } } } }() return reader } // Import imports an exported context into a store func Import(name string, s Store, reader io.Reader) error { tr := tar.NewReader(reader) tlsData := ContextTLSData{ Endpoints: map[string]EndpointTLSData{}, } for { hdr, err := tr.Next() if err == io.EOF { break } if err != nil { return err } if hdr.Typeflag == tar.TypeDir { // skip this entry, only taking files into account continue } if hdr.Name == metaFile { data, err := ioutil.ReadAll(tr) if err != nil { return err } var meta ContextMetadata if err := json.Unmarshal(data, &meta); err != nil { return err } meta.Name = name if err := s.CreateOrUpdateContext(meta); err != nil { return err } } else if strings.HasPrefix(hdr.Name, "tls/") { relative := strings.TrimPrefix(hdr.Name, "tls/") parts := strings.SplitN(relative, "/", 2) if len(parts) != 2 { return errors.New("archive format is invalid") } endpointName := parts[0] fileName := parts[1] data, err := ioutil.ReadAll(tr) if err != nil { return err } if _, ok := tlsData.Endpoints[endpointName]; !ok { tlsData.Endpoints[endpointName] = EndpointTLSData{ Files: map[string][]byte{}, } } tlsData.Endpoints[endpointName].Files[fileName] = data } } return s.ResetContextTLSMaterial(name, &tlsData) } type setContextName interface { setContext(name string) } type contextDoesNotExistError struct { name string } func (e *contextDoesNotExistError) Error() string { return fmt.Sprintf("context %q does not exist", e.name) } func (e *contextDoesNotExistError) setContext(name string) { e.name = name } // NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound func (e *contextDoesNotExistError) NotFound() {} type tlsDataDoesNotExist interface { errdefs.ErrNotFound IsTLSDataDoesNotExist() } type tlsDataDoesNotExistError struct { context, endpoint, file string } func (e *tlsDataDoesNotExistError) Error() string { return fmt.Sprintf("tls data for %s/%s/%s does not exist", e.context, e.endpoint, e.file) } func (e *tlsDataDoesNotExistError) setContext(name string) { e.context = name } // NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound func (e *tlsDataDoesNotExistError) NotFound() {} // IsTLSDataDoesNotExist satisfies tlsDataDoesNotExist func (e *tlsDataDoesNotExistError) IsTLSDataDoesNotExist() {} // IsErrContextDoesNotExist checks if the given error is a "context does not exist" condition func IsErrContextDoesNotExist(err error) bool { _, ok := err.(*contextDoesNotExistError) return ok } // IsErrTLSDataDoesNotExist checks if the given error is a "context does not exist" condition func IsErrTLSDataDoesNotExist(err error) bool { _, ok := err.(tlsDataDoesNotExist) return ok } type contextdir string func contextdirOf(name string) contextdir { return contextdir(digest.FromString(name).Encoded()) } func patchErrContextName(err error, name string) error { if typed, ok := err.(setContextName); ok { typed.setContext(name) } return err }