sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/statusreconciler/status.go (about) 1 /* 2 Copyright 2018 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 statusreconciler 18 19 import ( 20 "context" 21 stdio "io" 22 "time" 23 24 "github.com/sirupsen/logrus" 25 "sigs.k8s.io/yaml" 26 27 "sigs.k8s.io/prow/pkg/config" 28 configflagutil "sigs.k8s.io/prow/pkg/flagutil/config" 29 "sigs.k8s.io/prow/pkg/io" 30 ) 31 32 type storedState struct { 33 config.Config 34 } 35 36 type statusClient interface { 37 Load() (chan config.Delta, error) 38 Save() error 39 } 40 41 // opener has methods to read and write paths 42 type opener interface { 43 Reader(ctx context.Context, path string) (io.ReadCloser, error) 44 Writer(ctx context.Context, path string, opts ...io.WriterOptions) (io.WriteCloser, error) 45 } 46 47 type statusController struct { 48 logger *logrus.Entry 49 opener opener 50 statusURI string 51 configOpts configflagutil.ConfigOptions 52 53 storedState 54 config.Agent 55 } 56 57 func (s *statusController) Load() (chan config.Delta, error) { 58 s.Agent = config.Agent{} 59 state, err := s.loadState() 60 if err == nil { 61 s.Agent.Set(&state.Config) 62 } 63 changes := make(chan config.Delta) 64 s.Agent.Subscribe(changes) 65 66 if _, err := s.configOpts.ConfigAgent(&s.Agent); err != nil { 67 s.logger.WithError(err).Error("Error starting config agent.") 68 return nil, err 69 } 70 return changes, nil 71 } 72 73 func (s *statusController) Save() error { 74 if s.statusURI == "" { 75 return nil 76 } 77 entry := s.logger.WithField("path", s.statusURI) 78 current := s.Agent.Config() 79 buf, err := yaml.Marshal(current) 80 if err != nil { 81 entry.WithError(err).Warn("Cannot marshal state") 82 return err 83 } 84 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 85 defer cancel() 86 writer, err := s.opener.Writer(ctx, s.statusURI) 87 if err != nil { 88 entry.WithError(err).Warn("Cannot open state writer") 89 return err 90 } 91 if _, err = writer.Write(buf); err != nil { 92 entry.WithError(err).Warn("Cannot write state") 93 io.LogClose(writer) 94 return err 95 } 96 if err := writer.Close(); err != nil { 97 entry.WithError(err).Warn("Failed to close written state") 98 } 99 entry.Debug("Saved status state") 100 return nil 101 } 102 103 func (s *statusController) loadState() (storedState, error) { 104 var state storedState 105 if s.statusURI == "" { 106 s.logger.Debug("No stored state configured") 107 return state, nil 108 } 109 entry := s.logger.WithField("path", s.statusURI) 110 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 111 defer cancel() 112 reader, err := s.opener.Reader(ctx, s.statusURI) 113 if err != nil { 114 entry.WithError(err).Warn("Cannot open stored state") 115 return state, err 116 } 117 defer io.LogClose(reader) 118 119 buf, err := stdio.ReadAll(reader) 120 if err != nil { 121 entry.WithError(err).Warn("Cannot read stored state") 122 return state, err 123 } 124 125 if err := yaml.Unmarshal(buf, &state); err != nil { 126 entry.WithError(err).Warn("Cannot unmarshal stored state") 127 return state, err 128 } 129 return state, nil 130 } 131 132 func (s *statusController) Config() *config.Config { 133 return s.Agent.Config() 134 }