-
Notifications
You must be signed in to change notification settings - Fork 165
/
helm.go
145 lines (115 loc) · 3.28 KB
/
helm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package helm
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/grafana/tanka/pkg/kubernetes/manifest"
)
// Helm provides high level access to some Helm operations
type Helm interface {
// Pull downloads a Helm Chart from a remote
Pull(chart, version string, opts PullOpts) error
// RepoUpdate fetches the latest remote index
RepoUpdate(opts Opts) error
// Template returns the individual resources of a Helm Chart
Template(name, chart string, opts TemplateOpts) (manifest.List, error)
}
// PullOpts are additional, non-required options for Helm.Pull
type PullOpts struct {
Opts
// Directory to put the resulting .tgz into
Destination string
// Where to extract the chart to, defaults to the name of the chart
ExtractDirectory string
}
// Opts are additional, non-required options that all Helm operations accept
type Opts struct {
Repositories []Repo
}
// ExecHelm is a Helm implementation powered by the `helm` command line utility
type ExecHelm struct{}
// Pull implements Helm.Pull
func (e ExecHelm) Pull(chart, version string, opts PullOpts) error {
repoFile, err := writeRepoTmpFile(opts.Repositories)
if err != nil {
return err
}
defer os.Remove(repoFile)
// Pull to a temp dir within the destination directory (not /tmp) to avoid possible cross-device issues when renaming
tempDir, err := os.MkdirTemp(opts.Destination, ".pull-")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
cmd := e.cmd("pull", chart,
"--version", version,
"--repository-config", repoFile,
"--destination", tempDir,
"--untar",
)
if err = cmd.Run(); err != nil {
return err
}
chartName := parseReqName(chart)
if opts.ExtractDirectory == "" {
opts.ExtractDirectory = chartName
}
// It is not possible to tell `helm pull` to extract to a specific directory
// so we extract to a temp dir and then move the files to the destination
return os.Rename(
filepath.Join(tempDir, chartName),
filepath.Join(opts.Destination, opts.ExtractDirectory),
)
}
// RepoUpdate implements Helm.RepoUpdate
func (e ExecHelm) RepoUpdate(opts Opts) error {
repoFile, err := writeRepoTmpFile(opts.Repositories)
if err != nil {
return err
}
defer os.Remove(repoFile)
cmd := e.cmd("repo", "update",
"--repository-config", repoFile,
)
var errBuf bytes.Buffer
cmd.Stderr = &errBuf
if err := cmd.Run(); err != nil {
return fmt.Errorf("%s\n%s", errBuf.String(), err)
}
return nil
}
// cmd returns a prepared exec.Cmd to use the `helm` binary
func (e ExecHelm) cmd(action string, args ...string) *exec.Cmd {
argv := []string{action}
argv = append(argv, args...)
cmd := helmCmd(argv...)
cmd.Stderr = os.Stderr
return cmd
}
// helmCmd returns a bare exec.Cmd pointed at the local helm binary
func helmCmd(args ...string) *exec.Cmd {
bin := "helm"
if env := os.Getenv("TANKA_HELM_PATH"); env != "" {
bin = env
}
return exec.Command(bin, args...)
}
// writeRepoTmpFile creates a temporary repositories.yaml from the passed Repo
// slice to be used by the helm binary
func writeRepoTmpFile(r []Repo) (string, error) {
m := map[string]interface{}{
"repositories": r,
}
f, err := os.CreateTemp("", "charts-repos")
if err != nil {
return "", err
}
enc := json.NewEncoder(f)
if err := enc.Encode(m); err != nil {
return "", err
}
return f.Name(), nil
}