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  }