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 }