...
1 package chariot
2
3 import (
4 "crypto/md5"
5 "fmt"
6
7 "gopkg.in/yaml.v2"
8
9 "edge-infra.dev/pkg/edge/constants"
10 )
11
12 const (
13 OperationCreate = "CREATE"
14 OperationDelete = "DELETE"
15 )
16
17
18 type Request struct {
19
20
21
22
23
24
25
26
27 Operation string `json:"operation"`
28
29
30 Objects [][]byte `json:"objects"`
31
32
33 Banner string `json:"banner"`
34
35
36 Cluster string `json:"cluster,omitempty"`
37
38
39
40
41 Owner string `json:"owner"`
42
43
44 Dir string `json:"dir,omitempty"`
45
46
47 Notify bool `json:"notify,omitempty"`
48 }
49
50 const (
51 defaultChariotDir = "chariot"
52 locationFormatBannerWideObjects = "gs://%s/%s/%s.yaml"
53 locationFormatClusterWideObjects = "gs://%s/%s/%s/%s.yaml"
54 )
55
56
57 var whitelistedDirs = map[string]bool{
58 defaultChariotDir: true,
59 constants.KustomizationsDir: true,
60 constants.ShipmentKustomizationDir: true,
61 constants.ExternalSecretKustomizationDir: true,
62 }
63
64
65 func (r *Request) Validate() error {
66 if "" == r.Operation {
67 return fmt.Errorf("Operation must not be empty")
68 }
69
70 switch r.Operation {
71 case OperationCreate:
72 case OperationDelete:
73 default:
74 return fmt.Errorf("Unsupported Operation %q", r.Operation)
75 }
76
77 if 0 == len(r.Objects) {
78 return fmt.Errorf("Objects must not be empty")
79 }
80
81 if "" == r.Banner {
82 return fmt.Errorf("Banner must not be empty")
83 }
84
85 if "" == r.Owner {
86 return fmt.Errorf("Owner must not be empty")
87 }
88
89 if "" == r.Dir {
90
91 r.Dir = defaultChariotDir
92 }
93 if !whitelistedDirs[r.Dir] {
94 return fmt.Errorf("The provided directory is not whitelisted: %q", r.Dir)
95 }
96
97
98 for _, obj := range r.Objects {
99 var gvknn, err = ParseYamlGVKNN(obj)
100 if err != nil {
101 return err
102 } else if err = gvknn.Validate(); err != nil {
103 return err
104 }
105 }
106 return nil
107 }
108
109
110
111
112 func (r *Request) StorageObjects() ([]StorageObject, error) {
113
114 if err := r.Validate(); err != nil {
115 return nil, err
116 }
117
118 var so []StorageObject
119 for _, obj := range r.Objects {
120 var gvknn, err = ParseYamlGVKNN(obj)
121 if err != nil {
122 return nil, err
123 } else if err = gvknn.Validate(); err != nil {
124 return nil, err
125 }
126 var location = FmtStorageLocation(r.Banner, r.Cluster, r.Dir, gvknn.Hash())
127 so = append(so, StorageObject{
128 Location: location,
129 Content: string(obj),
130 })
131 }
132 return so, nil
133 }
134
135
136
137 type YamlGVKNN struct {
138
139
140
141 APIVersion string `yaml:"apiVersion"`
142 Kind string `yaml:"kind"`
143 Metadata struct {
144 Name string `yaml:"name"`
145 Namespace string `yaml:"namespace"`
146 } `yaml:"metadata"`
147 }
148
149
150 func ParseYamlGVKNN(y []byte) (YamlGVKNN, error) {
151 var parsed YamlGVKNN
152 err := yaml.Unmarshal(y, &parsed)
153 return parsed, err
154 }
155
156 func (y YamlGVKNN) Validate() error {
157 if "" == y.APIVersion {
158 return fmt.Errorf("apiVersion must not be empty")
159 }
160
161 if "" == y.Kind {
162 return fmt.Errorf("kind must not be empty")
163 }
164
165 if "" == y.Metadata.Name {
166 return fmt.Errorf("metadata.name must not be empty")
167 }
168
169 return nil
170 }
171
172
173
174
175 const HashFormatGVKNN = "chariot\ngroup/version:%q\nkind:%q\nmetadata.name:%q\nmetadata.namespace:%q\nend"
176
177
178 func (y YamlGVKNN) Hash() string {
179 var s = fmt.Sprintf(HashFormatGVKNN, y.APIVersion, y.Kind, y.Metadata.Name, y.Metadata.Namespace)
180 var m = md5.Sum([]byte(s))
181 return fmt.Sprintf("%x", m)
182 }
183
184
185
186 func FmtStorageLocation(banner, cluster, dir, chariotID string) string {
187 if dir == "" {
188 dir = defaultChariotDir
189 }
190 var location string
191 if cluster == "" {
192 location = fmt.Sprintf(locationFormatBannerWideObjects, banner, dir, chariotID)
193 } else {
194 location = fmt.Sprintf(locationFormatClusterWideObjects, banner, cluster, dir, chariotID)
195 }
196 return location
197 }
198
View as plain text