-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
235 lines (195 loc) · 7.54 KB
/
main.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package main
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"gopkg.in/yaml.v3"
"github.com/jessevdk/go-flags"
log "github.com/sirupsen/logrus"
"github.com/yargevad/filepathx"
)
var (
version = "dev"
commit = "none"
date = "unknown"
builtBy = "unknown"
)
type Options struct {
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
Version bool `long:"version" description:"Show version information"`
Environment string `short:"e" long:"environment" description:"The cloudtruth environment" default:"default" env:"CLOUDTRUTH_ENVIRONMENT"`
Project string `short:"p" long:"project" description:"The cloudtruth project" env:"CLOUDTRUTH_PROJECT"`
Tag string `short:"t" long:"tag" description:"The environment tag to restrict values to" env:"CLOUDTRUTH_TAG"`
ReferencePattern string `short:"r" long:"reference-pattern" description:"The reference pattern (go fmt) to substitute with parameters" default:"<%s>" env:"CLOUDTRUTH_REFERENCE_PATTERN"`
FilePattern []string `short:"f" long:"file-pattern" description:"The file pattern (glob) to perform substitutions on" default:"*.y*ml" env:"CLOUDTRUTH_FILE_PATTERN" env-delim:","`
ApiKey string `short:"a" long:"api-key" description:"The cloudtruth api key" env:"CLOUDTRUTH_API_KEY"`
ApiUrl string `short:"u" long:"api-url" description:"The cloudtruth api url" env:"CLOUDTRUTH_API_URL" hidden:"true" default:"https://api.cloudtruth.io"`
}
func mergeArgoEnv(name string, defaultValue string, pluginConfig map[string]string) string {
value := defaultValue
// Sytem env - we get defaults from here, they are set via in an Secret mounted with envFrom in the patch applied to
// the argocd-repo-server in our setup.sh
v := os.Getenv(name)
if v != "" {
value = v
}
// Params set within the yaml file used to trigger auto discovery of the plugin
v = pluginConfig[strings.Replace(strings.ToLower(name), "_", "-", -1)]
if v != "" {
value = v
}
// Env set for the plugin as part of its attachment to the argocd Application
v = os.Getenv("ARGOCD_ENV_" + name)
if v != "" {
value = v
}
// Params set for the plugin as part of its attachment to the argocd Application
v = os.Getenv("PARAM_" + name)
if v != "" {
value = v
}
return value
}
func readPluginConfig(filename string) map[string]string {
log.Debugf("Parsing plugin config file '%s'", filename)
data := make(map[string]string)
yfile, err := os.ReadFile(filename)
if err != nil {
log.Warnf("Unable to read plugin config from file '%s': %s", filename, err)
return data
}
err2 := yaml.Unmarshal(yfile, &data)
if err2 != nil {
log.Warnf("Could not parse yaml from plugin config file '%s': %s", filename, err2)
return data
}
return data
}
func setLogLevel(level int) {
switch level {
case 2:
log.SetLevel(log.DebugLevel)
case 1:
log.SetLevel(log.InfoLevel)
case 0:
log.SetLevel(log.WarnLevel)
default:
log.SetLevel(log.TraceLevel)
}
}
// Processes given files to replace paramater references with values from cloudtruth
func main() {
log.SetOutput(os.Stderr)
var config Options
p := flags.NewParser(&config, flags.Default)
p.LongDescription = "Processes given files to replace paramater references with values from cloudtruth."
_, err := p.Parse()
if config.Version {
fmt.Printf("argocd-cloudtruth-plugin %s, commit %s, built at %s by %s\n", version, commit, date, builtBy)
os.Exit(1)
}
if err != nil {
os.Exit(1)
}
pluginConfig := readPluginConfig(".argocd-cloudtruth-plugin")
log_level := mergeArgoEnv("CLOUDTRUTH_LOG_LEVEL", strconv.Itoa(len(config.Verbose)), pluginConfig)
log_level_int, err := strconv.Atoi(log_level)
if err != nil {
fmt.Printf("Invalid log level '%s'\n", log_level)
os.Exit(1)
}
setLogLevel(log_level_int)
config.ApiUrl = mergeArgoEnv("CLOUDTRUTH_API_URL", config.ApiUrl, pluginConfig)
config.ApiKey = mergeArgoEnv("CLOUDTRUTH_API_KEY", config.ApiKey, pluginConfig)
config.Environment = mergeArgoEnv("CLOUDTRUTH_ENVIRONMENT", config.Environment, pluginConfig)
config.Project = mergeArgoEnv("CLOUDTRUTH_PROJECT", config.Project, pluginConfig)
config.Tag = mergeArgoEnv("CLOUDTRUTH_TAG", config.Tag, pluginConfig)
config.ReferencePattern = mergeArgoEnv("CLOUDTRUTH_REFERENCE_PATTERN", config.ReferencePattern, pluginConfig)
config.FilePattern = strings.Split(mergeArgoEnv("CLOUDTRUTH_FILE_PATTERN", strings.Join(config.FilePattern, ","), pluginConfig), ",")
if config.ApiKey == "" {
fmt.Printf("An api key is required\n")
os.Exit(1)
}
if config.Project == "" {
fmt.Printf("A project is required\n")
os.Exit(1)
}
log.Debug("ApiUrl: ", config.ApiUrl)
log.Debug("Environment: ", config.Environment)
log.Debug("Project: ", config.Project)
log.Trace("ALL Config: ", config)
// TODO: allow user to specify project and/or environment in the replacement pattern, e.g. <ENV:PROJ:PARAM>
// TODO: scan files to figure out which ones have a pattern to be replaced rather than replacing against all files
// TODO: support templates - how do we map a template to a file, or just have a file that only contains the template?
ctapi := NewCTApi(config.ApiKey, config.ApiUrl, fmt.Sprintf("argocd-cloudtruth-plugin/%s/%s/go", version, commit), log_level_int >= 4)
ctproject := NewCTProject(ctapi, config.Project, config.Environment, config.Tag)
err = applyTransformations(config.FilePattern, config.ReferencePattern, ctproject)
if err != nil {
log.Fatalf("Failed to apply transformations: %v", err)
}
}
func applyTransformations(filePattern []string, referencePattern string, ctproject *CTProject) error {
first := true
for _, pattern := range filePattern {
log.Info("Processing pattern: ", pattern)
matches, err := filepathx.Glob(pattern)
if err != nil {
log.Errorf("Failed to match pattern: %v", err)
return err
}
for _, path := range matches {
log.Info("Processing file pattern match: ", path)
if !first {
fmt.Print("\n\n---\n\n")
}
result, e := fileReplace(path, referencePattern, ctproject)
if e != nil {
return e
}
fmt.Print(result)
first = false
}
}
return nil
}
func fileReplace(path string, pattern string, ctproject *CTProject) (string, error) {
originalContents, err := os.ReadFile(path)
if err != nil {
log.Errorf("Unable to read file: %v", err)
return "", err
}
replacedContents := string(originalContents)
// Kinda hacky, but no option due to use of golang tring format matches for replacement (<%s>)
// Should probably change the interface on the cli to use regexes (?), or maybe even use full liquid templates for plugin
templatesPresent := false
paramsPresent := false
re := regexp.MustCompile(strings.Replace(pattern, "%s", "(.+?)", -1))
refs := re.FindAllStringSubmatch(replacedContents, -1)
for _, ref := range refs {
if len(ref) > 1 && strings.HasPrefix(ref[1], "templates.") {
templatesPresent = true
} else {
paramsPresent = true
}
}
if paramsPresent {
for k, v := range ctproject.Parameters() {
pattern := fmt.Sprintf(pattern, k)
replacedContents = strings.Replace(replacedContents, pattern, v.value, -1)
}
}
if templatesPresent {
for k, v := range ctproject.Templates() {
pattern := fmt.Sprintf(pattern, "templates."+k)
if strings.Contains(replacedContents, pattern) {
log.Debugf("Replacing template '%s'", pattern)
template_value := v.Get()
replacedContents = strings.Replace(replacedContents, pattern, template_value, -1)
}
}
}
log.Tracef("**** Start Transformation result of '%s':\n%s\n**** End Transformation result of '%s'\n", path, replacedContents, path)
return replacedContents, nil
}