sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/moonraker/client.go (about) 1 /* 2 Copyright 2023 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 moonraker 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "net/http" 26 "net/url" 27 "sync" 28 "time" 29 30 "github.com/sirupsen/logrus" 31 32 "k8s.io/apimachinery/pkg/util/wait" 33 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 34 "sigs.k8s.io/prow/pkg/config" 35 "sigs.k8s.io/prow/pkg/version" 36 ) 37 38 // The the Client needs a Config agent client. Here we require that the Agent 39 // type fits the prowConfigAgentClient interface, which requires a Config() 40 // method to retrieve the current Config. Tests can use a fake Config agent 41 // instead of the real one. 42 var _ prowConfigAgentClient = (*config.Agent)(nil) 43 44 type prowConfigAgentClient interface { 45 Config() *config.Config 46 } 47 48 type Client struct { 49 host string 50 configAgent prowConfigAgentClient 51 52 sync.Mutex // protects below 53 httpClient *http.Client 54 } 55 56 func NewClient(host string, configAgent prowConfigAgentClient) (*Client, error) { 57 c := Client{ 58 host: host, 59 httpClient: &http.Client{ 60 Timeout: configAgent.Config().Moonraker.ClientTimeout.Duration, 61 }, 62 configAgent: configAgent, 63 } 64 65 // isMoonrakerUp is a ConditionFunc (see 66 // https://pkg.go.dev/k8s.io/apimachinery/pkg/util/wait#ConditionFunc). 67 isMoonrakerUp := func() (bool, error) { 68 if err := c.Ping(); err != nil { 69 return false, nil 70 } else { 71 return true, nil 72 } 73 } 74 75 pollLoopTimeout := 15 * time.Second 76 pollInterval := 500 * time.Millisecond 77 if err := wait.Poll(pollInterval, pollLoopTimeout, isMoonrakerUp); err != nil { 78 return nil, errors.New("timed out waiting for Moonraker to be available") 79 } 80 81 return &c, nil 82 } 83 84 func (c *Client) do(method, path string, payload []byte, params map[string]string) (*http.Response, error) { 85 baseURL, err := url.JoinPath(c.host, path) 86 if err != nil { 87 return nil, err 88 } 89 90 req, err := http.NewRequest(method, baseURL, bytes.NewBuffer(payload)) 91 if err != nil { 92 return nil, err 93 } 94 req.Header.Add("Content-Type", "application/json; charset=UTF-8") 95 req.Header.Add("User-Agent", version.UserAgent()) 96 q := req.URL.Query() 97 for key, val := range params { 98 q.Set(key, val) 99 } 100 req.URL.RawQuery = q.Encode() 101 return c.maybeUpdatedHttpClient().Do(req) 102 } 103 104 // maybeUpdatedHttpClient returns a new HTTP client with a new one (with 105 // a new timeout) if the provided timeout does not match the value already used 106 // for the current HTTP client. 107 func (c *Client) maybeUpdatedHttpClient() *http.Client { 108 c.Lock() 109 defer c.Unlock() 110 timeout := c.configAgent.Config().Moonraker.ClientTimeout.Duration 111 112 if c.httpClient.Timeout != timeout { 113 c.httpClient = &http.Client{Timeout: timeout} 114 } 115 116 return c.httpClient 117 } 118 119 func (c *Client) Ping() error { 120 resp, err := c.do(http.MethodGet, PathPing, nil, nil) 121 if err != nil { 122 return err 123 } 124 if resp.StatusCode == 200 { 125 return nil 126 } 127 128 return fmt.Errorf("invalid status code %d", resp.StatusCode) 129 } 130 131 // GetProwYAML returns the inrepoconfig contents for a repo, based on the Refs 132 // struct as the input. From the Refs, Moonraker can determine the org/repo, 133 // BaseSHA, and the Pulls[] (additional refs of each PR, if any) to grab the 134 // inrepoconfig contents. 135 func (c *Client) GetProwYAML(refs *prowapi.Refs) (*config.ProwYAML, error) { 136 payload := payload{ 137 Refs: *refs, 138 } 139 buf, err := json.Marshal(payload) 140 if err != nil { 141 return nil, fmt.Errorf("could not marshal %v", payload) 142 } 143 144 resp, err := c.do(http.MethodPost, PathGetInrepoconfig, buf, nil) 145 if err != nil { 146 return nil, err 147 } 148 if resp.StatusCode >= 300 { 149 return nil, fmt.Errorf("got %v response", resp.StatusCode) 150 } 151 152 body, err := io.ReadAll(resp.Body) 153 if err != nil { 154 return nil, err 155 } 156 157 prowYAML := config.ProwYAML{} 158 err = json.Unmarshal(body, &prowYAML) 159 if err != nil { 160 logrus.WithError(err).Info("unable to unmarshal getInrepoconfig request") 161 return nil, err 162 } 163 164 return &prowYAML, nil 165 } 166 167 // GetInRepoConfig just wraps around GetProwYAML(), converting the input 168 // parameters into a prowapi.Refs{} type. 169 // 170 // Importantly, it also does defaulting of the retrieved jobs. Defaulting is 171 // required because the Presubmit and Postsubmit job types have private fields 172 // in them that would not be serialized into JSON when sent over from the 173 // server. So the defaulting has to be done client-side. 174 func (c *Client) GetInRepoConfig(identifier, baseBranch string, baseSHAGetter config.RefGetter, headSHAGetters ...config.RefGetter) (*config.ProwYAML, error) { 175 refs := prowapi.Refs{} 176 177 orgRepo := config.NewOrgRepo(identifier) 178 refs.Org = orgRepo.Org 179 refs.Repo = orgRepo.Repo 180 181 var err error 182 refs.BaseSHA, err = baseSHAGetter() 183 if err != nil { 184 return nil, err 185 } 186 187 refs.BaseRef = baseBranch 188 189 pulls := []prowapi.Pull{} 190 for _, headSHAGetter := range headSHAGetters { 191 headSHA, err := headSHAGetter() 192 if err != nil { 193 return nil, err 194 } 195 pulls = append(pulls, prowapi.Pull{ 196 SHA: headSHA, 197 }) 198 } 199 refs.Pulls = pulls 200 201 prowYAML, err := c.GetProwYAML(&refs) 202 if err != nil { 203 return nil, err 204 } 205 206 cfg := c.configAgent.Config() 207 if err := config.DefaultAndValidateProwYAML(cfg, prowYAML, identifier); err != nil { 208 return nil, err 209 } 210 211 return prowYAML, nil 212 } 213 214 func (c *Client) GetPresubmits(identifier, baseBranch string, baseSHAGetter config.RefGetter, headSHAGetters ...config.RefGetter) ([]config.Presubmit, error) { 215 prowYAML, err := c.GetInRepoConfig(identifier, baseBranch, baseSHAGetter, headSHAGetters...) 216 if err != nil { 217 return nil, err 218 } 219 220 config := c.configAgent.Config() 221 return append(config.GetPresubmitsStatic(identifier), prowYAML.Presubmits...), nil 222 } 223 224 func (c *Client) GetPostsubmits(identifier, baseBranch string, baseSHAGetter config.RefGetter, headSHAGetters ...config.RefGetter) ([]config.Postsubmit, error) { 225 prowYAML, err := c.GetInRepoConfig(identifier, baseBranch, baseSHAGetter, headSHAGetters...) 226 if err != nil { 227 return nil, err 228 } 229 230 config := c.configAgent.Config() 231 return append(config.GetPostsubmitsStatic(identifier), prowYAML.Postsubmits...), nil 232 }