sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/clonerefs/run_test.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 clonerefs 18 19 import ( 20 "crypto/rand" 21 "crypto/rsa" 22 "crypto/x509" 23 "encoding/json" 24 "encoding/pem" 25 "fmt" 26 "net/http" 27 "net/http/httptest" 28 "os" 29 "path" 30 "path/filepath" 31 "reflect" 32 "sync" 33 "testing" 34 "time" 35 36 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 37 "sigs.k8s.io/prow/pkg/github" 38 "sigs.k8s.io/prow/pkg/pod-utils/clone" 39 ) 40 41 func TestRun(t *testing.T) { 42 srcRoot := t.TempDir() 43 44 oauthTokenDir := t.TempDir() 45 oauthTokenFilePath := filepath.Join(oauthTokenDir, "oauth-token") 46 oauthTokenValue := []byte("12345678") 47 if err := os.WriteFile(oauthTokenFilePath, oauthTokenValue, 0644); err != nil { 48 t.Fatalf("Error while create oauth token file: %v", err) 49 } 50 51 githubAppDir := t.TempDir() 52 githubAppPrivateKeyFilePath := filepath.Join(githubAppDir, "private-key.pem") 53 privateKey, err := rsa.GenerateKey(rand.Reader, 4096) 54 if err != nil { 55 t.Fatalf("Error while create github app private key file: %v", err) 56 } 57 githubAppPrivateKeyValue := pem.EncodeToMemory(&pem.Block{ 58 Type: "RSA PRIVATE KEY", 59 Bytes: x509.MarshalPKCS1PrivateKey(privateKey), 60 }) 61 if err := os.WriteFile(githubAppPrivateKeyFilePath, githubAppPrivateKeyValue, 0644); err != nil { 62 t.Fatalf("Error while create github app private key file: %v", err) 63 } 64 65 githubAppOrg := "kubernetes" 66 githubAppToken := "github-app-token" 67 mockGitHubAppServer := httptest.NewServer(mockGitHubAppHandler(githubAppOrg, githubAppToken)) 68 defer mockGitHubAppServer.Close() 69 70 type cloneRec struct { 71 refs prowapi.Refs 72 root string 73 user, email string 74 cookiePath string 75 env []string 76 authUser string 77 authToken string 78 authError error 79 } 80 81 var recordedClones []cloneRec 82 var lock sync.Mutex 83 cloneFuncOld := cloneFunc 84 cloneFunc = func(refs prowapi.Refs, root, user, email, cookiePath string, env []string, userGenerator github.UserGenerator, tokenGenerator github.TokenGenerator) clone.Record { 85 lock.Lock() 86 defer lock.Unlock() 87 var ( 88 authUser string 89 authToken string 90 authError error 91 ) 92 if userGenerator != nil { 93 user, err := userGenerator() 94 if err != nil { 95 authError = err 96 } 97 authUser = user 98 } 99 if tokenGenerator != nil { 100 token, err := tokenGenerator(refs.Org) 101 if err != nil { 102 authError = err 103 } 104 authToken = token 105 } 106 recordedClones = append(recordedClones, cloneRec{ 107 refs: refs, 108 root: root, 109 user: user, 110 email: email, 111 cookiePath: cookiePath, 112 env: env, 113 authUser: authUser, 114 authToken: authToken, 115 authError: authError, 116 }) 117 return clone.Record{} 118 } 119 defer func() { cloneFunc = cloneFuncOld }() 120 121 testcases := []struct { 122 name string 123 opts Options 124 expectedClones []cloneRec 125 }{ 126 { 127 name: "single PR clone", 128 opts: Options{ 129 SrcRoot: srcRoot, 130 Log: path.Join(srcRoot, "log.txt"), 131 GitUserName: "me", 132 GitUserEmail: "me@domain.com", 133 CookiePath: "cookies/path", 134 GitRefs: []prowapi.Refs{ 135 { 136 Org: "kubernetes", 137 Repo: "test-infra", 138 BaseRef: "master", 139 PathAlias: "sigs.k8s.io/prow", 140 Pulls: []prowapi.Pull{ 141 { 142 Number: 5, 143 SHA: "FEEDDAD", 144 }, 145 }, 146 SkipSubmodules: true, 147 }, 148 }, 149 }, 150 expectedClones: []cloneRec{ 151 { 152 refs: prowapi.Refs{ 153 Org: "kubernetes", 154 Repo: "test-infra", 155 BaseRef: "master", 156 PathAlias: "sigs.k8s.io/prow", 157 Pulls: []prowapi.Pull{ 158 { 159 Number: 5, 160 SHA: "FEEDDAD", 161 }, 162 }, 163 SkipSubmodules: true, 164 }, 165 root: srcRoot, 166 user: "me", 167 email: "me@domain.com", 168 cookiePath: "cookies/path", 169 }, 170 }, 171 }, 172 { 173 name: "multi repo clone", 174 opts: Options{ 175 Log: path.Join(srcRoot, "log.txt"), 176 GitRefs: []prowapi.Refs{ 177 { 178 Org: "kubernetes", 179 Repo: "test-infra", 180 BaseRef: "master", 181 PathAlias: "sigs.k8s.io/prow", 182 Pulls: []prowapi.Pull{ 183 { 184 Number: 5, 185 SHA: "FEEDDAD", 186 }, 187 }, 188 }, 189 { 190 Org: "kubernetes", 191 Repo: "release", 192 BaseRef: "master", 193 PathAlias: "k8s.io/release", 194 }, 195 }, 196 }, 197 expectedClones: []cloneRec{ 198 { 199 refs: prowapi.Refs{ 200 Org: "kubernetes", 201 Repo: "test-infra", 202 BaseRef: "master", 203 PathAlias: "sigs.k8s.io/prow", 204 Pulls: []prowapi.Pull{ 205 { 206 Number: 5, 207 SHA: "FEEDDAD", 208 }, 209 }, 210 }, 211 }, 212 { 213 refs: prowapi.Refs{ 214 Org: "kubernetes", 215 Repo: "release", 216 BaseRef: "master", 217 PathAlias: "k8s.io/release", 218 }, 219 }, 220 }, 221 }, 222 { 223 name: "single PR clone with oauth token", 224 opts: Options{ 225 OauthTokenFile: oauthTokenFilePath, 226 SrcRoot: srcRoot, 227 Log: path.Join(srcRoot, "log.txt"), 228 GitUserName: "me", 229 GitUserEmail: "me@domain.com", 230 CookiePath: "cookies/path", 231 GitRefs: []prowapi.Refs{ 232 { 233 Org: "kubernetes", 234 Repo: "test-infra", 235 BaseRef: "master", 236 PathAlias: "sigs.k8s.io/prow", 237 Pulls: []prowapi.Pull{ 238 { 239 Number: 5, 240 SHA: "FEEDDAD", 241 }, 242 }, 243 SkipSubmodules: true, 244 }, 245 }, 246 }, 247 expectedClones: []cloneRec{ 248 { 249 refs: prowapi.Refs{ 250 Org: "kubernetes", 251 Repo: "test-infra", 252 BaseRef: "master", 253 PathAlias: "sigs.k8s.io/prow", 254 Pulls: []prowapi.Pull{ 255 { 256 Number: 5, 257 SHA: "FEEDDAD", 258 }, 259 }, 260 SkipSubmodules: true, 261 }, 262 root: srcRoot, 263 user: "me", 264 email: "me@domain.com", 265 cookiePath: "cookies/path", 266 authToken: "12345678", 267 }, 268 }, 269 }, 270 { 271 name: "single PR clone with GitHub App", 272 opts: Options{ 273 GitHubAPIEndpoints: []string{ 274 mockGitHubAppServer.URL, 275 }, 276 GitHubAppID: "123456", 277 GitHubAppPrivateKeyFile: githubAppPrivateKeyFilePath, 278 SrcRoot: srcRoot, 279 Log: path.Join(srcRoot, "log.txt"), 280 GitUserName: "me", 281 GitUserEmail: "me@domain.com", 282 CookiePath: "cookies/path", 283 GitRefs: []prowapi.Refs{ 284 { 285 Org: githubAppOrg, 286 Repo: "test-infra", 287 BaseRef: "master", 288 PathAlias: "sigs.k8s.io/prow", 289 Pulls: []prowapi.Pull{ 290 { 291 Number: 5, 292 SHA: "FEEDDAD", 293 }, 294 }, 295 SkipSubmodules: true, 296 }, 297 }, 298 }, 299 expectedClones: []cloneRec{ 300 { 301 refs: prowapi.Refs{ 302 Org: "kubernetes", 303 Repo: "test-infra", 304 BaseRef: "master", 305 PathAlias: "sigs.k8s.io/prow", 306 Pulls: []prowapi.Pull{ 307 { 308 Number: 5, 309 SHA: "FEEDDAD", 310 }, 311 }, 312 SkipSubmodules: true, 313 }, 314 root: srcRoot, 315 user: "me", 316 email: "me@domain.com", 317 cookiePath: "cookies/path", 318 authUser: "x-access-token", 319 authToken: githubAppToken, 320 }, 321 }, 322 }, 323 } 324 for _, tc := range testcases { 325 t.Run(tc.name, func(t *testing.T) { 326 defer func() { recordedClones = nil }() 327 os.RemoveAll(srcRoot) 328 os.MkdirAll(srcRoot, os.ModePerm) 329 330 if err := tc.opts.Run(); err != nil { 331 t.Fatalf("Unexpected error: %v.", err) 332 } 333 334 // Check for set equality (ignore ordering) 335 for _, rec := range recordedClones { 336 found := false 337 var exp cloneRec 338 for _, exp = range tc.expectedClones { 339 if reflect.DeepEqual(rec, exp) { 340 found = true 341 break 342 } 343 } 344 if !found { 345 t.Errorf("recordedClones %#v is missing expected clone %#v", recordedClones, exp) 346 } 347 } 348 if rec, exp := len(recordedClones), len(tc.expectedClones); rec != exp { 349 t.Errorf("recordedClones has length %d and expectedClones has length %d", rec, exp) 350 } 351 }) 352 } 353 } 354 355 func TestNeedsGlobalCookiePath(t *testing.T) { 356 cases := []struct { 357 name string 358 cookieFile string 359 refs []prowapi.Refs 360 expected string 361 }{ 362 { 363 name: "basically works", 364 }, 365 { 366 name: "return empty when no cookieFile", 367 refs: []prowapi.Refs{ 368 {}, 369 }, 370 }, 371 { 372 name: "return empty when no refs", 373 cookieFile: "foo", 374 }, 375 { 376 name: "return empty when all refs skip submodules", 377 cookieFile: "foo", 378 refs: []prowapi.Refs{ 379 {SkipSubmodules: true}, 380 {SkipSubmodules: true}, 381 }, 382 }, 383 { 384 name: "return cookieFile when all refs use submodules", 385 cookieFile: "foo", 386 refs: []prowapi.Refs{ 387 {}, 388 {}, 389 }, 390 expected: "foo", 391 }, 392 { 393 name: "return cookieFile when any refs uses submodules", 394 cookieFile: "foo", 395 refs: []prowapi.Refs{ 396 {SkipSubmodules: true}, 397 {}, 398 }, 399 expected: "foo", 400 }, 401 } 402 403 for _, tc := range cases { 404 t.Run(tc.name, func(t *testing.T) { 405 if actual := needsGlobalCookiePath(tc.cookieFile, tc.refs...); actual != tc.expected { 406 t.Errorf("needsGlobalCookiePath(%q,%v) got %q, want %q", tc.cookieFile, tc.refs, actual, tc.expected) 407 } 408 }) 409 } 410 } 411 412 func mockGitHubAppHandler(org, token string) http.Handler { 413 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 414 switch r.URL.Path { 415 case "/app": 416 json.NewEncoder(w).Encode(github.App{ 417 Slug: "slug", 418 }) 419 case "/app/installations": 420 json.NewEncoder(w).Encode([]github.AppInstallation{ 421 { 422 ID: 1, 423 Account: github.User{ 424 Login: org, 425 }, 426 }, 427 }) 428 case "/app/installations/1/access_tokens": 429 w.WriteHeader(http.StatusCreated) 430 json.NewEncoder(w).Encode(&github.AppInstallationToken{ 431 Token: token, 432 ExpiresAt: time.Now().Add(time.Minute), 433 }) 434 default: 435 fmt.Println(r.URL.Path) 436 w.WriteHeader(http.StatusNotFound) 437 } 438 }) 439 }