k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/git_repo/git_repo_test.go (about) 1 /* 2 Copyright 2014 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 git_repo 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path" 24 "path/filepath" 25 "reflect" 26 "strings" 27 "testing" 28 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/kubernetes/pkg/volume" 33 "k8s.io/kubernetes/pkg/volume/emptydir" 34 volumetest "k8s.io/kubernetes/pkg/volume/testing" 35 "k8s.io/utils/exec" 36 fakeexec "k8s.io/utils/exec/testing" 37 ) 38 39 func newTestHost(t *testing.T) (string, volume.VolumeHost) { 40 tempDir, err := ioutil.TempDir("", "git_repo_test.") 41 if err != nil { 42 t.Fatalf("can't make a temp rootdir: %v", err) 43 } 44 return tempDir, volumetest.NewFakeVolumeHost(t, tempDir, nil, emptydir.ProbeVolumePlugins()) 45 } 46 47 func TestCanSupport(t *testing.T) { 48 plugMgr := volume.VolumePluginMgr{} 49 tempDir, host := newTestHost(t) 50 defer os.RemoveAll(tempDir) 51 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 52 53 plug, err := plugMgr.FindPluginByName("kubernetes.io/git-repo") 54 if err != nil { 55 t.Fatal("Can't find the plugin by name") 56 } 57 if plug.GetPluginName() != "kubernetes.io/git-repo" { 58 t.Errorf("Wrong name: %s", plug.GetPluginName()) 59 } 60 if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{GitRepo: &v1.GitRepoVolumeSource{}}}}) { 61 t.Errorf("Expected true") 62 } 63 } 64 65 // Expected command 66 type expectedCommand struct { 67 // The git command 68 cmd []string 69 // The dir of git command is executed 70 dir string 71 } 72 73 func TestPlugin(t *testing.T) { 74 gitURL := "https://github.com/kubernetes/kubernetes.git" 75 revision := "2a30ce65c5ab586b98916d83385c5983edd353a1" 76 77 scenarios := []struct { 78 name string 79 vol *v1.Volume 80 expecteds []expectedCommand 81 isExpectedFailure bool 82 }{ 83 { 84 name: "target-dir", 85 vol: &v1.Volume{ 86 Name: "vol1", 87 VolumeSource: v1.VolumeSource{ 88 GitRepo: &v1.GitRepoVolumeSource{ 89 Repository: gitURL, 90 Revision: revision, 91 Directory: "target_dir", 92 }, 93 }, 94 }, 95 expecteds: []expectedCommand{ 96 { 97 cmd: []string{"git", "clone", "--", gitURL, "target_dir"}, 98 dir: "", 99 }, 100 { 101 cmd: []string{"git", "checkout", revision}, 102 dir: "/target_dir", 103 }, 104 { 105 cmd: []string{"git", "reset", "--hard"}, 106 dir: "/target_dir", 107 }, 108 }, 109 isExpectedFailure: false, 110 }, 111 { 112 name: "target-dir-no-revision", 113 vol: &v1.Volume{ 114 Name: "vol1", 115 VolumeSource: v1.VolumeSource{ 116 GitRepo: &v1.GitRepoVolumeSource{ 117 Repository: gitURL, 118 Directory: "target_dir", 119 }, 120 }, 121 }, 122 expecteds: []expectedCommand{ 123 { 124 cmd: []string{"git", "clone", "--", gitURL, "target_dir"}, 125 dir: "", 126 }, 127 }, 128 isExpectedFailure: false, 129 }, 130 { 131 name: "only-git-clone", 132 vol: &v1.Volume{ 133 Name: "vol1", 134 VolumeSource: v1.VolumeSource{ 135 GitRepo: &v1.GitRepoVolumeSource{ 136 Repository: gitURL, 137 }, 138 }, 139 }, 140 expecteds: []expectedCommand{ 141 { 142 cmd: []string{"git", "clone", "--", gitURL}, 143 dir: "", 144 }, 145 }, 146 isExpectedFailure: false, 147 }, 148 { 149 name: "no-target-dir", 150 vol: &v1.Volume{ 151 Name: "vol1", 152 VolumeSource: v1.VolumeSource{ 153 GitRepo: &v1.GitRepoVolumeSource{ 154 Repository: gitURL, 155 Revision: revision, 156 Directory: "", 157 }, 158 }, 159 }, 160 expecteds: []expectedCommand{ 161 { 162 cmd: []string{"git", "clone", "--", gitURL}, 163 dir: "", 164 }, 165 { 166 cmd: []string{"git", "checkout", revision}, 167 dir: "/kubernetes", 168 }, 169 { 170 cmd: []string{"git", "reset", "--hard"}, 171 dir: "/kubernetes", 172 }, 173 }, 174 isExpectedFailure: false, 175 }, 176 { 177 name: "current-dir", 178 vol: &v1.Volume{ 179 Name: "vol1", 180 VolumeSource: v1.VolumeSource{ 181 GitRepo: &v1.GitRepoVolumeSource{ 182 Repository: gitURL, 183 Revision: revision, 184 Directory: ".", 185 }, 186 }, 187 }, 188 expecteds: []expectedCommand{ 189 { 190 cmd: []string{"git", "clone", "--", gitURL, "."}, 191 dir: "", 192 }, 193 { 194 cmd: []string{"git", "checkout", revision}, 195 dir: "", 196 }, 197 { 198 cmd: []string{"git", "reset", "--hard"}, 199 dir: "", 200 }, 201 }, 202 isExpectedFailure: false, 203 }, 204 { 205 name: "current-dir-mess", 206 vol: &v1.Volume{ 207 Name: "vol1", 208 VolumeSource: v1.VolumeSource{ 209 GitRepo: &v1.GitRepoVolumeSource{ 210 Repository: gitURL, 211 Revision: revision, 212 Directory: "./.", 213 }, 214 }, 215 }, 216 expecteds: []expectedCommand{ 217 { 218 cmd: []string{"git", "clone", "--", gitURL, "./."}, 219 dir: "", 220 }, 221 { 222 cmd: []string{"git", "checkout", revision}, 223 dir: "", 224 }, 225 { 226 cmd: []string{"git", "reset", "--hard"}, 227 dir: "", 228 }, 229 }, 230 isExpectedFailure: false, 231 }, 232 { 233 name: "invalid-repository", 234 vol: &v1.Volume{ 235 Name: "vol1", 236 VolumeSource: v1.VolumeSource{ 237 GitRepo: &v1.GitRepoVolumeSource{ 238 Repository: "--foo", 239 }, 240 }, 241 }, 242 isExpectedFailure: true, 243 }, 244 { 245 name: "invalid-revision", 246 vol: &v1.Volume{ 247 Name: "vol1", 248 VolumeSource: v1.VolumeSource{ 249 GitRepo: &v1.GitRepoVolumeSource{ 250 Repository: gitURL, 251 Revision: "--bar", 252 }, 253 }, 254 }, 255 isExpectedFailure: true, 256 }, 257 { 258 name: "invalid-directory", 259 vol: &v1.Volume{ 260 Name: "vol1", 261 VolumeSource: v1.VolumeSource{ 262 GitRepo: &v1.GitRepoVolumeSource{ 263 Repository: gitURL, 264 Directory: "-b", 265 }, 266 }, 267 }, 268 isExpectedFailure: true, 269 }, 270 { 271 name: "invalid-revision-directory-combo", 272 vol: &v1.Volume{ 273 Name: "vol1", 274 VolumeSource: v1.VolumeSource{ 275 GitRepo: &v1.GitRepoVolumeSource{ 276 Repository: gitURL, 277 Revision: "main", 278 Directory: "foo/bar", 279 }, 280 }, 281 }, 282 isExpectedFailure: true, 283 }, 284 } 285 286 for _, scenario := range scenarios { 287 allErrs := doTestPlugin(scenario, t) 288 if len(allErrs) == 0 && scenario.isExpectedFailure { 289 t.Errorf("Unexpected success for scenario: %s", scenario.name) 290 } 291 if len(allErrs) > 0 && !scenario.isExpectedFailure { 292 t.Errorf("Unexpected failure for scenario: %s - %+v", scenario.name, allErrs) 293 } 294 } 295 296 } 297 298 func doTestPlugin(scenario struct { 299 name string 300 vol *v1.Volume 301 expecteds []expectedCommand 302 isExpectedFailure bool 303 }, t *testing.T) []error { 304 allErrs := []error{} 305 306 plugMgr := volume.VolumePluginMgr{} 307 rootDir, host := newTestHost(t) 308 defer os.RemoveAll(rootDir) 309 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 310 311 plug, err := plugMgr.FindPluginByName("kubernetes.io/git-repo") 312 if err != nil { 313 allErrs = append(allErrs, 314 fmt.Errorf("can't find the plugin by name")) 315 return allErrs 316 } 317 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}} 318 mounter, err := plug.NewMounter(volume.NewSpecFromVolume(scenario.vol), pod, volume.VolumeOptions{}) 319 320 if err != nil { 321 allErrs = append(allErrs, 322 fmt.Errorf("failed to make a new Mounter: %w", err)) 323 return allErrs 324 } 325 if mounter == nil { 326 allErrs = append(allErrs, 327 fmt.Errorf("got a nil Mounter")) 328 return allErrs 329 } 330 331 path := mounter.GetPath() 332 suffix := filepath.Join("pods/poduid/volumes/kubernetes.io~git-repo", scenario.vol.Name) 333 if !strings.HasSuffix(path, suffix) { 334 allErrs = append(allErrs, 335 fmt.Errorf("got unexpected path: %s", path)) 336 return allErrs 337 } 338 339 // Test setUp() 340 setUpErrs := doTestSetUp(scenario, mounter) 341 allErrs = append(allErrs, setUpErrs...) 342 343 if _, err := os.Stat(path); err != nil { 344 if os.IsNotExist(err) { 345 allErrs = append(allErrs, 346 fmt.Errorf("SetUp() failed, volume path not created: %s", path)) 347 return allErrs 348 } 349 allErrs = append(allErrs, 350 fmt.Errorf("SetUp() failed: %v", err)) 351 return allErrs 352 353 } 354 355 // gitRepo volume should create its own empty wrapper path 356 podWrapperMetadataDir := fmt.Sprintf("%v/pods/poduid/plugins/kubernetes.io~empty-dir/wrapped_%v", rootDir, scenario.vol.Name) 357 358 if _, err := os.Stat(podWrapperMetadataDir); err != nil { 359 if os.IsNotExist(err) { 360 allErrs = append(allErrs, 361 fmt.Errorf("SetUp() failed, empty-dir wrapper path is not created: %s", podWrapperMetadataDir)) 362 } else { 363 allErrs = append(allErrs, 364 fmt.Errorf("SetUp() failed: %v", err)) 365 } 366 } 367 368 unmounter, err := plug.NewUnmounter("vol1", types.UID("poduid")) 369 if err != nil { 370 allErrs = append(allErrs, 371 fmt.Errorf("failed to make a new Unmounter: %w", err)) 372 return allErrs 373 } 374 if unmounter == nil { 375 allErrs = append(allErrs, 376 fmt.Errorf("got a nil Unmounter")) 377 return allErrs 378 } 379 380 if err := unmounter.TearDown(); err != nil { 381 allErrs = append(allErrs, 382 fmt.Errorf("expected success, got: %w", err)) 383 return allErrs 384 } 385 if _, err := os.Stat(path); err == nil { 386 allErrs = append(allErrs, 387 fmt.Errorf("TearDown() failed, volume path still exists: %s", path)) 388 } else if !os.IsNotExist(err) { 389 allErrs = append(allErrs, 390 fmt.Errorf("TearDown() failed: %w", err)) 391 } 392 return allErrs 393 } 394 395 func doTestSetUp(scenario struct { 396 name string 397 vol *v1.Volume 398 expecteds []expectedCommand 399 isExpectedFailure bool 400 }, mounter volume.Mounter) []error { 401 expecteds := scenario.expecteds 402 allErrs := []error{} 403 404 // Construct combined outputs from expected commands 405 var fakeOutputs []fakeexec.FakeAction 406 var fcmd fakeexec.FakeCmd 407 for _, expected := range expecteds { 408 expected := expected 409 if expected.cmd[1] == "clone" { 410 // Calculate the subdirectory clone would create (if any) 411 // git clone -- https://github.com/kubernetes/kubernetes.git target_dir --> target_dir 412 // git clone -- https://github.com/kubernetes/kubernetes.git --> kubernetes 413 // git clone -- https://github.com/kubernetes/kubernetes.git . --> . 414 // git clone -- https://github.com/kubernetes/kubernetes.git ./. --> . 415 cloneSubdir := path.Base(expected.cmd[len(expected.cmd)-1]) 416 if cloneSubdir == "kubernetes.git" { 417 cloneSubdir = "kubernetes" 418 } 419 fakeOutputs = append(fakeOutputs, func() ([]byte, []byte, error) { 420 // git clone, it creates new dir/files 421 os.MkdirAll(filepath.Join(fcmd.Dirs[0], expected.dir, cloneSubdir), 0750) 422 return []byte{}, nil, nil 423 }) 424 } else { 425 // git checkout || git reset, they create nothing 426 fakeOutputs = append(fakeOutputs, func() ([]byte, []byte, error) { 427 return []byte{}, nil, nil 428 }) 429 } 430 } 431 fcmd = fakeexec.FakeCmd{ 432 CombinedOutputScript: fakeOutputs, 433 } 434 435 // Construct fake exec outputs from fcmd 436 var fakeAction []fakeexec.FakeCommandAction 437 for i := 0; i < len(expecteds); i++ { 438 fakeAction = append(fakeAction, func(cmd string, args ...string) exec.Cmd { 439 return fakeexec.InitFakeCmd(&fcmd, cmd, args...) 440 }) 441 442 } 443 fake := &fakeexec.FakeExec{ 444 CommandScript: fakeAction, 445 } 446 447 g := mounter.(*gitRepoVolumeMounter) 448 g.exec = fake 449 450 err := g.SetUp(volume.MounterArgs{}) 451 if err != nil { 452 allErrs = append(allErrs, err) 453 } 454 455 if fake.CommandCalls != len(expecteds) { 456 allErrs = append(allErrs, 457 fmt.Errorf("unexpected command calls in scenario: expected %d, saw: %d", len(expecteds), fake.CommandCalls)) 458 } 459 var expectedCmds [][]string 460 for _, expected := range expecteds { 461 expectedCmds = append(expectedCmds, expected.cmd) 462 } 463 if !reflect.DeepEqual(expectedCmds, fcmd.CombinedOutputLog) { 464 allErrs = append(allErrs, 465 fmt.Errorf("unexpected commands: %v, expected: %v", fcmd.CombinedOutputLog, expectedCmds)) 466 } 467 468 var expectedPaths []string 469 for _, expected := range expecteds { 470 expectedPaths = append(expectedPaths, filepath.Join(g.GetPath(), expected.dir)) 471 } 472 if len(fcmd.Dirs) != len(expectedPaths) || !reflect.DeepEqual(expectedPaths, fcmd.Dirs) { 473 allErrs = append(allErrs, 474 fmt.Errorf("unexpected directories: %v, expected: %v", fcmd.Dirs, expectedPaths)) 475 } 476 477 return allErrs 478 }