Skip to content

Commit 05bc678

Browse files
committed
Support charts caching
1 parent 6666ec0 commit 05bc678

File tree

2 files changed

+137
-14
lines changed

2 files changed

+137
-14
lines changed

helm/digest.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package helm
2+
3+
import (
4+
"crypto"
5+
"encoding/hex"
6+
"io"
7+
"os"
8+
)
9+
10+
// DigestFile calculates a SHA256 hash (like Docker) for a given file.
11+
//
12+
// It takes the path to the archive file, and returns a string representation of
13+
// the SHA256 sum.
14+
//
15+
// The intended use of this function is to generate a sum of a chart TGZ file.
16+
func DigestFile(filename string) (string, error) {
17+
f, err := os.Open(filename)
18+
if err != nil {
19+
return "", err
20+
}
21+
defer f.Close()
22+
return Digest(f)
23+
}
24+
25+
// Digest hashes a reader and returns a SHA256 digest.
26+
//
27+
// Helm uses SHA256 as its default hash for all non-cryptographic applications.
28+
func Digest(in io.Reader) (string, error) {
29+
hash := crypto.SHA256.New()
30+
if _, err := io.Copy(hash, in); err != nil {
31+
return "", nil
32+
}
33+
return hex.EncodeToString(hash.Sum(nil)), nil
34+
}

main.go

+103-14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"net/url"
1010
"os"
11+
"path"
1112
"strings"
1213
"time"
1314

@@ -24,8 +25,17 @@ const (
2425
)
2526

2627
var client *github.Client
28+
var cacheDirBase string
29+
var chartsCacheDir string
2730

2831
func init() {
32+
if env, ok := os.LookupEnv("HELMGITHUB_DEBUG_LOG"); ok {
33+
t, err := os.OpenFile(env, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
34+
if err != nil {
35+
log.Panic(err)
36+
}
37+
log.SetOutput(t)
38+
}
2939
token, err := loadGithubToken()
3040
if err != nil {
3141
log.Panic(err)
@@ -35,18 +45,11 @@ func init() {
3545
)
3646
tc := oauth2.NewClient(context.Background(), ts)
3747
client = github.NewClient(tc)
48+
cacheDirBase = getCacheDirBase()
49+
chartsCacheDir = getChartCacheDir()
3850
}
3951

4052
func main() {
41-
if env, ok := os.LookupEnv("HELMGITHUB_DEBUG_LOG"); ok {
42-
t, err := os.OpenFile(env, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
43-
if err != nil {
44-
log.Panic(err)
45-
}
46-
defer t.Close()
47-
log.SetOutput(t)
48-
}
49-
5053
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
5154
defer cancel()
5255

@@ -65,17 +68,62 @@ func main() {
6568
}
6669
fmt.Println(string(bytes))
6770
} else {
68-
rc, err := fetchArchive(ctx, uri)
71+
cacheFile, err := openCacheFile(uri)
6972
if err != nil {
7073
log.Panic(err)
7174
}
72-
defer rc.Close()
73-
_, err = io.Copy(os.Stdout, rc)
74-
if err != nil {
75-
log.Panic(err)
75+
defer cacheFile.Close()
76+
ok := validateDigest(getArchiveDigest(uri), cacheFile.Name())
77+
if !ok {
78+
resp, err := fetchArchive(ctx, uri)
79+
if err != nil {
80+
_ = os.Remove(cacheFile.Name())
81+
log.Panic(err)
82+
}
83+
defer resp.Close()
84+
_, err = io.Copy(io.MultiWriter(os.Stdout, cacheFile), resp)
85+
if err != nil {
86+
_ = os.Remove(cacheFile.Name())
87+
log.Panic(err)
88+
}
89+
} else {
90+
_, err := io.Copy(os.Stdout, cacheFile)
91+
if err != nil {
92+
log.Panic(err)
93+
}
94+
}
95+
}
96+
}
97+
}
98+
99+
func getArchiveDigest(uri string) string {
100+
_, r := parseOwnerRepository(uri)
101+
bytes, err := os.ReadFile(path.Join(cacheDirBase, r+"-index.yaml"))
102+
if err != nil {
103+
return ""
104+
}
105+
idx := helm.IndexFile{}
106+
if err := yaml.Unmarshal(bytes, &idx); err != nil {
107+
return ""
108+
}
109+
for _, versions := range idx.Entries {
110+
for _, version := range versions {
111+
for _, u := range version.URLs {
112+
if strings.HasSuffix(u, uri) {
113+
return version.Digest
114+
}
76115
}
77116
}
78117
}
118+
return ""
119+
}
120+
121+
func validateDigest(digest string, fileName string) bool {
122+
df, err := helm.DigestFile(fileName)
123+
if err != nil {
124+
log.Panic(err)
125+
}
126+
return digest == df
79127
}
80128

81129
func loadGithubToken() (string, error) {
@@ -103,6 +151,29 @@ func getIndexBranch() string {
103151
return "gh-pages"
104152
}
105153

154+
func getChartCacheDir() string {
155+
dir := getCacheDirBase()
156+
dir = path.Join(dir, "github", "chart")
157+
if err := os.MkdirAll(dir, 0o777); err != nil {
158+
log.Panic(err)
159+
}
160+
return dir
161+
}
162+
163+
func getCacheDirBase() string {
164+
var dir string
165+
if env, ok := os.LookupEnv("HELM_REPOSITORY_CACHE"); ok {
166+
dir = env
167+
} else {
168+
ucd, err := os.UserCacheDir()
169+
if err != nil {
170+
log.Panic(err)
171+
}
172+
dir = ucd
173+
}
174+
return dir
175+
}
176+
106177
func fetchIndexFile(ctx context.Context, uri string) (helm.IndexFile, error) {
107178
owner, repository := parseOwnerRepository(uri)
108179
contents, _, _, err := client.Repositories.GetContents(ctx, owner, repository, indexFilename, &github.RepositoryContentGetOptions{Ref: getIndexBranch()})
@@ -121,6 +192,24 @@ func fetchIndexFile(ctx context.Context, uri string) (helm.IndexFile, error) {
121192
return file, nil
122193
}
123194

195+
func openCacheFile(uri string) (*os.File, error) {
196+
artifactName := parseArtifactName(uri)
197+
chartPath := path.Join(chartsCacheDir, artifactName+".tgz")
198+
_, err := os.Stat(chartPath)
199+
if err != nil {
200+
create, err := os.Create(chartPath)
201+
if err != nil {
202+
return nil, err
203+
}
204+
return create, nil
205+
}
206+
open, err := os.Open(chartPath)
207+
if err != nil {
208+
return nil, err
209+
}
210+
return open, nil
211+
}
212+
124213
func fetchArchive(ctx context.Context, uri string) (io.ReadCloser, error) {
125214
owner, repository := parseOwnerRepository(uri)
126215
tag := parseArtifactName(uri)

0 commit comments

Comments
 (0)