/
helm.go
169 lines (155 loc) · 5.25 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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/go-logr/logr"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/registry"
"sigs.k8s.io/yaml"
)
// helmDriver implements PackageDriver to install packages from Helm charts.
type helmDriver struct {
cfg *action.Configuration
log logr.Logger
settings *cli.EnvSettings
}
func NewHelm(log logr.Logger, authfile string) (*helmDriver, error) {
settings := cli.New()
options := registry.ClientOptCredentialsFile(authfile)
client, err := registry.NewClient(options)
if err != nil {
return nil, fmt.Errorf("creating registry client while initializing helm driver: %w", err)
}
cfg := &action.Configuration{RegistryClient: client}
err = cfg.Init(settings.RESTClientGetter(), settings.Namespace(),
os.Getenv("HELM_DRIVER"), helmLog(log))
if err != nil {
return nil, fmt.Errorf("initializing helm driver: %w", err)
}
return &helmDriver{
cfg: cfg,
log: log,
settings: settings,
}, nil
}
// PullHelmChart will take in a a remote Helm URI and attempt to pull down the chart if it exists.
func (d *helmDriver) PullHelmChart(name, version string) (string, error) {
BundleLog.Info("Pulling Helm chart", "URI", name, "version", version)
if name == "" || version == "" {
return "", fmt.Errorf("empty input for PullHelmChart, check flags")
}
install := action.NewInstall(d.cfg)
install.ChartPathOptions.Version = version
if !strings.HasPrefix(name, "oci://") {
name = fmt.Sprintf("oci://%s", name)
}
chartPath, err := install.LocateChart(name, d.settings)
if err != nil || chartPath == "" {
return "", fmt.Errorf("running the Helm LocateChart command, you might need run an AWS ECR Login: %w", err)
}
return chartPath, nil
}
// helmLog wraps logr.Logger to make it compatible with helm's DebugLog.
func helmLog(log logr.Logger) action.DebugLog {
return func(template string, args ...interface{}) {
log.Info(fmt.Sprintf(template, args...))
}
}
// UnTarHelmChart will attempt to move the helm chart out of the helm cache, by untaring it to the pwd and creating the filesystem to unpack it into.
func UnTarHelmChart(chartRef, chartPath, dest string) error {
if chartRef == "" || chartPath == "" || dest == "" {
return fmt.Errorf("Empty input value given for UnTarHelmChart")
}
_, err := os.Stat(dest)
if os.IsNotExist(err) {
if _, err := os.Stat(chartPath); err != nil {
if err := os.MkdirAll(chartPath, 0o755); err != nil {
return errors.Wrap(err, "failed to untar (mkdir)")
}
} else {
return errors.Errorf("failed to untar: a file or directory with the name %s already exists", dest)
}
} else {
if err != nil { // Checks directory check errors such as permission issues to read
return errors.Errorf("failed UnTarHelmChart: %s", err)
}
}
// Untar the files, and create the directory structure
return chartutil.ExpandFile(dest, chartRef)
}
// hasRequires checks for the existance of the requires.yaml within the helm directory
func hasRequires(helmdir string) (string, error) {
requires := filepath.Join(helmdir, "requires.yaml")
info, err := os.Stat(requires)
if os.IsNotExist(err) {
return "", err
}
if info.IsDir() {
return "", fmt.Errorf("Found Dir, not requires.yaml file")
}
return requires, nil
}
// validateHelmRequires runs the parse file into struct function, and validations
func validateHelmRequires(fileName string) (*Requires, error) {
helmrequires := &Requires{}
err := parseHelmRequires(fileName, helmrequires)
if err != nil {
return nil, err
}
err = validateHelmRequiresContent(helmrequires)
if err != nil {
return nil, err
}
return helmrequires, err
}
// validateHelmRequiresContent loops over the validation tests
func validateHelmRequiresContent(helmrequires *Requires) error {
for _, v := range helmRequiresValidations {
if err := v(helmrequires); err != nil {
return err
}
}
return nil
}
var helmRequiresValidations = []func(*Requires) error{
validateHelmRequiresName,
}
func validateHelmRequiresName(helmrequires *Requires) error {
err := helmrequires.validateHelmRequiresNotEmpty()
if err != nil {
return err
}
return nil
}
// validateHelmRequiresNotEmpty checks that it has at least one image in the spec
func (helmrequires *Requires) validateHelmRequiresNotEmpty() error {
// Check if Projects are listed
if len(helmrequires.Spec.Images) < 1 {
return fmt.Errorf("Should use non-empty list of images for requires")
}
return nil
}
// parseHelmRequires will attempt to unpack the requires.yaml into the Go struct `Requires`
func parseHelmRequires(fileName string, helmrequires *Requires) error {
content, err := ioutil.ReadFile(fileName)
if err != nil {
return fmt.Errorf("unable to read file due to: %v", err)
}
for _, c := range strings.Split(string(content), YamlSeparator) {
if err = yaml.Unmarshal([]byte(c), helmrequires); err != nil {
return fmt.Errorf("unable to parse %s\nyaml: %s\n %v", fileName, string(c), err)
}
err = yaml.UnmarshalStrict([]byte(c), helmrequires)
if err != nil {
return fmt.Errorf("unable to UnmarshalStrict %s\nyaml: %s\n %v", helmrequires, string(c), err)
}
return nil
}
return fmt.Errorf("cluster spec file %s is invalid or does not contain kind %v", fileName, helmrequires)
}