forked from jenkins-x-plugins/jx-gitops
-
Notifications
You must be signed in to change notification settings - Fork 0
/
validate.go
151 lines (125 loc) · 4.15 KB
/
validate.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
package validate
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/jenkins-x/jx-gitops/pkg/rootcmd"
"github.com/jenkins-x/jx-helpers/v3/pkg/cobras/helper"
"github.com/jenkins-x/jx-helpers/v3/pkg/cobras/templates"
"github.com/jenkins-x/jx-helpers/v3/pkg/yaml2s"
"github.com/pkg/errors"
"github.com/roboll/helmfile/pkg/state"
"github.com/spf13/cobra"
)
var (
cmdLong = templates.LongDesc(`
Parses a helmfile and any nested helmfiles and validates they conform to a canonical directory structure for jx based around namespace
`)
cmdExample = templates.Examples(`
# Validates helmfile.yaml within the current directory
%s helmfile validate
`)
)
type Options struct {
Dir string
Helmfile string
OutputDir string
}
func NewCmdHelmfileValidate() (*cobra.Command, *Options) {
o := &Options{}
cmd := &cobra.Command{
Use: "validate",
Short: "Validates helmfile.yaml against a jx canonical tree of helmfiles",
Long: cmdLong,
Example: fmt.Sprintf(cmdExample, rootcmd.BinaryName, rootcmd.BinaryName),
Run: func(cmd *cobra.Command, args []string) {
err := o.Run()
helper.CheckErr(err)
},
}
cmd.Flags().StringVarP(&o.Helmfile, "helmfile", "", "", "the helmfile to template. Defaults to 'helmfile.yaml' in the directory")
cmd.Flags().StringVarP(&o.Dir, "dir", "d", ".", "the directory that contains helmfile.yml")
return cmd, o
}
// Validate validates the options and populates any missing values
func (o *Options) Validate() error {
var err error
if o.Helmfile == "" {
o.Helmfile = filepath.Join(o.Dir, "helmfile.yaml")
}
if o.OutputDir == "" {
o.OutputDir, err = ioutil.TempDir("", "")
if err != nil {
return errors.Wrapf(err, "failed to create temporary output directory")
}
}
return nil
}
func (o Options) Run() error {
err := o.Validate()
if err != nil {
return errors.Wrapf(err, "failed to validate")
}
rootHelmState := state.HelmState{}
err = yaml2s.LoadFile(o.Helmfile, &rootHelmState)
for _, nestedState := range rootHelmState.Helmfiles {
err := o.validateSubHelmFile(nestedState.Path)
if err != nil {
return fmt.Errorf("failed to process nested helmfile %s with error %w", nestedState.Path, err)
}
}
return nil
}
func (o Options) validateSubHelmFile(path string) error {
targetNamespace, err := o.getSubHelmfileNamespace(path)
if err != nil {
return fmt.Errorf("failed to determine namespace from path %w", err)
}
helmState := state.HelmState{}
err = yaml2s.LoadFile(filepath.Join(o.Dir, path), &helmState)
if err != nil {
return fmt.Errorf("failed to load helmfile - %w", err)
}
for _, release := range helmState.Releases {
if release.Namespace != targetNamespace {
return fmt.Errorf("namespace for release %s is %s does not match namespace of folder %s", release.Name, release.Namespace, targetNamespace)
}
chartRepo, err := getChartRepository(release.Chart)
if err != nil {
return fmt.Errorf("failed parsing repo name for chart %s", release.Chart)
}
if err := checkChartRepositoryExists(chartRepo, helmState.Repositories); err != nil {
return fmt.Errorf("error finding chart repo for %s", chartRepo)
}
}
return nil
}
func checkChartRepositoryExists(chartRepo string, repos []state.RepositorySpec) error {
for _, repo := range repos {
if chartRepo == repo.Name {
return nil
}
}
return fmt.Errorf("repo for chart %s does not exist in repo list", chartRepo)
}
func getChartRepository(chart string) (string, error) {
chartSplit := strings.Split(chart, "/")
if len(chartSplit) != 2 {
return "", fmt.Errorf("failed to determine chartname for %s", chart)
}
return chartSplit[0], nil
}
func (o Options) getSubHelmfileNamespace(path string) (string, error) {
subHelmRel := path
if filepath.IsAbs(path) {
return "", fmt.Errorf("nested helmfiles should be specified relative to root helmfile")
}
subHelmRelDir, _ := filepath.Split(subHelmRel)
subHelmRelDir = filepath.Clean(subHelmRelDir)
directories := strings.Split(subHelmRelDir, string(filepath.Separator))
if len(directories) != 2 {
return "", fmt.Errorf("nested helmfile %s is not a grandchild of parent helmfile - should be stored within ./helmfiles/namespace", path)
}
return directories[1], nil
}