go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/appengine/ui/instance.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ui 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/dustin/go-humanize" 22 "google.golang.org/grpc/codes" 23 "google.golang.org/grpc/status" 24 25 "go.chromium.org/luci/common/clock" 26 "go.chromium.org/luci/common/sync/parallel" 27 "go.chromium.org/luci/server/router" 28 "go.chromium.org/luci/server/templates" 29 30 api "go.chromium.org/luci/cipd/api/cipd/v1" 31 "go.chromium.org/luci/cipd/common" 32 ) 33 34 func instancePage(c *router.Context, pkg, ver string) error { 35 pkg = strings.Trim(pkg, "/") 36 if err := common.ValidatePackageName(pkg); err != nil { 37 return status.Errorf(codes.InvalidArgument, "%s", err) 38 } 39 if err := common.ValidateInstanceVersion(ver); err != nil { 40 return status.Errorf(codes.InvalidArgument, "%s", err) 41 } 42 43 svc := state(c.Request.Context()).services 44 45 // Resolve the version first (even if is already IID). This also checks ACLs 46 // and verifies the instance exists. 47 inst, err := svc.PublicRepo.ResolveVersion(c.Request.Context(), &api.ResolveVersionRequest{ 48 Package: pkg, 49 Version: ver, 50 }) 51 if err != nil { 52 return err 53 } 54 55 // Do the rest in parallel. There can be only transient errors returned here, 56 // so collect them all into single Internal error. 57 var desc *api.DescribeInstanceResponse 58 var md *api.ListMetadataResponse 59 var url *api.ObjectURL 60 err = parallel.FanOutIn(func(tasks chan<- func() error) { 61 tasks <- func() (err error) { 62 desc, err = svc.PublicRepo.DescribeInstance(c.Request.Context(), &api.DescribeInstanceRequest{ 63 Package: inst.Package, 64 Instance: inst.Instance, 65 DescribeRefs: true, 66 DescribeTags: true, 67 DescribeProcessors: true, 68 }) 69 return 70 } 71 tasks <- func() (err error) { 72 md, err = svc.PublicRepo.ListMetadata(c.Request.Context(), &api.ListMetadataRequest{ 73 Package: inst.Package, 74 Instance: inst.Instance, 75 }) 76 return 77 } 78 tasks <- func() (err error) { 79 name := "" 80 chunks := strings.Split(pkg, "/") 81 if len(chunks) > 1 { 82 name = fmt.Sprintf("%s-%s", chunks[len(chunks)-2], chunks[len(chunks)-1]) 83 } else { 84 name = chunks[0] 85 } 86 url, err = svc.InternalCAS.GetObjectURL(c.Request.Context(), &api.GetObjectURLRequest{ 87 Object: inst.Instance, 88 DownloadFilename: name + ".zip", 89 }) 90 return 91 } 92 }) 93 if err != nil { 94 return status.Errorf(codes.Internal, "%s", err) 95 } 96 97 now := clock.Now(c.Request.Context()) 98 templates.MustRender(c.Request.Context(), c.Writer, "pages/instance.html", map[string]any{ 99 "Package": pkg, 100 "Version": ver, 101 "InstanceID": common.ObjectRefToInstanceID(inst.Instance), 102 "Breadcrumbs": breadcrumbs(pkg, ver, true), 103 "HashAlgo": inst.Instance.HashAlgo.String(), 104 "HexDigest": inst.Instance.HexDigest, 105 "DownloadURL": url.SignedUrl, 106 "Uploader": strings.TrimPrefix(inst.RegisteredBy, "user:"), 107 "Age": humanize.RelTime(inst.RegisteredTs.AsTime(), now, "", ""), 108 "Refs": refsListing(desc.Refs, pkg, now), 109 "Tags": tagsListing(desc.Tags, pkg, now), 110 "Metadata": instanceMetadataListing(md.Metadata, now), 111 }) 112 return nil 113 }