github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/pjutil/abort.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package pjutil 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "sort" 24 "strings" 25 26 jsonpatch "github.com/evanphx/json-patch" 27 "github.com/sirupsen/logrus" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 ktypes "k8s.io/apimachinery/pkg/types" 30 ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" 31 32 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 33 reporter "sigs.k8s.io/prow/pkg/crier/reporters/github" 34 ) 35 36 // patchClient a minimalistic prow client required by the aborter 37 type patchClient interface { 38 Patch(ctx context.Context, obj ctrlruntimeclient.Object, patch ctrlruntimeclient.Patch, opts ...ctrlruntimeclient.PatchOption) error 39 } 40 41 // prowClient a minimalistic prow client required by the aborter 42 type prowClient interface { 43 Patch(ctx context.Context, name string, pt ktypes.PatchType, data []byte, o metav1.PatchOptions, subresources ...string) (result *prowapi.ProwJob, err error) 44 } 45 46 // digestRefs digests a Refs to the fields we care about 47 // for termination, ensuring that permutations of pulls 48 // do not cause different digests 49 func digestRefs(ref prowapi.Refs) string { 50 var pulls []int 51 for _, pull := range ref.Pulls { 52 pulls = append(pulls, pull.Number) 53 } 54 sort.Ints(pulls) 55 return fmt.Sprintf("%s/%s@%s %v", ref.Org, ref.Repo, ref.BaseRef, pulls) 56 } 57 58 // TerminateOlderJobs aborts all presubmit jobs from the given list that have a newer version. It does not set 59 // the prowjob to complete. The responsible agent is expected to react to the aborted state by aborting the actual 60 // test payload and then setting the ProwJob to completed. 61 func TerminateOlderJobs(pjc patchClient, log *logrus.Entry, pjs []prowapi.ProwJob) error { 62 dupes := map[string]int{} 63 for i, pj := range pjs { 64 if pj.Complete() || pj.Spec.Type != prowapi.PresubmitJob { 65 continue 66 } 67 68 // we want to use salient fields of the job spec to create 69 // an identifier, so we digest the job spec and to ensure 70 // reentrancy, we must sort all of the slices in our identifier 71 // so that equivalent permutations of the refs map to the 72 // same identifier. We do not want commit hashes to matter 73 // here as a test for a newer set of commits but for the 74 // same set of names can abort older versions. We digest 75 // into strings as Go doesn't define equality for slices, 76 // so they are not valid to use in map keys. 77 identifiers := []string{ 78 string(pj.Spec.Type), 79 pj.Spec.Job, 80 } 81 if pj.Spec.Refs != nil { 82 identifiers = append(identifiers, digestRefs(*pj.Spec.Refs)) 83 } 84 for _, ref := range pj.Spec.ExtraRefs { 85 identifiers = append(identifiers, digestRefs(ref)) 86 } 87 88 sort.Strings(identifiers) 89 ji := strings.Join(identifiers, ",") 90 prev, ok := dupes[ji] 91 if !ok { 92 dupes[ji] = i 93 continue 94 } 95 cancelIndex := i 96 if (&pjs[prev].Status.StartTime).Before(&pj.Status.StartTime) { 97 cancelIndex = prev 98 dupes[ji] = i 99 } 100 toCancel := pjs[cancelIndex] 101 prevPJ := toCancel.DeepCopy() 102 103 toCancel.Status.State = prowapi.AbortedState 104 if toCancel.Status.PrevReportStates == nil { 105 toCancel.Status.PrevReportStates = map[string]prowapi.ProwJobState{} 106 } 107 toCancel.Status.PrevReportStates[reporter.GitHubReporterName] = toCancel.Status.State 108 109 log.WithFields(ProwJobFields(&toCancel)). 110 WithField("from", prevPJ.Status.State). 111 WithField("to", toCancel.Status.State).Info("Transitioning states") 112 113 if err := pjc.Patch(context.Background(), &toCancel, ctrlruntimeclient.MergeFrom(prevPJ)); err != nil { 114 return err 115 } 116 117 // Update the cancelled jobs entry in pjs. 118 pjs[cancelIndex] = toCancel 119 } 120 121 return nil 122 } 123 124 func PatchProwjob(ctx context.Context, pjc prowClient, log *logrus.Entry, srcPJ prowapi.ProwJob, destPJ prowapi.ProwJob) (*prowapi.ProwJob, error) { 125 srcPJData, err := json.Marshal(srcPJ) 126 if err != nil { 127 return nil, fmt.Errorf("marshal source prow job: %w", err) 128 } 129 130 destPJData, err := json.Marshal(destPJ) 131 if err != nil { 132 return nil, fmt.Errorf("marshal dest prow job: %w", err) 133 } 134 135 patch, err := jsonpatch.CreateMergePatch(srcPJData, destPJData) 136 if err != nil { 137 return nil, fmt.Errorf("cannot create JSON patch: %w", err) 138 } 139 140 newPJ, err := pjc.Patch(ctx, srcPJ.Name, ktypes.MergePatchType, patch, metav1.PatchOptions{}) 141 log.WithFields(ProwJobFields(&destPJ)).Debug("Patched ProwJob.") 142 return newPJ, err 143 }