k8s.io/kubernetes@v1.29.3/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 272 for _, scenario := range scenarios { 273 allErrs := doTestPlugin(scenario, t) 274 if len(allErrs) == 0 && scenario.isExpectedFailure { 275 t.Errorf("Unexpected success for scenario: %s", scenario.name) 276 } 277 if len(allErrs) > 0 && !scenario.isExpectedFailure { 278 t.Errorf("Unexpected failure for scenario: %s - %+v", scenario.name, allErrs) 279 } 280 } 281 282 } 283 284 func doTestPlugin(scenario struct { 285 name string 286 vol *v1.Volume 287 expecteds []expectedCommand 288 isExpectedFailure bool 289 }, t *testing.T) []error { 290 allErrs := []error{} 291 292 plugMgr := volume.VolumePluginMgr{} 293 rootDir, host := newTestHost(t) 294 defer os.RemoveAll(rootDir) 295 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 296 297 plug, err := plugMgr.FindPluginByName("kubernetes.io/git-repo") 298 if err != nil { 299 allErrs = append(allErrs, 300 fmt.Errorf("can't find the plugin by name")) 301 return allErrs 302 } 303 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}} 304 mounter, err := plug.NewMounter(volume.NewSpecFromVolume(scenario.vol), pod, volume.VolumeOptions{}) 305 306 if err != nil { 307 allErrs = append(allErrs, 308 fmt.Errorf("failed to make a new Mounter: %w", err)) 309 return allErrs 310 } 311 if mounter == nil { 312 allErrs = append(allErrs, 313 fmt.Errorf("got a nil Mounter")) 314 return allErrs 315 } 316 317 path := mounter.GetPath() 318 suffix := filepath.Join("pods/poduid/volumes/kubernetes.io~git-repo", scenario.vol.Name) 319 if !strings.HasSuffix(path, suffix) { 320 allErrs = append(allErrs, 321 fmt.Errorf("got unexpected path: %s", path)) 322 return allErrs 323 } 324 325 // Test setUp() 326 setUpErrs := doTestSetUp(scenario, mounter) 327 allErrs = append(allErrs, setUpErrs...) 328 329 if _, err := os.Stat(path); err != nil { 330 if os.IsNotExist(err) { 331 allErrs = append(allErrs, 332 fmt.Errorf("SetUp() failed, volume path not created: %s", path)) 333 return allErrs 334 } 335 allErrs = append(allErrs, 336 fmt.Errorf("SetUp() failed: %v", err)) 337 return allErrs 338 339 } 340 341 // gitRepo volume should create its own empty wrapper path 342 podWrapperMetadataDir := fmt.Sprintf("%v/pods/poduid/plugins/kubernetes.io~empty-dir/wrapped_%v", rootDir, scenario.vol.Name) 343 344 if _, err := os.Stat(podWrapperMetadataDir); err != nil { 345 if os.IsNotExist(err) { 346 allErrs = append(allErrs, 347 fmt.Errorf("SetUp() failed, empty-dir wrapper path is not created: %s", podWrapperMetadataDir)) 348 } else { 349 allErrs = append(allErrs, 350 fmt.Errorf("SetUp() failed: %v", err)) 351 } 352 } 353 354 unmounter, err := plug.NewUnmounter("vol1", types.UID("poduid")) 355 if err != nil { 356 allErrs = append(allErrs, 357 fmt.Errorf("failed to make a new Unmounter: %w", err)) 358 return allErrs 359 } 360 if unmounter == nil { 361 allErrs = append(allErrs, 362 fmt.Errorf("got a nil Unmounter")) 363 return allErrs 364 } 365 366 if err := unmounter.TearDown(); err != nil { 367 allErrs = append(allErrs, 368 fmt.Errorf("expected success, got: %w", err)) 369 return allErrs 370 } 371 if _, err := os.Stat(path); err == nil { 372 allErrs = append(allErrs, 373 fmt.Errorf("TearDown() failed, volume path still exists: %s", path)) 374 } else if !os.IsNotExist(err) { 375 allErrs = append(allErrs, 376 fmt.Errorf("TearDown() failed: %w", err)) 377 } 378 return allErrs 379 } 380 381 func doTestSetUp(scenario struct { 382 name string 383 vol *v1.Volume 384 expecteds []expectedCommand 385 isExpectedFailure bool 386 }, mounter volume.Mounter) []error { 387 expecteds := scenario.expecteds 388 allErrs := []error{} 389 390 // Construct combined outputs from expected commands 391 var fakeOutputs []fakeexec.FakeAction 392 var fcmd fakeexec.FakeCmd 393 for _, expected := range expecteds { 394 expected := expected 395 if expected.cmd[1] == "clone" { 396 // Calculate the subdirectory clone would create (if any) 397 // git clone -- https://github.com/kubernetes/kubernetes.git target_dir --> target_dir 398 // git clone -- https://github.com/kubernetes/kubernetes.git --> kubernetes 399 // git clone -- https://github.com/kubernetes/kubernetes.git . --> . 400 // git clone -- https://github.com/kubernetes/kubernetes.git ./. --> . 401 cloneSubdir := path.Base(expected.cmd[len(expected.cmd)-1]) 402 if cloneSubdir == "kubernetes.git" { 403 cloneSubdir = "kubernetes" 404 } 405 fakeOutputs = append(fakeOutputs, func() ([]byte, []byte, error) { 406 // git clone, it creates new dir/files 407 os.MkdirAll(filepath.Join(fcmd.Dirs[0], expected.dir, cloneSubdir), 0750) 408 return []byte{}, nil, nil 409 }) 410 } else { 411 // git checkout || git reset, they create nothing 412 fakeOutputs = append(fakeOutputs, func() ([]byte, []byte, error) { 413 return []byte{}, nil, nil 414 }) 415 } 416 } 417 fcmd = fakeexec.FakeCmd{ 418 CombinedOutputScript: fakeOutputs, 419 } 420 421 // Construct fake exec outputs from fcmd 422 var fakeAction []fakeexec.FakeCommandAction 423 for i := 0; i < len(expecteds); i++ { 424 fakeAction = append(fakeAction, func(cmd string, args ...string) exec.Cmd { 425 return fakeexec.InitFakeCmd(&fcmd, cmd, args...) 426 }) 427 428 } 429 fake := &fakeexec.FakeExec{ 430 CommandScript: fakeAction, 431 } 432 433 g := mounter.(*gitRepoVolumeMounter) 434 g.exec = fake 435 436 err := g.SetUp(volume.MounterArgs{}) 437 if err != nil { 438 allErrs = append(allErrs, err) 439 } 440 441 if fake.CommandCalls != len(expecteds) { 442 allErrs = append(allErrs, 443 fmt.Errorf("unexpected command calls in scenario: expected %d, saw: %d", len(expecteds), fake.CommandCalls)) 444 } 445 var expectedCmds [][]string 446 for _, expected := range expecteds { 447 expectedCmds = append(expectedCmds, expected.cmd) 448 } 449 if !reflect.DeepEqual(expectedCmds, fcmd.CombinedOutputLog) { 450 allErrs = append(allErrs, 451 fmt.Errorf("unexpected commands: %v, expected: %v", fcmd.CombinedOutputLog, expectedCmds)) 452 } 453 454 var expectedPaths []string 455 for _, expected := range expecteds { 456 expectedPaths = append(expectedPaths, filepath.Join(g.GetPath(), expected.dir)) 457 } 458 if len(fcmd.Dirs) != len(expectedPaths) || !reflect.DeepEqual(expectedPaths, fcmd.Dirs) { 459 allErrs = append(allErrs, 460 fmt.Errorf("unexpected directories: %v, expected: %v", fcmd.Dirs, expectedPaths)) 461 } 462 463 return allErrs 464 }