1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package snippetgeneration
16
17 import (
18 "fmt"
19 "path"
20 "strings"
21
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/fileutil"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/mapslice"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/repo"
25
26 "github.com/ghodss/yaml"
27 goyaml "gopkg.in/yaml.v2"
28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29 )
30
31
32
33
34 var preferredSampleForResource = map[string]string{
35 "bigqueryjob": "query-bigquery-job",
36 "bigtableappprofile": "multicluster-bigtable-app-profile",
37 "bigtableinstance": "replicated-instance",
38 "billingbudgetsbudget": "calendar-budget",
39 "binaryauthorizationpolicy": "cluster-policy",
40 "cloudbuildtrigger": "build-trigger-for-cloud-source-repo",
41 "cloudfunctionsfunction": "httpstrigger",
42 "cloudidentitymembership": "membership-with-manager-role",
43 "cloudschedulerjob": "scheduler-job-pubsub",
44 "computehealthcheck": "global-health-check",
45 "computeaddress": "global-compute-address",
46 "computebackendbucket": "basic-backend-bucket",
47 "computebackendservice": "external-load-balancing-backend-service",
48 "computedisk": "zonal-compute-disk",
49 "computefirewall": "allow-rule-firewall",
50 "computefirewallpolicyassociation": "association-with-folder-attachment-target",
51 "computeforwardingrule": "global-forwarding-rule-with-target-http-proxy",
52 "computeimage": "image-from-url-raw",
53 "computeinstance": "cloud-machine-instance",
54 "computeinstancegroupmanager": "regional-compute-instance-group-manager",
55 "computenodetemplate": "flexible-node-template",
56 "computeregionnetworkendpointgroup": "cloud-function-region-network-endpoint-group",
57 "computereservation": "specialized-compute-reservation",
58 "computeresourcepolicy": "weekly-resource-policy-schedule",
59 "computerouternat": "router-nat-for-all-subnets",
60 "computesecuritypolicy": "multirule-security-policy",
61 "computesslcertificate": "global-compute-ssl-certificate",
62 "computesslpolicy": "modern-tls-1-1-ssl-policy",
63 "computeurlmap": "global-compute-url-map",
64 "configcontrollerinstance": "autopilot-config-controller-instance",
65 "containercluster": "vpc-native-container-cluster",
66 "containernodepool": "basic-node-pool",
67 "dataflowjob": "streaming-dataflow-job",
68 "dataflowflextemplatejob": "streaming-dataflow-flex-template-job",
69 "dlpstoredinfotype": "big-query-field-stored-info-type",
70 "dlpdeidentifytemplate": "info-type-deidentify-template",
71 "dlpinspecttemplate": "custom-inspect-template",
72 "dlpjobtrigger": "big-query-job-trigger",
73 "dnsrecordset": "dns-a-record-set",
74 "folder": "folder-in-folder",
75 "gkehubfeature": "multi-cluster-ingress-feature",
76 "gkehubfeaturemembership": "config-management-feature-membership",
77 "iamauditconfig": "project-level-audit-config",
78 "iamcustomrole": "project-role",
79 "iampolicy": "external-project-level-policy",
80 "iampartialpolicy": "project-level-policy",
81 "iampolicymember": "external-project-level-policy-member",
82 "iamworkforcepoolprovider": "oidc-workforce-pool-provider",
83 "iamworkloadidentitypoolprovider": "oidc-workload-identity-pool-provider",
84 "logginglogbucket": "project-log-bucket",
85 "logginglogexclusion": "project-exclusion",
86 "logginglogmetric": "linear-log-metric",
87 "logginglogsink": "project-sink",
88 "logginglogview": "project-log-view",
89 "monitoringalertpolicy": "network-connectivity-alert-policy",
90 "monitoringnotificationchannel": "sms-monitoring-notification-channel",
91 "monitoringservicelevelobjective": "window-based-gtr-distribution-cut",
92 "monitoringuptimecheckconfig": "http-uptime-check-config",
93 "osconfigospolicyassignment": "fixed-os-policy-assignment",
94 "privatecacertificate": "basic-certificate",
95 "project": "project-in-folder",
96 "pubsubsubscription": "basic-pubsub-subscription",
97 "runjob": "basic-job",
98 "recaptchaenterprisekey": "challenge-based-web-recaptcha-enterprise-key",
99 "resourcemanagerpolicy": "organization-policy-for-project",
100 "secretmanagersecret": "automatic-secret-replication",
101 "sqlinstance": "mysql-sql-instance",
102 "vpcaccessconnector": "cidr-connector",
103 }
104
105 type Snippet struct {
106 Label string `yaml:"label"`
107 MarkdownDescription string `yaml:"markdownDescription"`
108 InsertText string `yaml:"insertText"`
109 }
110
111
112
113
114
115 func PathToSampleFileUsedForSnippets(resourceDirName string) (string, error) {
116 samplesPath := repo.GetResourcesSamplesPath()
117
118 resourceDirPath := path.Join(samplesPath, resourceDirName)
119 dirExists, err := fileutil.DirExists(resourceDirPath)
120 if err != nil {
121 return "", fmt.Errorf("error: failed to determine if directory with name %v exists in %v: %v", resourceDirName, samplesPath, err)
122 }
123 if !dirExists {
124 return "", fmt.Errorf("error: no directory with name %v found in %v", resourceDirName, samplesPath)
125 }
126
127 hasSubdirs, err := fileutil.HasSubdirs(resourceDirPath)
128 if err != nil {
129 return "", fmt.Errorf("error determining if directory at %v has subdirectories: %v", resourceDirPath, err)
130 }
131
132 sampleDirPath := resourceDirPath
133 if hasSubdirs {
134 sampleDirPath, err = pathToPreferredSamplesSubdirForResource(resourceDirPath)
135 if err != nil {
136 return "", err
137 }
138 }
139
140 fileNames, err := fileutil.FileNamesWithSuffixInDir(sampleDirPath, resourceDirName+".yaml")
141 if err != nil || len(fileNames) != 1 {
142 return "", fmt.Errorf("error getting exactly one file to use for generating snippets: %v", err)
143 }
144
145 return path.Join(sampleDirPath, fileNames[0]), nil
146 }
147
148 func pathToPreferredSamplesSubdirForResource(resourceDirPath string) (string, error) {
149 resourceDirName := path.Base(resourceDirPath)
150 sampleSubdirName, ok := preferredSampleForResource[resourceDirName]
151 if !ok {
152 return "", fmt.Errorf("error: no sample subdirectory specified for resource directory '%v'", resourceDirName)
153 }
154 sampleSubdirPath := path.Join(resourceDirPath, sampleSubdirName)
155 dirExists, err := fileutil.DirExists(sampleSubdirPath)
156 if err != nil {
157 return "", fmt.Errorf("error: failed to determine if directory at %v exists: %v", sampleSubdirPath, err)
158 }
159 if !dirExists {
160 return "", fmt.Errorf("error: no directory found at %v", sampleSubdirPath)
161 }
162 return sampleSubdirPath, nil
163 }
164
165 func SnippifyResourceConfig(resourceConfig []byte) (Snippet, error) {
166 kind, err := resourceKind(resourceConfig)
167 if err != nil {
168 return Snippet{}, fmt.Errorf("error parsing resource kind from resource config: %v", err)
169 }
170 config, err := snippifyResourceConfig(kind, resourceConfig)
171 if err != nil {
172 return Snippet{}, fmt.Errorf("error snippifying resource config: %v", err)
173 }
174 return Snippet{
175 Label: "Config Connector " + kind,
176 MarkdownDescription: fmt.Sprintf("Creates yaml for a %v resource", kind),
177 InsertText: config,
178 }, nil
179 }
180
181 func snippifyResourceConfig(kind string, config []byte) (string, error) {
182 var mapSlice goyaml.MapSlice
183 err := goyaml.Unmarshal(config, &mapSlice)
184 if err != nil {
185 return "", fmt.Errorf("error unmarshalling bytes: %v", err)
186 }
187
188 newMapSlice := goyaml.MapSlice{}
189 varNum := 1
190 for _, item := range mapSlice {
191 switch key := item.Key.(string); key {
192 case "metadata":
193 item.Value = snippifyMetadata(item.Value, kind, &varNum)
194 case "spec":
195 item.Value = snippifyAllLeavesInTree(item.Value, &varNum)
196 }
197 newMapSlice = append(newMapSlice, item)
198 }
199
200 out, err := goyaml.Marshal(newMapSlice)
201 if err != nil {
202 return "", fmt.Errorf("error marshalling bytes to YAML: %v", err)
203 }
204 return string(out), nil
205 }
206
207 func snippifyMetadata(metadataFields interface{}, kind string, varNum *int) interface{} {
208 m := metadataFields.(goyaml.MapSlice)
209 out := goyaml.MapSlice{}
210
211 labels := mapslice.Value(m, "labels")
212 name := mapslice.Value(m, "name")
213
214 if labels != nil {
215 labels := labels.(goyaml.MapSlice)
216 newLabels := make([]goyaml.MapItem, 0)
217 for _, l := range labels {
218 newLabels = append(newLabels, goyaml.MapItem{
219 Key: snippifyVal(l.Key.(string), varNum),
220 Value: snippifyVal(l.Value.(string), varNum),
221 })
222 }
223 out = append(out, goyaml.MapItem{
224 Key: "labels",
225 Value: newLabels,
226 })
227 }
228 if name != nil {
229 out = append(out, goyaml.MapItem{
230 Key: "name",
231 Value: snippifyVal(strings.ToLower(kind)+"-name", varNum),
232 })
233 }
234 return out
235 }
236
237 func snippifyAllLeavesInTree(node interface{}, varNum *int) interface{} {
238 switch v := node.(type) {
239 case goyaml.MapSlice:
240 out := goyaml.MapSlice{}
241 for _, item := range v {
242 item.Value = snippifyAllLeavesInTree(item.Value, varNum)
243 out = append(out, item)
244 }
245 return out
246 case []interface{}:
247 out := make([]interface{}, 0)
248 for _, val := range v {
249 out = append(out, snippifyAllLeavesInTree(val, varNum))
250 }
251 return out
252 default:
253 return snippifyVal(fmt.Sprintf("%v", v), varNum)
254 }
255 }
256
257 func snippifyVal(s string, varNum *int) string {
258
259 s = strings.ReplaceAll(s, "$", "")
260 s = strings.ReplaceAll(s, "{", "[")
261 s = strings.ReplaceAll(s, "}", "]")
262
263 v := fmt.Sprintf("\\${%v:%v}", *varNum, s)
264 *varNum++
265 return v
266 }
267
268 func resourceKind(config []byte) (string, error) {
269 u := &unstructured.Unstructured{}
270 err := yaml.Unmarshal(config, u)
271 if err != nil {
272 return "", fmt.Errorf("error unmarshalling bytes to CRD: %v", err)
273 }
274 return u.GetKind(), nil
275 }
276
View as plain text