Skip to content

Commit e2ac5a4

Browse files
authored
Add repository config option for private repositories (#1017)
1 parent f73b937 commit e2ac5a4

File tree

4 files changed

+123
-20
lines changed

4 files changed

+123
-20
lines changed

cmd/tk/toolCharts.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@ func chartsVendorCmd() *cli.Command {
3535
Short: "Download Charts to a local folder",
3636
}
3737
prune := cmd.Flags().Bool("prune", false, "also remove non-vendored files from the destination directory")
38+
repoConfigPath := cmd.Flags().String("repository-config", "", "specify a local helm repository config file to use instead of the repositories in the chartfile.yaml. For use with private repositories")
3839

3940
cmd.Run = func(cmd *cli.Command, args []string) error {
4041
c, err := loadChartfile()
4142
if err != nil {
4243
return err
4344
}
4445

45-
return c.Vendor(*prune)
46+
return c.Vendor(*prune, *repoConfigPath)
4647
}
4748

4849
return cmd
@@ -53,14 +54,15 @@ func chartsAddCmd() *cli.Command {
5354
Use: "add [chart@version] [...]",
5455
Short: "Adds Charts to the chartfile",
5556
}
57+
repoConfigPath := cmd.Flags().String("repository-config", "", "specify a local helm repository config file to use instead of the repositories in the chartfile.yaml. For use with private repositories")
5658

5759
cmd.Run = func(cmd *cli.Command, args []string) error {
5860
c, err := loadChartfile()
5961
if err != nil {
6062
return err
6163
}
6264

63-
return c.Add(args)
65+
return c.Add(args, *repoConfigPath)
6466
}
6567

6668
return cmd

pkg/helm/charts.go

+34-3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,29 @@ func LoadChartfile(projectRoot string) (*Charts, error) {
5858
return charts, nil
5959
}
6060

61+
// LoadHelmRepoConfig reads in a helm config file
62+
func LoadHelmRepoConfig(repoConfigPath string) (*ConfigFile, error) {
63+
// make sure path is valid
64+
repoPath, err := filepath.Abs(repoConfigPath)
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
// open repo config file
70+
data, err := os.ReadFile(repoPath)
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
// parse the file non-strictly to account for any minor config changes
76+
rc := &ConfigFile{}
77+
if err := yaml.Unmarshal(data, rc); err != nil {
78+
return nil, err
79+
}
80+
81+
return rc, nil
82+
}
83+
6184
// Charts exposes the central Chartfile management functions
6285
type Charts struct {
6386
// Manifest are the chartfile.yaml contents. It holds data about the developers intentions
@@ -89,12 +112,20 @@ func (c Charts) ManifestFile() string {
89112

90113
// Vendor pulls all Charts specified in the manifest into the local charts
91114
// directory. It fetches the repository index before doing so.
92-
func (c Charts) Vendor(prune bool) error {
115+
func (c Charts) Vendor(prune bool, repoConfigPath string) error {
93116
dir := c.ChartDir()
94117
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
95118
return err
96119
}
97120

121+
if repoConfigPath != "" {
122+
repoConfig, err := LoadHelmRepoConfig(repoConfigPath)
123+
if err != nil {
124+
return err
125+
}
126+
c.Manifest.Repositories = repoConfig.Repositories
127+
}
128+
98129
// Check that there are no output conflicts before vendoring
99130
if err := c.Manifest.Requires.Validate(); err != nil {
100131
return err
@@ -199,7 +230,7 @@ func (c Charts) Vendor(prune bool) error {
199230

200231
// Add adds every Chart in reqs to the Manifest after validation, and runs
201232
// Vendor afterwards
202-
func (c *Charts) Add(reqs []string) error {
233+
func (c *Charts) Add(reqs []string, repoConfigPath string) error {
203234
log.Info().Msgf("Adding %v Charts ...", len(reqs))
204235

205236
// parse new charts, append in memory
@@ -238,7 +269,7 @@ func (c *Charts) Add(reqs []string) error {
238269

239270
// worked fine? vendor it
240271
log.Info().Msgf("Added %v Charts to helmfile.yaml. Vendoring ...", added)
241-
return c.Vendor(false)
272+
return c.Vendor(false, repoConfigPath)
242273
}
243274

244275
func (c *Charts) AddRepos(repos ...Repo) error {

pkg/helm/charts_test.go

+72-15
Original file line numberDiff line numberDiff line change
@@ -88,28 +88,47 @@ func TestAdd(t *testing.T) {
8888
c, err := InitChartfile(filepath.Join(tempDir, Filename))
8989
require.NoError(t, err)
9090

91-
err = c.Add([]string{"stable/[email protected]"})
91+
err = c.Add([]string{"stable/[email protected]"}, "")
9292
assert.NoError(t, err)
9393

9494
// Adding again the same chart
95-
err = c.Add([]string{"stable/[email protected]"})
95+
err = c.Add([]string{"stable/[email protected]"}, "")
9696
assert.EqualError(t, err, "1 Chart(s) were skipped. Please check above logs for details")
9797

9898
// Adding a chart with a different version to the same path, causes a conflict
99-
err = c.Add([]string{"stable/[email protected]"})
99+
err = c.Add([]string{"stable/[email protected]"}, "")
100100
assert.EqualError(t, err, `Validation errors:
101101
- output directory "prometheus" is used twice, by charts "stable/[email protected]" and "stable/[email protected]"`)
102102

103103
// Add a chart with a specific extract directory
104-
err = c.Add([]string{"stable/[email protected]:prometheus-11.12.0"})
104+
err = c.Add([]string{"stable/[email protected]:prometheus-11.12.0"}, "")
105+
assert.NoError(t, err)
106+
107+
// Add a chart while specifying a helm repo config file
108+
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "helmConfig.yaml"), []byte(`
109+
apiVersion: ""
110+
generated: "0001-01-01T00:00:00Z"
111+
repositories:
112+
- caFile: ""
113+
certFile: ""
114+
insecure_skip_tls_verify: false
115+
keyFile: ""
116+
name: private
117+
pass_credentials_all: false
118+
password: ""
119+
url: https://charts.helm.sh/stable
120+
username: ""
121+
`), 0644))
122+
err = c.Add([]string{"private/[email protected]:private-11.12.1"}, filepath.Join(tempDir, "helmConfig.yaml"))
105123
assert.NoError(t, err)
106124

107125
// Check file contents
108126
listResult, err := os.ReadDir(filepath.Join(tempDir, "charts"))
109127
assert.NoError(t, err)
110-
assert.Equal(t, 2, len(listResult))
111-
assert.Equal(t, "prometheus", listResult[0].Name())
112-
assert.Equal(t, "prometheus-11.12.0", listResult[1].Name())
128+
assert.Equal(t, 3, len(listResult))
129+
assert.Equal(t, "private-11.12.1", listResult[0].Name())
130+
assert.Equal(t, "prometheus", listResult[1].Name())
131+
assert.Equal(t, "prometheus-11.12.0", listResult[2].Name())
113132

114133
chartContent, err := os.ReadFile(filepath.Join(tempDir, "charts", "prometheus", "Chart.yaml"))
115134
assert.NoError(t, err)
@@ -118,6 +137,10 @@ func TestAdd(t *testing.T) {
118137
chartContent, err = os.ReadFile(filepath.Join(tempDir, "charts", "prometheus-11.12.0", "Chart.yaml"))
119138
assert.NoError(t, err)
120139
assert.Contains(t, string(chartContent), `version: 11.12.0`)
140+
141+
chartContent, err = os.ReadFile(filepath.Join(tempDir, "charts", "private-11.12.1", "Chart.yaml"))
142+
assert.NoError(t, err)
143+
assert.Contains(t, string(chartContent), `version: 11.12.1`)
121144
}
122145

123146
func TestAddOCI(t *testing.T) {
@@ -128,7 +151,7 @@ func TestAddOCI(t *testing.T) {
128151
err = c.AddRepos(Repo{Name: "karpenter", URL: "oci://public.ecr.aws/karpenter"})
129152
assert.NoError(t, err)
130153

131-
err = c.Add([]string{"karpenter/[email protected]"})
154+
err = c.Add([]string{"karpenter/[email protected]"}, "")
132155
assert.NoError(t, err)
133156

134157
// Check file contents
@@ -143,7 +166,7 @@ func TestRevendorDeletedFiles(t *testing.T) {
143166
c, err := InitChartfile(filepath.Join(tempDir, Filename))
144167
require.NoError(t, err)
145168

146-
err = c.Add([]string{"stable/[email protected]"})
169+
err = c.Add([]string{"stable/[email protected]"}, "")
147170
assert.NoError(t, err)
148171

149172
// Check file contents
@@ -153,7 +176,7 @@ func TestRevendorDeletedFiles(t *testing.T) {
153176

154177
// Delete the whole dir and revendor
155178
require.NoError(t, os.RemoveAll(filepath.Join(tempDir, "charts", "prometheus")))
156-
assert.NoError(t, c.Vendor(true))
179+
assert.NoError(t, c.Vendor(true, ""))
157180

158181
// Check file contents
159182
chartContent, err = os.ReadFile(filepath.Join(tempDir, "charts", "prometheus", "Chart.yaml"))
@@ -162,7 +185,7 @@ func TestRevendorDeletedFiles(t *testing.T) {
162185

163186
// Delete just the Chart.yaml and revendor
164187
require.NoError(t, os.Remove(filepath.Join(tempDir, "charts", "prometheus", "Chart.yaml")))
165-
assert.NoError(t, c.Vendor(true))
188+
assert.NoError(t, c.Vendor(true, ""))
166189

167190
// Check file contents
168191
chartContent, err = os.ReadFile(filepath.Join(tempDir, "charts", "prometheus", "Chart.yaml"))
@@ -178,17 +201,17 @@ func TestPrune(t *testing.T) {
178201
require.NoError(t, err)
179202

180203
// Add a chart
181-
require.NoError(t, c.Add([]string{"stable/[email protected]"}))
204+
require.NoError(t, c.Add([]string{"stable/[email protected]"}, ""))
182205

183206
// Add a chart with a directory
184-
require.NoError(t, c.Add([]string{"stable/[email protected]:custom-dir"}))
207+
require.NoError(t, c.Add([]string{"stable/[email protected]:custom-dir"}, ""))
185208

186209
// Add unrelated files and folders
187210
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "charts", "foo.txt"), []byte("foo"), 0644))
188211
require.NoError(t, os.Mkdir(filepath.Join(tempDir, "charts", "foo"), 0755))
189212
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "charts", "foo", "Chart.yaml"), []byte("foo"), 0644))
190213

191-
require.NoError(t, c.Vendor(prune))
214+
require.NoError(t, c.Vendor(prune, ""))
192215

193216
// Check if files are pruned
194217
listResult, err := os.ReadDir(filepath.Join(tempDir, "charts"))
@@ -217,7 +240,41 @@ func TestInvalidChartName(t *testing.T) {
217240
Version: "1.0.0",
218241
})
219242

220-
err = c.Vendor(false)
243+
err = c.Vendor(false, "")
221244
assert.EqualError(t, err, `Validation errors:
222245
- Chart name "noslash" is not valid. Expecting a repo/name format.`)
223246
}
247+
248+
func TestConfigFileOption(t *testing.T) {
249+
tempDir := t.TempDir()
250+
c, err := InitChartfile(filepath.Join(tempDir, Filename))
251+
require.NoError(t, err)
252+
253+
// Don't want to commit credentials so we just verify the "private" repo reference will make
254+
// use of this helm config since the InitChartfile does not have a reference to it.
255+
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "helmConfig.yaml"), []byte(`
256+
apiVersion: ""
257+
generated: "0001-01-01T00:00:00Z"
258+
repositories:
259+
- caFile: ""
260+
certFile: ""
261+
insecure_skip_tls_verify: false
262+
keyFile: ""
263+
name: private
264+
pass_credentials_all: false
265+
password: ""
266+
url: https://charts.helm.sh/stable
267+
username: ""
268+
`), 0644))
269+
c.Manifest.Requires = append(c.Manifest.Requires, Requirement{
270+
Chart: "private/prometheus",
271+
Version: "11.12.1",
272+
})
273+
274+
err = c.Vendor(false, filepath.Join(tempDir, "helmConfig.yaml"))
275+
assert.NoError(t, err)
276+
277+
chartContent, err := os.ReadFile(filepath.Join(tempDir, "charts", "prometheus", "Chart.yaml"))
278+
assert.NoError(t, err)
279+
assert.Contains(t, string(chartContent), `version: 11.12.1`)
280+
}

pkg/helm/spec.go

+13
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ type Chartfile struct {
3333
Directory string `json:"directory,omitempty"`
3434
}
3535

36+
// ConfigFile represents the default Helm config structure to be used in place of the chartfile
37+
// Repositories if supplied.
38+
type ConfigFile struct {
39+
// Version of the Helm repo config schema
40+
APIVersion string `json:"apiVersion"`
41+
42+
// The datetime of when this repo config was generated
43+
Generated string `json:"generated"`
44+
45+
// Repositories to source from
46+
Repositories Repos `json:"repositories"`
47+
}
48+
3649
// Repo describes a single Helm repository
3750
type Repo struct {
3851
Name string `json:"name,omitempty"`

0 commit comments

Comments
 (0)