fuzzyworld

template / ci-testing / files / profile.go

Branch ci-testing · default branch · all branches

profile.go

Blame: the commit that last changed each line · file · history

d026838a 2022-09-22 Pavel Safronov1package main
2
3import (
ae89a42a 2022-11-02 Pavel Safronov4 "bytes"
d026838a 2022-09-22 Pavel Safronov5 "fmt"
6 "os"
05f9e8ef 2022-10-06 Pavel Safronov7 "os/user"
319b8b3f 2022-09-22 Pavel Safronov8 "path/filepath"
2bc4bd60 2022-09-23 Pavel Safronov9 "runtime"
230da3be 2022-09-22 Pavel Safronov10 "strings"
11 "time"
763c260d 2022-10-06 Pavel Safronov12
13 "golang.org/x/exp/slices"
14)
15
16type ProfileType string
17
18const (
19 FileType ProfileType = "file"
20 FolderType ProfileType = "folder"
21 ScriptType ProfileType = "script"
22)
23
24type OSType string
25
26const (
27 Windows OSType = "Windows"
28 Mac OSType = "Mac"
29 All OSType = "All"
d026838a 2022-09-22 Pavel Safronov30)
31
1a02ef4e 2022-10-21 Pavel Safronov32type ParameterizedText string
33
b0586433 2022-10-21 Pavel Safronov34type File struct {
e86c26ae 2022-11-02 Pavel Safronov35 Template ParameterizedText `yaml:"template,omitempty"`
b0586433 2022-10-21 Pavel Safronov36}
763c260d 2022-10-06 Pavel Safronov37
5e7795dc 2022-10-21 Pavel Safronov38type FileInFolder struct {
e86c26ae 2022-11-02 Pavel Safronov39 File `yaml:"file,omitempty"`
5e7795dc 2022-10-21 Pavel Safronov40 Name ParameterizedText
41}
42
b0586433 2022-10-21 Pavel Safronov43type Profile struct {
687d5626 2022-10-28 Pavel Safronov44 Name string
e86c26ae 2022-11-02 Pavel Safronov45 Description string `yaml:"description,omitempty"`
46 IsDefault bool
10676da3 2022-11-06 Pavel Safronov47 IsDisabled bool
687d5626 2022-10-28 Pavel Safronov48 Type ProfileType
e86c26ae 2022-11-02 Pavel Safronov49 Path string `yaml:"path,omitempty"`
50 OS OSType `yaml:"os,omitempty"`
687d5626 2022-10-28 Pavel Safronov51 NamePatterns []ParameterizedText
e86c26ae 2022-11-02 Pavel Safronov52 Files []FileInFolder `yaml:"files,omitempty"`
53 RunPostscriptOnFailure bool `yaml:"runpostscriptonfailure,omitempty"`
54 PostScripts []ParameterizedText `yaml:"postscripts,omitempty"`
55 File `yaml:"file,omitempty"`
763c260d 2022-10-06 Pavel Safronov56}
57
58var forbiddenProfileNames = []string{
59 "list",
60 "get",
61}
62
687d5626 2022-10-28 Pavel Safronov63func processProfile(profile Profile, projectName string, extras map[string]string, usr user.User) []error {
10676da3 2022-11-06 Pavel Safronov64 if profile.IsDisabled {
9573ca02 2022-11-16 Pavel Safronov65 return []error{fmt.Errorf("Profile is disabled, doing nothing")}
10676da3 2022-11-06 Pavel Safronov66 }
67
da15cf49 2022-09-23 Pavel Safronov68 // decide if the profile should run on this OS
2bc4bd60 2022-09-23 Pavel Safronov69 profileOs := profile.OS
70 isWindows := runtime.GOOS == "windows"
b65fb5e9 2022-09-23 Pavel Safronov71 shouldRun := profileOs == "" ||
72 profileOs == "All" ||
2bc4bd60 2022-09-23 Pavel Safronov73 (profileOs == "Windows" && isWindows) ||
74 (profileOs == "Mac" && !isWindows)
75 if !shouldRun {
9573ca02 2022-11-16 Pavel Safronov76 return []error{
77 fmt.Errorf("This profile is not supported on this OS, doing nothing. Profile OS: %s, system OS: %s. Supported OS values: Windows, Mac, All", profileOs, runtime.GOOS),
78 }
2bc4bd60 2022-09-23 Pavel Safronov79 }
80
c3fc7a22 2022-10-30 Pavel Safronov81 cwd, err := monkey.OSGetwd()
319b8b3f 2022-09-22 Pavel Safronov82 if err != nil {
687d5626 2022-10-28 Pavel Safronov83 return []error{fmt.Errorf("unable to get current working directory: %v", err)}
da15cf49 2022-09-23 Pavel Safronov84 }
85 if profile.Path != "" {
86 // change cwd to the profile's path, if one is specified
05f9e8ef 2022-10-06 Pavel Safronov87 path := getFullPath(profile.Path, usr)
c3fc7a22 2022-10-30 Pavel Safronov88 err = monkey.OSChdir(path)
da15cf49 2022-09-23 Pavel Safronov89 if err != nil {
687d5626 2022-10-28 Pavel Safronov90 return []error{fmt.Errorf("unable to change directory (1) to %v: %v", path, err)}
da15cf49 2022-09-23 Pavel Safronov91 }
92 cwd = path
319b8b3f 2022-09-22 Pavel Safronov93 }
94
95 // determine if we're dealing with files or directories
2c2e1241 2022-10-05 Pavel Safronov96 profileType := profile.Type
d026838a 2022-09-22 Pavel Safronov97
687d5626 2022-10-28 Pavel Safronov98 errors := []error{}
8c15a714 2022-11-04 Pavel Safronov99 fullPath := ""
100 now := time.Now()
101 prePatternData := createPrePatternData(now, projectName, extras)
102 if profileType == FileType || profileType == FolderType {
103 // get all entities in current directory
104 files, err := monkey.OSReadDir(cwd)
105 if err != nil {
106 return []error{err}
107 }
319b8b3f 2022-09-22 Pavel Safronov108
8c15a714 2022-11-04 Pavel Safronov109 // filter entities to just files or directories
110 var entities []string
111 for _, file := range files {
112 isDir := file.IsDir()
113 if isDir == (profileType == FolderType) {
114 entities = append(entities, file.Name())
115 }
687d5626 2022-10-28 Pavel Safronov116 }
2bc4bd60 2022-09-23 Pavel Safronov117
8c15a714 2022-11-04 Pavel Safronov118 // generate a new name based on time, profile, and existing entities
10676da3 2022-11-06 Pavel Safronov119 validator := func(newName string) bool { return !slices.Contains(entities, newName) }
120 newName, err := createNewNameForPatterns(now, prePatternData, projectName, profile.NamePatterns, extras, validator)
8c15a714 2022-11-04 Pavel Safronov121 swallowPrePostScriptErrors := profile.RunPostscriptOnFailure && profile.PostScripts != nil && len(profile.PostScripts) > 0
122
123 // check newName is created
124 if newName == "" {
e86c26ae 2022-11-02 Pavel Safronov125 if swallowPrePostScriptErrors {
8c15a714 2022-11-04 Pavel Safronov126 fmt.Println("No new name was created, but will attempt to run post scripts")
e86c26ae 2022-11-02 Pavel Safronov127 } else {
8c15a714 2022-11-04 Pavel Safronov128 errors = append(errors, fmt.Errorf("couldn't find a good new name"))
687d5626 2022-10-28 Pavel Safronov129 return errors
fe139e41 2022-09-25 Pavel Safronov130 }
5e7795dc 2022-10-21 Pavel Safronov131 }
8c15a714 2022-11-04 Pavel Safronov132
133 if err == nil && newName != "" {
134 fullPath, err = createEntity(now, profile, cwd, newName, projectName, extras, prePatternData)
135 if err != nil {
136 if swallowPrePostScriptErrors {
137 fmt.Printf("Error creating entity, but will attempt to run post scripts: %v", err)
138 } else {
139 errors = append(errors, fmt.Errorf("unable to create entity: %v", err))
140 return errors
141 }
142 }
143 }
319b8b3f 2022-09-22 Pavel Safronov144 }
145
146 // execute PostScript, if present
9573ca02 2022-11-16 Pavel Safronov147 if profile.PostScripts != nil && len(profile.PostScripts) > 0 {
148 postPatternData := createPostPatternData(fullPath, prePatternData)
149 postScripts, err := createPostScriptArrays(profile, postPatternData)
ae89a42a 2022-11-02 Pavel Safronov150 if err != nil {
9573ca02 2022-11-16 Pavel Safronov151 errors = append(errors, fmt.Errorf("unable to create post scripts: %v", err))
152 return errors
153 }
154 for _, postScript := range postScripts {
155
156 shell, args := shellCommandAndArgs(postScript)
157 var out bytes.Buffer
158 fmt.Printf("Executing post script: %v %v\n", shell, strings.Join(args, " "))
159 cmd := monkey.ExecCommand(shell, args...)
160 cmd.Stdout = &out
161 err := cmd.Run()
162 output := out.String()
163 if output != "" {
164 fmt.Printf("Post script output: %v\n", output)
165 } else {
166 fmt.Printf("Post script had no output\n")
167 }
168 if err != nil {
169 // post scripts can fail (TODO: investigate why), so for now we'll just log the error
2bc4bd60 2022-09-23 Pavel Safronov170
9573ca02 2022-11-16 Pavel Safronov171 fmt.Printf("Error executing post script '%v': %v\n",
172 fmt.Sprintf("%v %v", shell, strings.Trim(fmt.Sprint(args), "[]")), err)
173 }
319b8b3f 2022-09-22 Pavel Safronov174 }
175 }
176
177 return nil
178}
763c260d 2022-10-06 Pavel Safronov179
52e9ad7c 2022-11-08 Pavel Safronov180func shellCommandAndArgs(command string) (shell string, args []string) {
181 if runtime.GOOS == "windows" {
182 shell = "cmd"
183 args = []string{"/c", command}
184 return
185 } else {
186 shell = "sh"
187 args = []string{"-c", command}
188 return
189 }
190}
191
8c15a714 2022-11-04 Pavel Safronov192func createPrePatternData(now time.Time, projectName string, extras map[string]string) PrePatternData {
193 prePatternData := PrePatternData{
194 Year: now.Format("2006"),
195 Month: now.Format("01"),
196 Day: now.Format("02"),
197 Hour: now.Format("15"),
198 Minute: now.Format("04"),
199 Second: now.Format("05"),
200 ProjectName: projectName,
201 Extras: extras,
202 }
203 return prePatternData
204}
205
687d5626 2022-10-28 Pavel Safronov206func createEntity(
207 now time.Time,
208 profile Profile,
209 cwd string,
210 newName string,
211 projectName string,
212 extras map[string]string,
213 prePatternData PrePatternData) (string, error) {
214
c3fc7a22 2022-10-30 Pavel Safronov215 profileType := profile.Type
687d5626 2022-10-28 Pavel Safronov216 fullPath := filepath.Join(cwd, newName)
217 if profileType == FolderType {
e86c26ae 2022-11-02 Pavel Safronov218 fmt.Printf("Creating folder: %v\n", fullPath)
c3fc7a22 2022-10-30 Pavel Safronov219 err := monkey.OSMkdir(fullPath, os.ModePerm)
687d5626 2022-10-28 Pavel Safronov220 if err != nil {
221 return "", fmt.Errorf("error creating directory: %v", err)
222 }
c3fc7a22 2022-10-30 Pavel Safronov223 err = monkey.OSChdir(fullPath)
687d5626 2022-10-28 Pavel Safronov224 if err != nil {
225 return "", fmt.Errorf("unable to change directory (2) to %v: %v", fullPath, err)
226 }
227
228 // create files in the new folder
229 if len(profile.Files) > 0 {
e86c26ae 2022-11-02 Pavel Safronov230 fmt.Printf("Creating %v files in folder: %v\n", len(profile.Files), fullPath)
10676da3 2022-11-06 Pavel Safronov231 existingFiles := []string{}
687d5626 2022-10-28 Pavel Safronov232 for index, file := range profile.Files {
8c15a714 2022-11-04 Pavel Safronov233 filePrePatternData := createPrePatternData(now, projectName, extras)
10676da3 2022-11-06 Pavel Safronov234 validator := func(newName string) bool { return !slices.Contains(existingFiles, newName) }
235 fileName, err := createNewNameForPatterns(now, filePrePatternData, projectName, []ParameterizedText{file.Name}, extras, validator)
687d5626 2022-10-28 Pavel Safronov236 if err != nil {
237 return "", fmt.Errorf("error creating file name for file index %v : %v", index, err)
238 }
10676da3 2022-11-06 Pavel Safronov239 existingFiles = append(existingFiles, fileName)
687d5626 2022-10-28 Pavel Safronov240 subFilePath := filepath.Join(fullPath, fileName)
5e424f1b 2022-11-12 Pavel Safronov241 subDirPath := filepath.Dir(subFilePath)
242
243 if _, err := os.Stat(subDirPath); os.IsNotExist(err) {
244 fmt.Printf("Creating subdirectory: %v\n", subDirPath)
245 err = monkey.OSMkdir(subDirPath, os.ModePerm)
246 if err != nil {
247 return "", fmt.Errorf("error creating sub directory for file index %v : %v", index, err)
248 }
249 }
e86c26ae 2022-11-02 Pavel Safronov250 fmt.Printf("Creating sub file: %v\n", subFilePath)
687d5626 2022-10-28 Pavel Safronov251 err = createFile(filePrePatternData, file.File, subFilePath)
252 if err != nil {
886f7fa2 2022-11-02 Pavel Safronov253 return "", fmt.Errorf("error creating file %v: %v", subFilePath, err)
687d5626 2022-10-28 Pavel Safronov254 }
255 }
256 }
257 } else if profileType == FileType {
e86c26ae 2022-11-02 Pavel Safronov258 fmt.Printf("Creating file: %v\n", fullPath)
687d5626 2022-10-28 Pavel Safronov259 err := createFile(prePatternData, profile.File, fullPath)
5e7795dc 2022-10-21 Pavel Safronov260 if err != nil {
687d5626 2022-10-28 Pavel Safronov261 return "", err
5e7795dc 2022-10-21 Pavel Safronov262 }
263 } else {
e86c26ae 2022-11-02 Pavel Safronov264 fmt.Printf("Profile type is script, doing nothing during main phase\n")
5e7795dc 2022-10-21 Pavel Safronov265 }
687d5626 2022-10-28 Pavel Safronov266 return fullPath, nil
5e7795dc 2022-10-21 Pavel Safronov267}
268
b0586433 2022-10-21 Pavel Safronov269func validateProfile(profile *Profile) error {
763c260d 2022-10-06 Pavel Safronov270 if profile.Name == "" {
271 return fmt.Errorf("Profile name is required")
272 }
273 if slices.Contains(forbiddenProfileNames, profile.Name) {
274 return fmt.Errorf("forbidden profile name '%s'", profile.Name)
275 }
10676da3 2022-11-06 Pavel Safronov276 if profile.NamePatterns == nil || len(profile.NamePatterns) == 0 {
277 return fmt.Errorf("namePatterns is required")
278 }
279 for index, namePattern := range profile.NamePatterns {
280 if namePattern == "" {
281 return fmt.Errorf("name pattern is required for pattern index %v of profile %v", index, profile.Name)
282 }
283 }
763c260d 2022-10-06 Pavel Safronov284 if profile.Type == "" || (profile.Type != FileType && profile.Type != FolderType && profile.Type != ScriptType) {
285 return fmt.Errorf("invalid profile type %s for profile %s", profile.Type, profile.Name)
286 }
b0586433 2022-10-21 Pavel Safronov287 if profile.Type != FileType && profile.Template != "" {
05f9e8ef 2022-10-06 Pavel Safronov288 return fmt.Errorf("file template is not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
289 }
8c15a714 2022-11-04 Pavel Safronov290 if profile.Type == FileType && profile.Path == "" {
291 return fmt.Errorf("path is not specified for profile %s, which has Type=%s", profile.Name, profile.Type)
292 }
293 if profile.Type == FolderType && profile.Path == "" {
294 return fmt.Errorf("path is required for profile %s, which has Type=%s", profile.Name, profile.Type)
295 }
763c260d 2022-10-06 Pavel Safronov296 if profile.Type == ScriptType && profile.NamePatterns != nil && len(profile.NamePatterns) > 1 {
297 return fmt.Errorf("multiple name patterns are not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
298 }
b0586433 2022-10-21 Pavel Safronov299 if profile.Files == nil {
5e7795dc 2022-10-21 Pavel Safronov300 profile.Files = []FileInFolder{}
b0586433 2022-10-21 Pavel Safronov301 }
763c260d 2022-10-06 Pavel Safronov302 return nil
303}