github.com/koderover/helm@v2.17.0+incompatible/pkg/plugin/installer/http_installer_test.go (about) 1 /* 2 Copyright The Helm Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package installer // import "k8s.io/helm/pkg/plugin/installer" 17 18 import ( 19 "archive/tar" 20 "bytes" 21 "compress/gzip" 22 "encoding/base64" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "syscall" 28 "testing" 29 30 "k8s.io/helm/pkg/helm/helmpath" 31 ) 32 33 var _ Installer = new(HTTPInstaller) 34 35 // Fake http client 36 type TestHTTPGetter struct { 37 MockResponse *bytes.Buffer 38 MockError error 39 } 40 41 func (t *TestHTTPGetter) Get(href string) (*bytes.Buffer, error) { return t.MockResponse, t.MockError } 42 43 // Fake plugin tarball data 44 var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA=" 45 46 func TestStripName(t *testing.T) { 47 if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" { 48 t.Errorf("name does not match expected value") 49 } 50 if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" { 51 t.Errorf("name does not match expected value") 52 } 53 if stripPluginName("fake-plugin.tgz") != "fake-plugin" { 54 t.Errorf("name does not match expected value") 55 } 56 if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" { 57 t.Errorf("name does not match expected value") 58 } 59 } 60 61 func TestHTTPInstaller(t *testing.T) { 62 source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" 63 hh, err := ioutil.TempDir("", "helm-home-") 64 if err != nil { 65 t.Fatal(err) 66 } 67 defer os.RemoveAll(hh) 68 69 home := helmpath.Home(hh) 70 if err := os.MkdirAll(home.Plugins(), 0755); err != nil { 71 t.Fatalf("Could not create %s: %s", home.Plugins(), err) 72 } 73 74 i, err := NewForSource(source, "0.0.1", home) 75 if err != nil { 76 t.Errorf("unexpected error: %s", err) 77 } 78 79 // ensure a HTTPInstaller was returned 80 httpInstaller, ok := i.(*HTTPInstaller) 81 if !ok { 82 t.Error("expected a HTTPInstaller") 83 } 84 85 // inject fake http client responding with minimal plugin tarball 86 mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) 87 if err != nil { 88 t.Fatalf("Could not decode fake tgz plugin: %s", err) 89 } 90 91 httpInstaller.getter = &TestHTTPGetter{ 92 MockResponse: bytes.NewBuffer(mockTgz), 93 } 94 95 // install the plugin 96 if err := Install(i); err != nil { 97 t.Error(err) 98 } 99 if i.Path() != home.Path("plugins", "fake-plugin") { 100 t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) 101 } 102 103 // Install again to test plugin exists error 104 if err := Install(i); err == nil { 105 t.Error("expected error for plugin exists, got none") 106 } else if err.Error() != "plugin already exists" { 107 t.Errorf("expected error for plugin exists, got (%v)", err) 108 } 109 110 } 111 112 func TestHTTPInstallerNonExistentVersion(t *testing.T) { 113 source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz" 114 hh, err := ioutil.TempDir("", "helm-home-") 115 if err != nil { 116 t.Fatal(err) 117 } 118 defer os.RemoveAll(hh) 119 120 home := helmpath.Home(hh) 121 if err := os.MkdirAll(home.Plugins(), 0755); err != nil { 122 t.Fatalf("Could not create %s: %s", home.Plugins(), err) 123 } 124 125 i, err := NewForSource(source, "0.0.2", home) 126 if err != nil { 127 t.Errorf("unexpected error: %s", err) 128 } 129 130 // ensure a HTTPInstaller was returned 131 httpInstaller, ok := i.(*HTTPInstaller) 132 if !ok { 133 t.Error("expected a HTTPInstaller") 134 } 135 136 // inject fake http client responding with error 137 httpInstaller.getter = &TestHTTPGetter{ 138 MockError: fmt.Errorf("failed to download plugin for some reason"), 139 } 140 141 // attempt to install the plugin 142 if err := Install(i); err == nil { 143 t.Error("expected error from http client") 144 } 145 146 } 147 148 func TestHTTPInstallerUpdate(t *testing.T) { 149 source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" 150 hh, err := ioutil.TempDir("", "helm-home-") 151 if err != nil { 152 t.Fatal(err) 153 } 154 defer os.RemoveAll(hh) 155 156 home := helmpath.Home(hh) 157 if err := os.MkdirAll(home.Plugins(), 0755); err != nil { 158 t.Fatalf("Could not create %s: %s", home.Plugins(), err) 159 } 160 161 i, err := NewForSource(source, "0.0.1", home) 162 if err != nil { 163 t.Errorf("unexpected error: %s", err) 164 } 165 166 // ensure a HTTPInstaller was returned 167 httpInstaller, ok := i.(*HTTPInstaller) 168 if !ok { 169 t.Error("expected a HTTPInstaller") 170 } 171 172 // inject fake http client responding with minimal plugin tarball 173 mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) 174 if err != nil { 175 t.Fatalf("Could not decode fake tgz plugin: %s", err) 176 } 177 178 httpInstaller.getter = &TestHTTPGetter{ 179 MockResponse: bytes.NewBuffer(mockTgz), 180 } 181 182 // install the plugin before updating 183 if err := Install(i); err != nil { 184 t.Error(err) 185 } 186 if i.Path() != home.Path("plugins", "fake-plugin") { 187 t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) 188 } 189 190 // Update plugin, should fail because it is not implemented 191 if err := Update(i); err == nil { 192 t.Error("update method not implemented for http installer") 193 } 194 } 195 196 func TestExtract(t *testing.T) { 197 //create a temp home 198 hh, err := ioutil.TempDir("", "helm-home-") 199 if err != nil { 200 t.Fatal(err) 201 } 202 defer os.RemoveAll(hh) 203 204 home := helmpath.Home(hh) 205 if err := os.MkdirAll(home.Plugins(), 0755); err != nil { 206 t.Fatalf("Could not create %s: %s", home.Plugins(), err) 207 } 208 209 cacheDir := filepath.Join(home.Cache(), "plugins", "plugin-key") 210 if err := os.MkdirAll(cacheDir, 0755); err != nil { 211 t.Fatalf("Could not create %s: %s", cacheDir, err) 212 } 213 214 //{"plugin.yaml", "plugin metadata up in here"}, 215 //{"README.md", "so you know what's upp"}, 216 //{"script.sh", "echo script"}, 217 218 syscall.Umask(0000) 219 220 var tarbuf bytes.Buffer 221 tw := tar.NewWriter(&tarbuf) 222 var files = []struct { 223 Name, Body string 224 Mode int64 225 }{ 226 {"./plugin.yaml", "sneaky plugin metadata", 0600}, 227 {"README.md", "some text", 0777}, 228 } 229 for _, file := range files { 230 hdr := &tar.Header{ 231 Name: file.Name, 232 Typeflag: tar.TypeReg, 233 Mode: file.Mode, 234 Size: int64(len(file.Body)), 235 } 236 if err := tw.WriteHeader(hdr); err != nil { 237 t.Fatal(err) 238 } 239 if _, err := tw.Write([]byte(file.Body)); err != nil { 240 t.Fatal(err) 241 } 242 } 243 if err := tw.Close(); err != nil { 244 t.Fatal(err) 245 } 246 247 var buf bytes.Buffer 248 gz := gzip.NewWriter(&buf) 249 if _, err := gz.Write(tarbuf.Bytes()); err != nil { 250 t.Fatal(err) 251 } 252 gz.Close() 253 254 source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tgz" 255 extr, err := NewExtractor(source) 256 if err != nil { 257 t.Fatal(err) 258 } 259 260 if err = extr.Extract(&buf, cacheDir); err != nil { 261 t.Errorf("Did not expect error but got error: %v", err) 262 } 263 264 pluginYAMLFullPath := filepath.Join(cacheDir, "plugin.yaml") 265 if info, err := os.Stat(pluginYAMLFullPath); err != nil { 266 if os.IsNotExist(err) { 267 t.Errorf("Expected %s to exist but doesn't", pluginYAMLFullPath) 268 } else { 269 t.Error(err) 270 } 271 } else if info.Mode().Perm() != 0600 { 272 t.Errorf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm()) 273 } 274 275 readmeFullPath := filepath.Join(cacheDir, "README.md") 276 if info, err := os.Stat(readmeFullPath); err != nil { 277 if os.IsNotExist(err) { 278 t.Errorf("Expected %s to exist but doesn't", readmeFullPath) 279 } else { 280 t.Error(err) 281 } 282 } else if info.Mode().Perm() != 0777 { 283 t.Errorf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm()) 284 } 285 286 } 287 288 func TestExtractBadPlugin(t *testing.T) { 289 //create a temp home 290 hh, err := ioutil.TempDir("", "helm-home-") 291 if err != nil { 292 t.Fatal(err) 293 } 294 defer os.RemoveAll(hh) 295 296 home := helmpath.Home(hh) 297 if err := os.MkdirAll(home.Plugins(), 0755); err != nil { 298 t.Fatalf("Could not create %s: %s", home.Plugins(), err) 299 } 300 301 cacheDir := filepath.Join(home.Cache(), "plugins", "plugin-key") 302 if err := os.MkdirAll(cacheDir, 0755); err != nil { 303 t.Fatalf("Could not create %s: %s", cacheDir, err) 304 } 305 306 syscall.Umask(0000) 307 308 var tarbuf bytes.Buffer 309 tw := tar.NewWriter(&tarbuf) 310 var files = []struct { 311 Name, Body string 312 Mode int64 313 }{ 314 {"../../plugin.yaml", "sneaky plugin metadata", 0600}, 315 {"README.md", "some text", 0777}, 316 } 317 for _, file := range files { 318 hdr := &tar.Header{ 319 Name: file.Name, 320 Typeflag: tar.TypeReg, 321 Mode: file.Mode, 322 Size: int64(len(file.Body)), 323 } 324 if err := tw.WriteHeader(hdr); err != nil { 325 t.Fatal(err) 326 } 327 if _, err := tw.Write([]byte(file.Body)); err != nil { 328 t.Fatal(err) 329 } 330 } 331 if err := tw.Close(); err != nil { 332 t.Fatal(err) 333 } 334 335 var buf bytes.Buffer 336 gz := gzip.NewWriter(&buf) 337 if _, err := gz.Write(tarbuf.Bytes()); err != nil { 338 t.Fatal(err) 339 } 340 gz.Close() 341 342 source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tgz" 343 extr, err := NewExtractor(source) 344 if err != nil { 345 t.Fatal(err) 346 } 347 348 if err = extr.Extract(&buf, cacheDir); err == nil { 349 t.Error("Should have failed because of bad plugin.yaml path") 350 } 351 } 352 353 func TestCleanJoin(t *testing.T) { 354 for i, fixture := range []struct { 355 path string 356 expect string 357 expectError bool 358 }{ 359 {"foo/bar.txt", "/tmp/foo/bar.txt", false}, 360 {"/foo/bar.txt", "", true}, 361 {"./foo/bar.txt", "/tmp/foo/bar.txt", false}, 362 {"./././././foo/bar.txt", "/tmp/foo/bar.txt", false}, 363 {"../../../../foo/bar.txt", "", true}, 364 {"foo/../../../../bar.txt", "", true}, 365 {"c:/foo/bar.txt", "/tmp/c:/foo/bar.txt", true}, 366 {"foo\\bar.txt", "/tmp/foo/bar.txt", false}, 367 {"c:\\foo\\bar.txt", "", true}, 368 } { 369 out, err := cleanJoin("/tmp", fixture.path) 370 if err != nil { 371 if !fixture.expectError { 372 t.Errorf("Test %d: Path was not cleaned: %s", i, err) 373 } 374 continue 375 } 376 if fixture.expect != out { 377 t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out) 378 } 379 } 380 381 }