github.com/argoproj/argo-cd/v2@v2.10.9/util/helm/client_test.go (about) 1 package helm 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "math" 8 "net/url" 9 "os" 10 "strings" 11 "testing" 12 13 "net/http" 14 "net/http/httptest" 15 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 "gopkg.in/yaml.v2" 19 20 "github.com/argoproj/argo-cd/v2/util/io" 21 ) 22 23 type fakeIndexCache struct { 24 data []byte 25 } 26 27 func (f *fakeIndexCache) SetHelmIndex(_ string, indexData []byte) error { 28 f.data = indexData 29 return nil 30 } 31 32 func (f *fakeIndexCache) GetHelmIndex(_ string, indexData *[]byte) error { 33 *indexData = f.data 34 return nil 35 } 36 37 func TestIndex(t *testing.T) { 38 t.Run("Invalid", func(t *testing.T) { 39 client := NewClient("", Creds{}, false, "") 40 _, err := client.GetIndex(false, 10000) 41 assert.Error(t, err) 42 }) 43 t.Run("Stable", func(t *testing.T) { 44 client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "") 45 index, err := client.GetIndex(false, 10000) 46 assert.NoError(t, err) 47 assert.NotNil(t, index) 48 }) 49 t.Run("BasicAuth", func(t *testing.T) { 50 client := NewClient("https://argoproj.github.io/argo-helm", Creds{ 51 Username: "my-password", 52 Password: "my-username", 53 }, false, "") 54 index, err := client.GetIndex(false, 10000) 55 assert.NoError(t, err) 56 assert.NotNil(t, index) 57 }) 58 59 t.Run("Cached", func(t *testing.T) { 60 fakeIndex := Index{Entries: map[string]Entries{"fake": {}}} 61 data := bytes.Buffer{} 62 err := yaml.NewEncoder(&data).Encode(fakeIndex) 63 require.NoError(t, err) 64 65 client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "", WithIndexCache(&fakeIndexCache{data: data.Bytes()})) 66 index, err := client.GetIndex(false, 10000) 67 68 assert.NoError(t, err) 69 assert.Equal(t, fakeIndex, *index) 70 }) 71 72 t.Run("Limited", func(t *testing.T) { 73 client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "") 74 _, err := client.GetIndex(false, 100) 75 76 assert.ErrorContains(t, err, "unexpected end of stream") 77 }) 78 } 79 80 func Test_nativeHelmChart_ExtractChart(t *testing.T) { 81 client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "") 82 path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true) 83 assert.NoError(t, err) 84 defer io.Close(closer) 85 info, err := os.Stat(path) 86 assert.NoError(t, err) 87 assert.True(t, info.IsDir()) 88 } 89 90 func Test_nativeHelmChart_ExtractChartWithLimiter(t *testing.T) { 91 client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "") 92 _, _, err := client.ExtractChart("argo-cd", "0.7.1", false, 100, false) 93 assert.Error(t, err, "error while iterating on tar reader: unexpected EOF") 94 } 95 96 func Test_nativeHelmChart_ExtractChart_insecure(t *testing.T) { 97 client := NewClient("https://argoproj.github.io/argo-helm", Creds{InsecureSkipVerify: true}, false, "") 98 path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true) 99 assert.NoError(t, err) 100 defer io.Close(closer) 101 info, err := os.Stat(path) 102 assert.NoError(t, err) 103 assert.True(t, info.IsDir()) 104 } 105 106 func Test_normalizeChartName(t *testing.T) { 107 t.Run("Test non-slashed name", func(t *testing.T) { 108 n := normalizeChartName("mychart") 109 assert.Equal(t, n, "mychart") 110 }) 111 t.Run("Test single-slashed name", func(t *testing.T) { 112 n := normalizeChartName("myorg/mychart") 113 assert.Equal(t, n, "mychart") 114 }) 115 t.Run("Test chart name with suborg", func(t *testing.T) { 116 n := normalizeChartName("myorg/mysuborg/mychart") 117 assert.Equal(t, n, "mychart") 118 }) 119 t.Run("Test double-slashed name", func(t *testing.T) { 120 n := normalizeChartName("myorg//mychart") 121 assert.Equal(t, n, "mychart") 122 }) 123 t.Run("Test invalid chart name - ends with slash", func(t *testing.T) { 124 n := normalizeChartName("myorg/") 125 assert.Equal(t, n, "myorg/") 126 }) 127 t.Run("Test invalid chart name - is dot", func(t *testing.T) { 128 n := normalizeChartName("myorg/.") 129 assert.Equal(t, n, "myorg/.") 130 }) 131 t.Run("Test invalid chart name - is two dots", func(t *testing.T) { 132 n := normalizeChartName("myorg/..") 133 assert.Equal(t, n, "myorg/..") 134 }) 135 } 136 137 func TestIsHelmOciRepo(t *testing.T) { 138 assert.True(t, IsHelmOciRepo("demo.goharbor.io")) 139 assert.True(t, IsHelmOciRepo("demo.goharbor.io:8080")) 140 assert.False(t, IsHelmOciRepo("https://demo.goharbor.io")) 141 assert.False(t, IsHelmOciRepo("https://demo.goharbor.io:8080")) 142 } 143 144 func TestGetIndexURL(t *testing.T) { 145 urlTemplate := `https://gitlab.com/projects/%s/packages/helm/stable` 146 t.Run("URL without escaped characters", func(t *testing.T) { 147 rawURL := fmt.Sprintf(urlTemplate, "232323982") 148 want := rawURL + "/index.yaml" 149 got, err := getIndexURL(rawURL) 150 assert.Equal(t, want, got) 151 assert.NoError(t, err) 152 }) 153 t.Run("URL with escaped characters", func(t *testing.T) { 154 rawURL := fmt.Sprintf(urlTemplate, "mygroup%2Fmyproject") 155 want := rawURL + "/index.yaml" 156 got, err := getIndexURL(rawURL) 157 assert.Equal(t, want, got) 158 assert.NoError(t, err) 159 }) 160 t.Run("URL with invalid escaped characters", func(t *testing.T) { 161 rawURL := fmt.Sprintf(urlTemplate, "mygroup%**myproject") 162 got, err := getIndexURL(rawURL) 163 assert.Equal(t, "", got) 164 assert.Error(t, err) 165 }) 166 } 167 168 func TestGetTagsFromUrl(t *testing.T) { 169 t.Run("should return tags correctly while following the link header", func(t *testing.T) { 170 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 171 t.Logf("called %s", r.URL.Path) 172 responseTags := TagsList{} 173 w.Header().Set("Content-Type", "application/json") 174 if !strings.Contains(r.URL.String(), "token") { 175 w.Header().Set("Link", fmt.Sprintf("<https://%s%s?token=next-token>; rel=next", r.Host, r.URL.Path)) 176 responseTags.Tags = []string{"first"} 177 } else { 178 responseTags.Tags = []string{ 179 "second", 180 "2.8.0", 181 "2.8.0-prerelease", 182 "2.8.0_build", 183 "2.8.0-prerelease_build", 184 "2.8.0-prerelease.1_build.1234", 185 } 186 } 187 w.WriteHeader(http.StatusOK) 188 err := json.NewEncoder(w).Encode(responseTags) 189 if err != nil { 190 t.Fatal(err) 191 } 192 })) 193 194 client := NewClient(server.URL, Creds{InsecureSkipVerify: true}, true, "") 195 196 tags, err := client.GetTags("mychart", true) 197 assert.NoError(t, err) 198 assert.ElementsMatch(t, tags.Tags, []string{ 199 "first", 200 "second", 201 "2.8.0", 202 "2.8.0-prerelease", 203 "2.8.0+build", 204 "2.8.0-prerelease+build", 205 "2.8.0-prerelease.1+build.1234", 206 }) 207 }) 208 209 t.Run("should return an error not when oci is not enabled", func(t *testing.T) { 210 client := NewClient("example.com", Creds{}, false, "") 211 212 _, err := client.GetTags("my-chart", true) 213 assert.ErrorIs(t, OCINotEnabledErr, err) 214 }) 215 } 216 217 func TestGetTagsFromURLPrivateRepoAuthentication(t *testing.T) { 218 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 219 t.Logf("called %s", r.URL.Path) 220 221 authorization := r.Header.Get("Authorization") 222 if authorization == "" { 223 w.Header().Set("WWW-Authenticate", `Basic realm="helm repo to get tags"`) 224 w.WriteHeader(http.StatusUnauthorized) 225 return 226 } 227 228 t.Logf("authorization received %s", authorization) 229 230 responseTags := TagsList{ 231 Tags: []string{ 232 "2.8.0", 233 "2.8.0-prerelease", 234 "2.8.0_build", 235 "2.8.0-prerelease_build", 236 "2.8.0-prerelease.1_build.1234", 237 }, 238 } 239 240 w.Header().Set("Content-Type", "application/json") 241 w.WriteHeader(http.StatusOK) 242 err := json.NewEncoder(w).Encode(responseTags) 243 if err != nil { 244 t.Fatal(err) 245 } 246 })) 247 t.Cleanup(server.Close) 248 249 serverURL, err := url.Parse(server.URL) 250 assert.NoError(t, err) 251 252 testCases := []struct { 253 name string 254 repoURL string 255 }{ 256 { 257 name: "should login correctly when the repo path is in the server root with http scheme", 258 repoURL: server.URL, 259 }, 260 { 261 name: "should login correctly when the repo path is not in the server root with http scheme", 262 repoURL: fmt.Sprintf("%s/my-repo", server.URL), 263 }, 264 { 265 name: "should login correctly when the repo path is in the server root without http scheme", 266 repoURL: serverURL.Host, 267 }, 268 { 269 name: "should login correctly when the repo path is not in the server root without http scheme", 270 repoURL: fmt.Sprintf("%s/my-repo", serverURL.Host), 271 }, 272 } 273 274 for _, testCase := range testCases { 275 t.Run(testCase.name, func(t *testing.T) { 276 client := NewClient(testCase.repoURL, Creds{ 277 InsecureSkipVerify: true, 278 Username: "my-username", 279 Password: "my-password", 280 }, true, "") 281 282 tags, err := client.GetTags("mychart", true) 283 284 assert.NoError(t, err) 285 assert.ElementsMatch(t, tags.Tags, []string{ 286 "2.8.0", 287 "2.8.0-prerelease", 288 "2.8.0+build", 289 "2.8.0-prerelease+build", 290 "2.8.0-prerelease.1+build.1234", 291 }) 292 }) 293 } 294 }