go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config_service/internal/validation/examine.go (about) 1 // Copyright 2023 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 validation 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "net/http" 22 "sort" 23 "strings" 24 "sync" 25 26 "cloud.google.com/go/storage" 27 "golang.org/x/sync/errgroup" 28 29 "go.chromium.org/luci/common/gcloud/gs" 30 "go.chromium.org/luci/common/logging" 31 "go.chromium.org/luci/config" 32 33 "go.chromium.org/luci/config_service/internal/common" 34 ) 35 36 // ExamineResult is the result of `Examine` method. 37 type ExamineResult struct { 38 // MissingFiles are files that don't have the corresponding GCS objects. 39 // 40 // Use the attached signed URL to instruct client to upload the config 41 // content. 42 MissingFiles []struct { 43 File File 44 SignedURL string 45 } 46 // UnvalidatableFiles are files that no service can validate. 47 // 48 // Those files SHOULD not be included in the validation request. 49 UnvalidatableFiles []File 50 } 51 52 // Passed return True if the config files passed the examination and can 53 // proceed to `Validate`. 54 func (er *ExamineResult) Passed() bool { 55 return er == nil || (len(er.MissingFiles) == 0 && len(er.UnvalidatableFiles) == 0) 56 } 57 58 var signedPutHeaders = map[string]string{ 59 "Content-Encoding": "gzip", 60 "x-goog-content-length-range": fmt.Sprintf("0,%d", common.ConfigMaxSize), 61 } 62 63 // Examine examines the configs files to ensure successful validation. 64 func (v *Validator) Examine(ctx context.Context, cs config.Set, files []File) (*ExamineResult, error) { 65 eg, ectx := errgroup.WithContext(ctx) 66 eg.SetLimit(8) 67 ret := &ExamineResult{} 68 var mu sync.Mutex 69 for _, file := range files { 70 file := file 71 eg.Go(func() error { 72 services := v.Finder.FindInterestedServices(ectx, cs, file.GetPath()) 73 if len(services) == 0 { 74 mu.Lock() 75 ret.UnvalidatableFiles = append(ret.UnvalidatableFiles, file) 76 mu.Unlock() 77 return nil 78 } 79 80 bucket, object := file.GetGSPath().Split() 81 switch err := v.GsClient.Touch(ectx, bucket, object); { 82 case errors.Is(err, context.Canceled): 83 logging.Warningf(ctx, "touching config file %q is cancelled", file.GetPath()) 84 return err 85 case errors.Is(err, storage.ErrObjectNotExist): 86 urls, err := common.CreateSignedURLs(ectx, v.GsClient, []gs.Path{file.GetGSPath()}, http.MethodPut, signedPutHeaders) 87 switch { 88 case errors.Is(err, context.Canceled): 89 logging.Warningf(ctx, "creating signed url for GS path %q is cancelled", file.GetGSPath()) 90 return err 91 case err != nil: 92 logging.Errorf(ctx, "failed to create signed url for GS path %q: %s", file.GetGSPath(), err) 93 return err 94 } 95 mu.Lock() 96 ret.MissingFiles = append(ret.MissingFiles, struct { 97 File File 98 SignedURL string 99 }{File: file, SignedURL: urls[0]}) 100 mu.Unlock() 101 return nil 102 case err != nil: 103 logging.Errorf(ctx, "failed to touch config file %q: %s", file.GetPath(), err) 104 return err 105 default: 106 return nil // file ready for validation. 107 } 108 }) 109 } 110 if err := eg.Wait(); err != nil { 111 return nil, err 112 } 113 sort.SliceStable(ret.MissingFiles, func(i, j int) bool { 114 return strings.Compare(ret.MissingFiles[i].File.GetPath(), ret.MissingFiles[j].File.GetPath()) < 0 115 }) 116 sort.SliceStable(ret.UnvalidatableFiles, func(i, j int) bool { 117 return strings.Compare(ret.UnvalidatableFiles[i].GetPath(), ret.UnvalidatableFiles[j].GetPath()) < 0 118 }) 119 return ret, nil 120 }