github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/repo/chartrepo_test.go (about) 1 /* 2 Copyright The Helm 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 repo 18 19 import ( 20 "bytes" 21 "io/ioutil" 22 "net/http" 23 "net/http/httptest" 24 "os" 25 "path/filepath" 26 "reflect" 27 "runtime" 28 "strings" 29 "testing" 30 "time" 31 32 "sigs.k8s.io/yaml" 33 34 "github.com/stefanmcshane/helm/internal/test/ensure" 35 "github.com/stefanmcshane/helm/pkg/chart" 36 "github.com/stefanmcshane/helm/pkg/cli" 37 "github.com/stefanmcshane/helm/pkg/getter" 38 ) 39 40 const ( 41 testRepository = "testdata/repository" 42 testURL = "http://example-charts.com" 43 ) 44 45 func TestLoadChartRepository(t *testing.T) { 46 r, err := NewChartRepository(&Entry{ 47 Name: testRepository, 48 URL: testURL, 49 }, getter.All(&cli.EnvSettings{})) 50 if err != nil { 51 t.Errorf("Problem creating chart repository from %s: %v", testRepository, err) 52 } 53 54 if err := r.Load(); err != nil { 55 t.Errorf("Problem loading chart repository from %s: %v", testRepository, err) 56 } 57 58 paths := []string{ 59 filepath.Join(testRepository, "frobnitz-1.2.3.tgz"), 60 filepath.Join(testRepository, "sprocket-1.1.0.tgz"), 61 filepath.Join(testRepository, "sprocket-1.2.0.tgz"), 62 filepath.Join(testRepository, "universe/zarthal-1.0.0.tgz"), 63 } 64 65 if r.Config.Name != testRepository { 66 t.Errorf("Expected %s as Name but got %s", testRepository, r.Config.Name) 67 } 68 69 if !reflect.DeepEqual(r.ChartPaths, paths) { 70 t.Errorf("Expected %#v but got %#v\n", paths, r.ChartPaths) 71 } 72 73 if r.Config.URL != testURL { 74 t.Errorf("Expected url for chart repository to be %s but got %s", testURL, r.Config.URL) 75 } 76 } 77 78 func TestIndex(t *testing.T) { 79 r, err := NewChartRepository(&Entry{ 80 Name: testRepository, 81 URL: testURL, 82 }, getter.All(&cli.EnvSettings{})) 83 if err != nil { 84 t.Errorf("Problem creating chart repository from %s: %v", testRepository, err) 85 } 86 87 if err := r.Load(); err != nil { 88 t.Errorf("Problem loading chart repository from %s: %v", testRepository, err) 89 } 90 91 err = r.Index() 92 if err != nil { 93 t.Errorf("Error performing index: %v\n", err) 94 } 95 96 tempIndexPath := filepath.Join(testRepository, indexPath) 97 actual, err := LoadIndexFile(tempIndexPath) 98 defer os.Remove(tempIndexPath) // clean up 99 if err != nil { 100 t.Errorf("Error loading index file %v", err) 101 } 102 verifyIndex(t, actual) 103 104 // Re-index and test again. 105 err = r.Index() 106 if err != nil { 107 t.Errorf("Error performing re-index: %s\n", err) 108 } 109 second, err := LoadIndexFile(tempIndexPath) 110 if err != nil { 111 t.Errorf("Error re-loading index file %v", err) 112 } 113 verifyIndex(t, second) 114 } 115 116 type CustomGetter struct { 117 repoUrls []string 118 } 119 120 func (g *CustomGetter) Get(href string, options ...getter.Option) (*bytes.Buffer, error) { 121 index := &IndexFile{ 122 APIVersion: "v1", 123 Generated: time.Now(), 124 } 125 indexBytes, err := yaml.Marshal(index) 126 if err != nil { 127 return nil, err 128 } 129 g.repoUrls = append(g.repoUrls, href) 130 return bytes.NewBuffer(indexBytes), nil 131 } 132 133 func TestIndexCustomSchemeDownload(t *testing.T) { 134 repoName := "gcs-repo" 135 repoURL := "gs://some-gcs-bucket" 136 myCustomGetter := &CustomGetter{} 137 customGetterConstructor := func(options ...getter.Option) (getter.Getter, error) { 138 return myCustomGetter, nil 139 } 140 providers := getter.Providers{{ 141 Schemes: []string{"gs"}, 142 New: customGetterConstructor, 143 }} 144 repo, err := NewChartRepository(&Entry{ 145 Name: repoName, 146 URL: repoURL, 147 }, providers) 148 if err != nil { 149 t.Fatalf("Problem loading chart repository from %s: %v", repoURL, err) 150 } 151 repo.CachePath = ensure.TempDir(t) 152 defer os.RemoveAll(repo.CachePath) 153 154 tempIndexFile, err := ioutil.TempFile("", "test-repo") 155 if err != nil { 156 t.Fatalf("Failed to create temp index file: %v", err) 157 } 158 defer os.Remove(tempIndexFile.Name()) 159 160 idx, err := repo.DownloadIndexFile() 161 if err != nil { 162 t.Fatalf("Failed to download index file to %s: %v", idx, err) 163 } 164 165 if len(myCustomGetter.repoUrls) != 1 { 166 t.Fatalf("Custom Getter.Get should be called once") 167 } 168 169 expectedRepoIndexURL := repoURL + "/index.yaml" 170 if myCustomGetter.repoUrls[0] != expectedRepoIndexURL { 171 t.Fatalf("Custom Getter.Get should be called with %s", expectedRepoIndexURL) 172 } 173 } 174 175 func verifyIndex(t *testing.T, actual *IndexFile) { 176 var empty time.Time 177 if actual.Generated.Equal(empty) { 178 t.Errorf("Generated should be greater than 0: %s", actual.Generated) 179 } 180 181 if actual.APIVersion != APIVersionV1 { 182 t.Error("Expected v1 API") 183 } 184 185 entries := actual.Entries 186 if numEntries := len(entries); numEntries != 3 { 187 t.Errorf("Expected 3 charts to be listed in index file but got %v", numEntries) 188 } 189 190 expects := map[string]ChartVersions{ 191 "frobnitz": { 192 { 193 Metadata: &chart.Metadata{ 194 Name: "frobnitz", 195 Version: "1.2.3", 196 }, 197 }, 198 }, 199 "sprocket": { 200 { 201 Metadata: &chart.Metadata{ 202 Name: "sprocket", 203 Version: "1.2.0", 204 }, 205 }, 206 { 207 Metadata: &chart.Metadata{ 208 Name: "sprocket", 209 Version: "1.1.0", 210 }, 211 }, 212 }, 213 "zarthal": { 214 { 215 Metadata: &chart.Metadata{ 216 Name: "zarthal", 217 Version: "1.0.0", 218 }, 219 }, 220 }, 221 } 222 223 for name, versions := range expects { 224 got, ok := entries[name] 225 if !ok { 226 t.Errorf("Could not find %q entry", name) 227 continue 228 } 229 if len(versions) != len(got) { 230 t.Errorf("Expected %d versions, got %d", len(versions), len(got)) 231 continue 232 } 233 for i, e := range versions { 234 g := got[i] 235 if e.Name != g.Name { 236 t.Errorf("Expected %q, got %q", e.Name, g.Name) 237 } 238 if e.Version != g.Version { 239 t.Errorf("Expected %q, got %q", e.Version, g.Version) 240 } 241 if len(g.Keywords) != 3 { 242 t.Error("Expected 3 keywords.") 243 } 244 if len(g.Maintainers) != 2 { 245 t.Error("Expected 2 maintainers.") 246 } 247 if g.Created.Equal(empty) { 248 t.Error("Expected created to be non-empty") 249 } 250 if g.Description == "" { 251 t.Error("Expected description to be non-empty") 252 } 253 if g.Home == "" { 254 t.Error("Expected home to be non-empty") 255 } 256 if g.Digest == "" { 257 t.Error("Expected digest to be non-empty") 258 } 259 if len(g.URLs) != 1 { 260 t.Error("Expected exactly 1 URL") 261 } 262 } 263 } 264 } 265 266 // startLocalServerForTests Start the local helm server 267 func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { 268 if handler == nil { 269 fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") 270 if err != nil { 271 return nil, err 272 } 273 handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 274 w.Write(fileBytes) 275 }) 276 } 277 278 return httptest.NewServer(handler), nil 279 } 280 281 // startLocalTLSServerForTests Start the local helm server with TLS 282 func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) { 283 if handler == nil { 284 fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") 285 if err != nil { 286 return nil, err 287 } 288 handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 289 w.Write(fileBytes) 290 }) 291 } 292 293 return httptest.NewTLSServer(handler), nil 294 } 295 296 func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { 297 srv, err := startLocalTLSServerForTests(nil) 298 if err != nil { 299 t.Fatal(err) 300 } 301 defer srv.Close() 302 303 chartURL, err := FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, false, getter.All(&cli.EnvSettings{})) 304 if err != nil { 305 t.Fatalf("%v", err) 306 } 307 if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { 308 t.Errorf("%s is not the valid URL", chartURL) 309 } 310 311 // If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority". 312 _, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{})) 313 // Go communicates with the platform and different platforms return different messages. Go itself tests darwin 314 // differently for its message. On newer versions of Darwin the message includes the "Acme Co" portion while older 315 // versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test 316 // for both messages. 317 if runtime.GOOS == "darwin" { 318 if !strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") && !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { 319 t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) 320 } 321 } else if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { 322 t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) 323 } 324 } 325 326 func TestFindChartInRepoURL(t *testing.T) { 327 srv, err := startLocalServerForTests(nil) 328 if err != nil { 329 t.Fatal(err) 330 } 331 defer srv.Close() 332 333 chartURL, err := FindChartInRepoURL(srv.URL, "nginx", "", "", "", "", getter.All(&cli.EnvSettings{})) 334 if err != nil { 335 t.Fatalf("%v", err) 336 } 337 if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { 338 t.Errorf("%s is not the valid URL", chartURL) 339 } 340 341 chartURL, err = FindChartInRepoURL(srv.URL, "nginx", "0.1.0", "", "", "", getter.All(&cli.EnvSettings{})) 342 if err != nil { 343 t.Errorf("%s", err) 344 } 345 if chartURL != "https://charts.helm.sh/stable/nginx-0.1.0.tgz" { 346 t.Errorf("%s is not the valid URL", chartURL) 347 } 348 } 349 350 func TestErrorFindChartInRepoURL(t *testing.T) { 351 352 g := getter.All(&cli.EnvSettings{ 353 RepositoryCache: ensure.TempDir(t), 354 }) 355 356 if _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", g); err == nil { 357 t.Errorf("Expected error for bad chart URL, but did not get any errors") 358 } else if !strings.Contains(err.Error(), `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`) { 359 t.Errorf("Expected error for bad chart URL, but got a different error (%v)", err) 360 } 361 362 srv, err := startLocalServerForTests(nil) 363 if err != nil { 364 t.Fatal(err) 365 } 366 defer srv.Close() 367 368 if _, err = FindChartInRepoURL(srv.URL, "nginx1", "", "", "", "", g); err == nil { 369 t.Errorf("Expected error for chart not found, but did not get any errors") 370 } else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` { 371 t.Errorf("Expected error for chart not found, but got a different error (%v)", err) 372 } 373 374 if _, err = FindChartInRepoURL(srv.URL, "nginx1", "0.1.0", "", "", "", g); err == nil { 375 t.Errorf("Expected error for chart not found, but did not get any errors") 376 } else if err.Error() != `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository` { 377 t.Errorf("Expected error for chart not found, but got a different error (%v)", err) 378 } 379 380 if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", "", "", "", "", g); err == nil { 381 t.Errorf("Expected error for no chart URLs available, but did not get any errors") 382 } else if err.Error() != `chart "chartWithNoURL" has no downloadable URLs` { 383 t.Errorf("Expected error for chart not found, but got a different error (%v)", err) 384 } 385 } 386 387 func TestResolveReferenceURL(t *testing.T) { 388 chartURL, err := ResolveReferenceURL("http://localhost:8123/charts/", "nginx-0.2.0.tgz") 389 if err != nil { 390 t.Errorf("%s", err) 391 } 392 if chartURL != "http://localhost:8123/charts/nginx-0.2.0.tgz" { 393 t.Errorf("%s", chartURL) 394 } 395 396 chartURL, err = ResolveReferenceURL("http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz") 397 if err != nil { 398 t.Errorf("%s", err) 399 } 400 if chartURL != "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz" { 401 t.Errorf("%s", chartURL) 402 } 403 404 chartURL, err = ResolveReferenceURL("http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz") 405 if err != nil { 406 t.Errorf("%s", err) 407 } 408 if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { 409 t.Errorf("%s", chartURL) 410 } 411 412 chartURL, err = ResolveReferenceURL("http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz") 413 if err != nil { 414 t.Errorf("%s", err) 415 } 416 if chartURL != "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz" { 417 t.Errorf("%s", chartURL) 418 } 419 }