github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/api/lc_artifact.go (about) 1 /* 2 * Copyright (c) 2018-2020 vChain, Inc. All Rights Reserved. 3 * This software is released under GPL3. 4 * The full license information can be found under: 5 * https://www.gnu.org/licenses/gpl-3.0.en.html 6 * 7 */ 8 9 package api 10 11 import ( 12 "archive/zip" 13 "bytes" 14 "context" 15 "crypto/sha256" 16 "encoding/hex" 17 "encoding/json" 18 "errors" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "math" 23 "net/http" 24 "os" 25 "path" 26 "regexp" 27 "sort" 28 "strconv" 29 "strings" 30 "time" 31 32 "github.com/spf13/viper" 33 34 immuschema "github.com/codenotary/immudb/pkg/api/schema" 35 "github.com/vchain-us/ledger-compliance-go/schema" 36 "github.com/vchain-us/vcn/pkg/meta" 37 "google.golang.org/grpc/metadata" 38 "google.golang.org/grpc/status" 39 ) 40 41 func (a Artifact) toLcArtifact() *LcArtifact { 42 aR := &LcArtifact{ 43 // root fields 44 Kind: a.Kind, 45 Name: a.Name, 46 Hash: a.Hash, 47 Size: a.Size, 48 ContentType: a.ContentType, 49 50 // custom metadata 51 Metadata: a.Metadata, 52 } 53 54 return aR 55 } 56 func ItemToLcArtifact(item *schema.ItemExt) (*LcArtifact, error) { 57 var lca LcArtifact 58 err := json.Unmarshal(item.Item.Value, &lca) 59 if err != nil { 60 return nil, err 61 } 62 ts := time.Unix(item.Timestamp.GetSeconds(), int64(item.Timestamp.GetNanos())) 63 lca.Uid = strconv.Itoa(int(ts.UnixNano())) 64 lca.Timestamp = ts.UTC() 65 // if ApikeyRevoked == nil no revoked infos available. Old key type 66 if item.ApikeyRevoked != nil { 67 if item.ApikeyRevoked.GetSeconds() > 0 { 68 t := time.Unix(item.ApikeyRevoked.GetSeconds(), int64(item.ApikeyRevoked.Nanos)).UTC() 69 lca.Revoked = &t 70 } else { 71 lca.Revoked = &time.Time{} 72 } 73 } 74 lca.Ledger = item.LedgerName 75 return &lca, nil 76 } 77 78 func ZItemToLcArtifact(ie *schema.ZItemExt) (*LcArtifact, error) { 79 var lca LcArtifact 80 err := json.Unmarshal(ie.Item.Entry.Value, &lca) 81 if err != nil { 82 return nil, err 83 } 84 ts := time.Unix(ie.Timestamp.GetSeconds(), int64(ie.Timestamp.GetNanos())) 85 lca.Uid = strconv.Itoa(int(ts.UnixNano())) 86 lca.Timestamp = ts.UTC() 87 // if ApikeyRevoked == nil no revoked infos available. Old key type 88 if ie.ApikeyRevoked != nil { 89 if ie.ApikeyRevoked.GetSeconds() > 0 { 90 t := time.Unix(ie.ApikeyRevoked.GetSeconds(), int64(ie.ApikeyRevoked.Nanos)).UTC() 91 lca.Revoked = &t 92 } else { 93 lca.Revoked = &time.Time{} 94 } 95 } 96 lca.Ledger = ie.LedgerName 97 return &lca, nil 98 } 99 100 func VerifiableItemExtToLcArtifact(item *schema.VerifiableItemExt) (*LcArtifact, error) { 101 var lca LcArtifact 102 err := json.Unmarshal(item.Item.Entry.Value, &lca) 103 if err != nil { 104 return nil, err 105 } 106 ts := time.Unix(item.Timestamp.GetSeconds(), int64(item.Timestamp.GetNanos())) 107 lca.Uid = strconv.Itoa(int(ts.UnixNano())) 108 lca.Timestamp = ts.UTC() 109 // if ApikeyRevoked == nil no revoked infos available. Old key type 110 if item.ApikeyRevoked != nil { 111 if item.ApikeyRevoked.GetSeconds() > 0 { 112 t := time.Unix(item.ApikeyRevoked.GetSeconds(), int64(item.ApikeyRevoked.Nanos)).UTC() 113 lca.Revoked = &t 114 } else { 115 lca.Revoked = &time.Time{} 116 } 117 } 118 lca.Ledger = item.LedgerName 119 return &lca, nil 120 } 121 122 type LcArtifact struct { 123 // root fields 124 Uid string `json:"uid" yaml:"uid" vcn:"UID"` 125 Kind string `json:"kind" yaml:"kind" vcn:"Kind"` 126 Name string `json:"name" yaml:"name" vcn:"Name"` 127 Hash string `json:"hash" yaml:"hash" vcn:"Hash"` 128 Size uint64 `json:"size" yaml:"size" vcn:"Size"` 129 Timestamp time.Time `json:"timestamp,omitempty" yaml:"timestamp" vcn:"Timestamp"` 130 ContentType string `json:"contentType" yaml:"contentType" vcn:"ContentType"` 131 132 // custom metadata 133 Metadata Metadata `json:"metadata" yaml:"metadata" vcn:"Metadata"` 134 Attachments []Attachment `json:"attachments" yaml:"attachments" vcn:"Attachments"` 135 136 Signer string `json:"signer" yaml:"signer" vcn:"SignerID"` 137 Revoked *time.Time `json:"revoked,omitempty" yaml:"revoked" vcn:"Apikey revoked"` 138 Status meta.Status `json:"status" yaml:"status" vcn:"Status"` 139 Ledger string `json:"ledger,omitempty" yaml:"ledger"` 140 141 IncludedIn []PackageDetails `json:"included_in,omitempty" yaml:"included_in,omitempty" vcn:"Included in"` 142 Deps []PackageDetails `json:"bom,omitempty" yaml:"bom,omitempty" vcn:"Dependencies"` 143 } 144 145 func (u LcUser) artifactToSetRequest( 146 artifact *Artifact, 147 status meta.Status, 148 attach []string, 149 bomText string, 150 outReq *immuschema.SetRequest, 151 ) error { 152 153 aR := artifact.toLcArtifact() 154 aR.Status = status 155 156 aR.Signer = GetSignerIDByApiKey(u.Client.ApiKey) 157 158 // vcn.myApiKey.{artifact hash} 159 // attachment key need to have "vcn." prefix because it's handled inside cnil frontend. (attachment is listed in the UI). 160 key := AppendPrefix(meta.VcnPrefix, []byte(aR.Signer)) 161 key = AppendSignerId(artifact.Hash, key) 162 163 // Attachments handler 164 // attachments info generation and multi kv preparation 165 var aKVs []*immuschema.KeyValue 166 var aRattachment []Attachment 167 168 if bomText != "" { 169 kv := &immuschema.KeyValue{ 170 Key: []byte(meta.BomEntryKeyName), 171 Value: []byte(bomText), 172 } 173 174 aKVs = append(aKVs, kv) 175 } 176 177 // map to save all the attachments with a specific label 178 labelMap := make(map[string][]Attachment) 179 180 if viper.GetBool("compress") && len(attach) > 0 { 181 zipBuf := new(bytes.Buffer) 182 183 z := zip.NewWriter(zipBuf) 184 for _, a := range attach { 185 // In case of compressed attachments ignore the optional tag 186 fields := strings.SplitN(a, ":", 2) 187 if len(fields) > 1 { 188 return errors.New("cannot specify tag for attachments to be compressed") 189 } 190 err := compressFile(z, fields[0]) 191 if err != nil { 192 return err 193 } 194 } 195 err := z.Close() 196 if err != nil { 197 return err 198 } 199 200 checksum := sha256.Sum256(zipBuf.Bytes()) 201 hash := hex.EncodeToString(checksum[:]) 202 akey := AppendAttachment(hash, key) 203 204 kv := &immuschema.KeyValue{ 205 Key: []byte(akey), 206 Value: zipBuf.Bytes(), 207 } 208 209 aKVs = append(aKVs, kv) 210 211 at := Attachment{ 212 Filename: "attachments.zip", 213 Hash: hash, 214 Mime: "application/zip", 215 } 216 aRattachment = append(aRattachment, at) 217 } else { 218 for _, al := range attach { 219 // attachment can be --attach=vscanner.result:jobid123. jobid123 is the label 220 alSlice := strings.SplitN(al, ":", 2) 221 a := alSlice[0] 222 /** friendly label **/ 223 label := "" 224 if len(alSlice) > 1 { 225 label = alSlice[1] 226 } 227 228 // attachment 229 f, err := os.Open(a) 230 if err != nil { 231 return err 232 } 233 defer f.Close() 234 235 fc, err := ioutil.ReadFile(a) 236 if err != nil { 237 return err 238 } 239 h := sha256.New() 240 if _, err := io.Copy(h, f); err != nil { 241 return err 242 } 243 checksum := h.Sum(nil) 244 hash := hex.EncodeToString(checksum) 245 akey := AppendAttachment(hash, key) 246 247 kv := &immuschema.KeyValue{ 248 Key: []byte(akey), 249 Value: fc, 250 } 251 252 aKVs = append(aKVs, kv) 253 254 mime := http.DetectContentType(fc) 255 at := Attachment{ 256 Filename: path.Base(a), 257 Hash: hash, 258 Mime: mime, 259 Label: label, 260 } 261 262 /** friendly label **/ 263 /* _ITEM.ATTACH.LABEL.myApiKey.{arifact hash}.vscanner.result:jobid123 */ 264 if label != "" { 265 labelKey := meta.VcnAttachmentLabelPrefix + "." + aR.Signer + "." + artifact.Hash + "." + al 266 // here is used an array to be downloadable by the same code in the attachments map use case 267 attachs := []Attachment{at} 268 attachmentsListJSON, err := json.Marshal(attachs) 269 if err != nil { 270 return err 271 } 272 labelKV := &immuschema.KeyValue{ 273 Key: []byte(labelKey), 274 Value: attachmentsListJSON, 275 } 276 aKVs = append(aKVs, labelKV) 277 278 // label map 279 // append the attachment key in the labelMap at specific label key 280 labelMap[label] = append(labelMap[label], at) 281 } 282 283 aRattachment = append(aRattachment, at) 284 } 285 } 286 287 aR.Attachments = aRattachment 288 arJSON, err := json.Marshal(aR) 289 if err != nil { 290 return err 291 } 292 293 outReq.KVs = []*immuschema.KeyValue{{Key: key, Value: arJSON}} 294 if len(aKVs) > 0 { 295 outReq.KVs = append(outReq.KVs, aKVs...) 296 } 297 298 // here is built a key to retrieve in a single call all the attachment with a specific label. The value is a list of attachment keys joined by ":" separator 299 for label, attachments := range labelMap { 300 /* _ITEM.ATTACH.LABEL.myApiKey.{arifact hash}.jobid123 */ 301 labelMapKey := meta.VcnAttachmentLabelPrefix + "." + aR.Signer + "." + artifact.Hash + "." + label 302 303 attachmentsListJSON, err := json.Marshal(attachments) 304 if err != nil { 305 return err 306 } 307 labelMapKV := &immuschema.KeyValue{ 308 Key: []byte(labelMapKey), 309 Value: attachmentsListJSON, // attachmentKeys 310 } 311 312 outReq.KVs = append(outReq.KVs, labelMapKV) 313 } 314 315 return nil 316 } 317 318 func (u LcUser) createArtifact( 319 artifact Artifact, 320 status meta.Status, 321 attach []string, 322 bomText string, 323 ) (bool, uint64, error) { 324 325 var setRequest immuschema.SetRequest 326 if err := u.artifactToSetRequest( 327 &artifact, status, attach, bomText, &setRequest); err != nil { 328 return false, 0, err 329 } 330 331 md := metadata.Pairs( 332 meta.VcnLCPluginTypeHeaderName, meta.VcnLCPluginTypeHeaderValue, 333 meta.VcnLCCmdHeaderName, meta.VcnLCNotarizeCmdHeaderValue, 334 ) 335 ctx := metadata.NewOutgoingContext(context.Background(), md) 336 337 txMeta, err := u.Client.SetAll(ctx, &setRequest) 338 if err != nil { 339 return false, 0, err 340 } 341 return true, txMeta.Id, nil 342 } 343 344 func (u LcUser) createArtifacts( 345 artifacts []*Artifact, 346 statuses []meta.Status, 347 attachments [][]string, 348 bomTexts []string, 349 ) ([]bool, []uint64, []error, error) { 350 351 if len(artifacts) != len(statuses) || len(artifacts) != len(attachments) || len(artifacts) != len(bomTexts) { 352 return nil, nil, nil, errors.New( 353 "artifacts, statuses, attachments and bomTexts must have the same length") 354 } 355 356 verifieds := make([]bool, len(artifacts)) 357 txIDs := make([]uint64, len(artifacts)) 358 errs := make([]error, len(artifacts)) 359 360 for i := 0; i < len(artifacts); i++ { 361 var setRequest immuschema.SetRequest 362 if err := u.artifactToSetRequest( 363 artifacts[i], statuses[i], attachments[i], bomTexts[i], &setRequest); err != nil { 364 return nil, nil, nil, err 365 } 366 367 md := metadata.Pairs( 368 meta.VcnLCPluginTypeHeaderName, meta.VcnLCPluginTypeHeaderValue, 369 meta.VcnLCCmdHeaderName, meta.VcnLCNotarizeCmdHeaderValue, 370 ) 371 ctx := metadata.NewOutgoingContext(context.Background(), md) 372 373 txMeta, err := u.Client.SetAll(ctx, &setRequest) 374 if err != nil { 375 errs[i] = err 376 continue 377 } 378 379 verifieds[i] = true 380 txIDs[i] = txMeta.Id 381 } 382 383 return verifieds, txIDs, errs, nil 384 } 385 386 // LoadArtifact fetches and returns an *lcArtifact for the given hash and current u, if any. 387 func (u *LcUser) LoadArtifact( 388 hash, signerID string, 389 uid string, 390 tx uint64, 391 gRPCMetadata map[string][]string, 392 ) (lc *LcArtifact, verified bool, err error) { 393 394 md := metadata.Pairs(meta.VcnLCPluginTypeHeaderName, meta.VcnLCPluginTypeHeaderValue) 395 if len(gRPCMetadata) > 0 { 396 md = metadata.Join(md, gRPCMetadata) 397 } 398 ctx := metadata.NewOutgoingContext(context.Background(), md) 399 400 if signerID == "" { 401 signerID = GetSignerIDByApiKey(u.Client.ApiKey) 402 } 403 404 key := AppendPrefix(meta.VcnPrefix, []byte(signerID)) 405 key = AppendSignerId(hash, key) 406 407 var jsonAr *schema.VerifiableItemExt 408 if uid != "" { 409 score, err := strconv.ParseFloat(uid, 64) 410 if err != nil { 411 return nil, false, err 412 } 413 zitems, err := u.Client.ZScanExt(ctx, &immuschema.ZScanRequest{ 414 Set: key, 415 SeekScore: math.MaxFloat64, 416 SeekAtTx: tx, 417 Limit: 1, 418 MinScore: &immuschema.Score{Score: score}, 419 MaxScore: &immuschema.Score{Score: score}, 420 SinceTx: math.MaxUint64, 421 NoWait: true, 422 }) 423 if err != nil { 424 return nil, false, err 425 } 426 if len(zitems.Items) > 0 { 427 jsonAr, err = u.Client.VerifiedGetExtAt(ctx, zitems.Items[0].Item.Key, zitems.Items[0].Item.AtTx) 428 } else { 429 return nil, false, ErrNotFound 430 } 431 } else { 432 jsonAr, err = u.Client.VerifiedGetExtAt(ctx, key, tx) 433 } 434 if err != nil { 435 s, ok := status.FromError(err) 436 if ok && s.Message() == "data is corrupted" { 437 return nil, false, ErrNotVerified 438 } 439 if err.Error() == "data is corrupted" { 440 return nil, false, ErrNotVerified 441 } 442 if ok && s.Message() == "key not found" { 443 return nil, false, ErrNotFound 444 } 445 return nil, true, err 446 } 447 448 lcArtifact, err := VerifiableItemExtToLcArtifact(jsonAr) 449 if err != nil { 450 return nil, false, err 451 } 452 453 return lcArtifact, true, nil 454 } 455 456 // LoadArtifacts fetches and returns multiple *lcArtifact for the given hashes and current u, if any. 457 func (u *LcUser) LoadArtifacts( 458 signerID string, 459 hashes []string, 460 gRPCMetadata map[string][]string, 461 ) (artifacts []*LcArtifact, verified []bool, errs []error, err error) { 462 463 md := metadata.Pairs(meta.VcnLCPluginTypeHeaderName, meta.VcnLCPluginTypeHeaderValue) 464 if len(gRPCMetadata) > 0 { 465 md = metadata.Join(md, gRPCMetadata) 466 } 467 ctx := metadata.NewOutgoingContext(context.Background(), md) 468 469 if signerID == "" { 470 signerID = GetSignerIDByApiKey(u.Client.ApiKey) 471 } 472 473 prefixedSignerID := AppendPrefix(meta.VcnPrefix, []byte(signerID)) 474 475 keys := make([][]byte, 0, len(hashes)) 476 for _, hash := range hashes { 477 key := AppendSignerId(hash, prefixedSignerID) 478 keys = append(keys, key) 479 } 480 481 itemsExt, errsMsgs, err := u.Client.VerifiedGetExtAtMulti(ctx, keys, make([]uint64, len(keys))) 482 if err != nil { 483 return nil, nil, nil, err 484 } 485 486 if len(itemsExt) != len(keys) || len(errsMsgs) != len(keys) { 487 return nil, nil, nil, fmt.Errorf( 488 "internal logic error: expected size of the reponse %d, got %d items and %d errors", 489 len(keys), len(itemsExt), len(errsMsgs)) 490 } 491 492 lcArtifacts := make([]*LcArtifact, len(itemsExt)) 493 verified = make([]bool, len(itemsExt)) 494 errs = make([]error, len(itemsExt)) 495 496 for i := 0; i < len(keys); i++ { 497 if len(errsMsgs[i]) > 0 { 498 switch { 499 case strings.HasSuffix(errsMsgs[i], "data is corrupted"): 500 errs[i] = ErrNotVerified 501 case strings.HasSuffix(errsMsgs[i], "key not found"): 502 errs[i] = ErrNotFound 503 default: 504 errs[i] = errors.New(errsMsgs[i]) 505 verified[i] = true 506 } 507 continue 508 } 509 510 lcArtifact, err := VerifiableItemExtToLcArtifact(itemsExt[i]) 511 if err != nil { 512 return nil, nil, nil, err 513 } 514 lcArtifacts[i] = lcArtifact 515 verified[i] = true 516 } 517 518 return lcArtifacts, verified, errs, nil 519 } 520 521 // GetArtifactAttachmentListByLabel returns the attachment list of an artifact and the most recent uid by a provided label and signerID 522 // When there are multiple attachments with same file name it adds an enumerator postfix. 523 func (u *LcUser) GetArtifactAttachmentListByLabel(hash string, signerID, label string) ([]Attachment, string, error) { 524 if label == "" { 525 return nil, "", errors.New("no attachment provided") 526 } 527 if hash == "" { 528 return nil, "", errors.New("no artifact provided") 529 } 530 var attachmentList []Attachment 531 var uid string 532 attachmentMap, err := u.fetchAttachmentMapByLabel(hash, signerID, label) 533 if err != nil { 534 return nil, "", err 535 } 536 // map order is not guaranted so here obtain a sorted string array 537 var attachDriver []string 538 for k, _ := range attachmentMap { 539 attachDriver = append(attachDriver, k) 540 } 541 sort.Strings(attachDriver) 542 // reverse the driver 543 last := len(attachDriver) - 1 544 for i := 0; i < len(attachDriver)/2; i++ { 545 attachDriver[i], attachDriver[last-i] = attachDriver[last-i], attachDriver[i] 546 } 547 // attachmentFileNameMap is used internally to produce a map to handle attachments with same name 548 attachmentFileNameMap := make(map[string][]*Attachment) 549 550 for _, k := range attachDriver { 551 attachMapEntry := attachmentMap[k] 552 // latest uid, needed to authenticate the latest notarized artifact 553 if uid == "" { 554 uid = k 555 } 556 for _, att := range attachMapEntry { 557 fn := att.Filename 558 if _, ok := attachmentFileNameMap[fn]; ok && len(attachmentFileNameMap[fn]) > 0 { 559 // if there is a newer filename here a postfix is added. ~1,~2 ... ~N 560 att.Filename = fn + "~" + strconv.Itoa(len(attachmentFileNameMap[fn])) 561 } 562 attachmentFileNameMap[fn] = append(attachmentFileNameMap[fn], att) 563 // attachmentList contains all attachments with latest first order 564 attachmentList = append(attachmentList, *att) 565 } 566 } 567 return attachmentList, uid, nil 568 } 569 570 func (u *LcUser) fetchAttachmentMapByLabel(hash, signerID string, attach string) (map[string][]*Attachment, error) { 571 572 md := metadata.Pairs(meta.VcnLCPluginTypeHeaderName, meta.VcnLCPluginTypeHeaderValue) 573 ctx := metadata.NewOutgoingContext(context.Background(), md) 574 575 if signerID == "" { 576 signerID = GetSignerIDByApiKey(u.Client.ApiKey) 577 } 578 579 key := meta.VcnAttachmentLabelPrefix + "." + signerID + "." + hash + "." + attach 580 581 /* _ITEM.ATTACH.LABEL.myApiKey.{arifact hash}.vscanner.result:jobid123 */ 582 /* _ITEM.ATTACH.LABEL.myApiKey.{arifact hash}.jobid123 */ 583 sr := &immuschema.ScanRequest{ 584 Prefix: []byte(key), 585 SinceTx: math.MaxUint64, 586 NoWait: true, 587 Desc: true, 588 } 589 590 res, err := u.Client.Scan(ctx, sr) 591 if err != nil { 592 return nil, err 593 } 594 if len(res.Entries) < 1 { 595 return nil, errors.New("provided label does not contains entries") 596 } 597 598 attachMap := make(map[string][]*Attachment) 599 600 for _, entry := range res.Entries { 601 // ori reg ex _ITEM\.ATTACH\.LABEL\.[^.]+\.[^.]+\.(\S+:\S[^.]+|\S+)\.([0-9]+) 602 var regex = regexp.MustCompile("_ITEM\\.ATTACH\\.LABEL\\.[^.]+\\.[^.]+\\.(\\S+:\\S[^.]+|\\S+)\\.([0-9]+)") 603 keyAndUid := regex.FindStringSubmatch(string(entry.Key)) 604 605 if len(keyAndUid) != 3 { 606 return nil, errors.New("not consistent data when retrieving uid from attachment label entry") 607 } 608 609 attachmentList := make([]*Attachment, 0) 610 err = json.Unmarshal(entry.Value, &attachmentList) 611 if err != nil { 612 return nil, err 613 } 614 attachMap[keyAndUid[2]] = attachmentList 615 } 616 617 return attachMap, nil 618 } 619 620 func AppendPrefix(prefix string, key []byte) []byte { 621 var prefixed = make([]byte, len(prefix)+1+len(key)) 622 copy(prefixed[0:], prefix+".") 623 copy(prefixed[len(prefix)+1:], key) 624 return prefixed 625 } 626 627 func AppendSignerId(signerId string, k []byte) []byte { 628 var prefixed = make([]byte, len(k)+len(signerId)+1) 629 copy(prefixed[0:], k) 630 copy(prefixed[len(k):], "."+signerId) 631 return prefixed 632 } 633 634 func AppendAttachment(attachHash string, key []byte) []byte { 635 //vcn.$AssetHash.Attachment.$AttachmentHash 636 var prefixed = make([]byte, len(attachHash)+len(meta.AttachmentSeparator)+len(key)) 637 copy(prefixed[0:], key) 638 copy(prefixed[len(key):], meta.AttachmentSeparator+attachHash) 639 return prefixed 640 } 641 642 func AppendLabel(label string, key []byte) []byte { 643 //vcn.$AssetHash.Attachment.$AttachmentHash 644 var prefixed = make([]byte, len(label)+len(meta.AttachmentSeparator)+len(key)) 645 copy(prefixed[0:], key) 646 copy(prefixed[len(key):], meta.AttachmentSeparator+label) 647 return prefixed 648 } 649 650 // DownloadAttachment download locally all the attachments linked to the assets 651 func (u *LcUser) DownloadAttachment(attach *Attachment, ar *LcArtifact, tx uint64, lcAttachForce bool) (err error) { 652 653 md := metadata.Pairs(meta.VcnLCPluginTypeHeaderName, meta.VcnLCPluginTypeHeaderValue) 654 ctx := metadata.NewOutgoingContext(context.Background(), md) 655 656 key := AppendPrefix(meta.VcnPrefix, []byte(ar.Signer)) 657 key = AppendSignerId(ar.Hash, key) 658 attachmentKey := AppendAttachment(attach.Hash, key) 659 660 attachEntry, err := u.Client.VerifiedGetAt(ctx, attachmentKey, tx) 661 if err != nil { 662 return err 663 } 664 if _, err := os.Stat(attach.Filename); os.IsNotExist(err) || lcAttachForce { 665 return ioutil.WriteFile(attach.Filename, attachEntry.Value, 0644) 666 } 667 return fmt.Errorf("attachment %s already present on disk. Use --force to overwrite silently", attach.Filename) 668 } 669 670 // Date returns a RFC3339 formatted string of verification time (v.Timestamp), if any, otherwise an empty string. 671 func (lca *LcArtifact) Date() string { 672 if lca != nil { 673 ut := lca.Timestamp.UTC() 674 if ut.Unix() > 0 { 675 return ut.Format(time.RFC3339) 676 } 677 } 678 return "" 679 } 680 681 func compressFile(z *zip.Writer, name string) error { 682 src, err := os.Open(name) 683 if err != nil { 684 return err 685 } 686 defer src.Close() 687 fs, err := src.Stat() 688 if err != nil { 689 return err 690 } 691 header, err := zip.FileInfoHeader(fs) 692 if err != nil { 693 return err 694 } 695 header.Method = zip.Deflate // it is Store by default 696 dst, err := z.CreateHeader(header) // use of CreateHeader rather then just Create keeps file time and mode 697 if err != nil { 698 return err 699 } 700 if _, err = io.Copy(dst, src); err != nil { 701 return err 702 } 703 704 return nil 705 }