sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/crier/reporters/criercommonlib/updatereportstatus.go (about) 1 /* 2 Copyright 2022 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 criercommonlib contains shared lib used by reporters 18 package criercommonlib 19 20 import ( 21 "context" 22 "fmt" 23 "time" 24 25 "github.com/sirupsen/logrus" 26 "k8s.io/apimachinery/pkg/types" 27 "k8s.io/apimachinery/pkg/util/wait" 28 "k8s.io/client-go/util/retry" 29 30 ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" 31 32 prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 33 ) 34 35 func updateReportState(ctx context.Context, pj *prowv1.ProwJob, log *logrus.Entry, reportedState prowv1.ProwJobState, pjclientset ctrlruntimeclient.Client, reporterName string) error { 36 // update pj report status 37 newpj := pj.DeepCopy() 38 // we set omitempty on PrevReportStates, so here we need to init it if is nil 39 if newpj.Status.PrevReportStates == nil { 40 newpj.Status.PrevReportStates = map[string]prowv1.ProwJobState{} 41 } 42 newpj.Status.PrevReportStates[reporterName] = reportedState 43 44 if err := pjclientset.Patch(ctx, newpj, ctrlruntimeclient.MergeFrom(pj)); err != nil { 45 return fmt.Errorf("failed to patch: %w", err) 46 } 47 48 // Block until the update is in the lister to make sure that events from another controller 49 // that also does reporting dont trigger another report because our lister doesn't yet contain 50 // the updated Status 51 name := types.NamespacedName{Namespace: pj.Namespace, Name: pj.Name} 52 if err := wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) { 53 if err := pjclientset.Get(ctx, name, pj); err != nil { 54 return false, err 55 } 56 if pj.Status.PrevReportStates != nil && 57 pj.Status.PrevReportStates[reporterName] == reportedState { 58 return true, nil 59 } 60 return false, nil 61 }); err != nil { 62 return fmt.Errorf("failed to wait for updated report status to be in lister: %w", err) 63 } 64 return nil 65 } 66 67 func UpdateReportStateWithRetries(ctx context.Context, pj *prowv1.ProwJob, log *logrus.Entry, pjclientset ctrlruntimeclient.Client, reporterName string) error { 68 reportState := pj.Status.State 69 log = log.WithFields(logrus.Fields{ 70 "prowjob": pj.Name, 71 "jobName": pj.Spec.Job, 72 "jobStatus": reportState, 73 }) 74 // We have to retry here, if we return we lose the information that we already reported this job. 75 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 76 // Get it first, this is very cheap 77 name := types.NamespacedName{Namespace: pj.Namespace, Name: pj.Name} 78 if err := pjclientset.Get(ctx, name, pj); err != nil { 79 return err 80 } 81 // Must not wrap until we have kube 1.19, otherwise the RetryOnConflict won't recognize conflicts 82 // correctly 83 return updateReportState(ctx, pj, log, reportState, pjclientset, reporterName) 84 }); err != nil { 85 // Very subpar, we will report again. But even if we didn't do that now, we would do so 86 // latest when crier gets restarted. In an ideal world, all reporters are idempotent and 87 // reporting has no cost. 88 return fmt.Errorf("failed to update report state on prowjob: %w", err) 89 } 90 91 log.Info("Successfully updated report state on prowjob") 92 return nil 93 }