template / ci-testing / files / profile.go
Branch ci-testing · default branch · all branches
profile.go
d026838a 2022-09-22 Pavel Safronov | 1 | package main |
| 2 | ||
| 3 | import ( | |
ae89a42a 2022-11-02 Pavel Safronov | 4 | "bytes" |
d026838a 2022-09-22 Pavel Safronov | 5 | "fmt" |
| 6 | "os" | |
05f9e8ef 2022-10-06 Pavel Safronov | 7 | "os/user" |
319b8b3f 2022-09-22 Pavel Safronov | 8 | "path/filepath" |
2bc4bd60 2022-09-23 Pavel Safronov | 9 | "runtime" |
230da3be 2022-09-22 Pavel Safronov | 10 | "strings" |
| 11 | "time" | |
763c260d 2022-10-06 Pavel Safronov | 12 | |
| 13 | "golang.org/x/exp/slices" | |
| 14 | ) | |
| 15 | ||
| 16 | type ProfileType string | |
| 17 | ||
| 18 | const ( | |
| 19 | FileType ProfileType = "file" | |
| 20 | FolderType ProfileType = "folder" | |
| 21 | ScriptType ProfileType = "script" | |
| 22 | ) | |
| 23 | ||
| 24 | type OSType string | |
| 25 | ||
| 26 | const ( | |
| 27 | Windows OSType = "Windows" | |
| 28 | Mac OSType = "Mac" | |
| 29 | All OSType = "All" | |
d026838a 2022-09-22 Pavel Safronov | 30 | ) |
| 31 | ||
1a02ef4e 2022-10-21 Pavel Safronov | 32 | type ParameterizedText string |
| 33 | ||
b0586433 2022-10-21 Pavel Safronov | 34 | type File struct { |
e86c26ae 2022-11-02 Pavel Safronov | 35 | Template ParameterizedText `yaml:"template,omitempty"` |
b0586433 2022-10-21 Pavel Safronov | 36 | } |
763c260d 2022-10-06 Pavel Safronov | 37 | |
5e7795dc 2022-10-21 Pavel Safronov | 38 | type FileInFolder struct { |
e86c26ae 2022-11-02 Pavel Safronov | 39 | File `yaml:"file,omitempty"` |
5e7795dc 2022-10-21 Pavel Safronov | 40 | Name ParameterizedText |
| 41 | } | |
| 42 | ||
b0586433 2022-10-21 Pavel Safronov | 43 | type Profile struct { |
687d5626 2022-10-28 Pavel Safronov | 44 | Name string |
e86c26ae 2022-11-02 Pavel Safronov | 45 | Description string `yaml:"description,omitempty"` |
| 46 | IsDefault bool | |
10676da3 2022-11-06 Pavel Safronov | 47 | IsDisabled bool |
687d5626 2022-10-28 Pavel Safronov | 48 | Type ProfileType |
e86c26ae 2022-11-02 Pavel Safronov | 49 | Path string `yaml:"path,omitempty"` |
| 50 | OS OSType `yaml:"os,omitempty"` | |
687d5626 2022-10-28 Pavel Safronov | 51 | NamePatterns []ParameterizedText |
e86c26ae 2022-11-02 Pavel Safronov | 52 | 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 Safronov | 56 | } |
| 57 | ||
| 58 | var forbiddenProfileNames = []string{ | |
| 59 | "list", | |
| 60 | "get", | |
| 61 | } | |
| 62 | ||
687d5626 2022-10-28 Pavel Safronov | 63 | func processProfile(profile Profile, projectName string, extras map[string]string, usr user.User) []error { |
10676da3 2022-11-06 Pavel Safronov | 64 | if profile.IsDisabled { |
9573ca02 2022-11-16 Pavel Safronov | 65 | return []error{fmt.Errorf("Profile is disabled, doing nothing")} |
10676da3 2022-11-06 Pavel Safronov | 66 | } |
| 67 | ||
da15cf49 2022-09-23 Pavel Safronov | 68 | // decide if the profile should run on this OS |
2bc4bd60 2022-09-23 Pavel Safronov | 69 | profileOs := profile.OS |
| 70 | isWindows := runtime.GOOS == "windows" | |
b65fb5e9 2022-09-23 Pavel Safronov | 71 | shouldRun := profileOs == "" || |
| 72 | profileOs == "All" || | |
2bc4bd60 2022-09-23 Pavel Safronov | 73 | (profileOs == "Windows" && isWindows) || |
| 74 | (profileOs == "Mac" && !isWindows) | |
| 75 | if !shouldRun { | |
9573ca02 2022-11-16 Pavel Safronov | 76 | 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 Safronov | 79 | } |
| 80 | ||
c3fc7a22 2022-10-30 Pavel Safronov | 81 | cwd, err := monkey.OSGetwd() |
319b8b3f 2022-09-22 Pavel Safronov | 82 | if err != nil { |
687d5626 2022-10-28 Pavel Safronov | 83 | return []error{fmt.Errorf("unable to get current working directory: %v", err)} |
da15cf49 2022-09-23 Pavel Safronov | 84 | } |
| 85 | if profile.Path != "" { | |
| 86 | // change cwd to the profile's path, if one is specified | |
05f9e8ef 2022-10-06 Pavel Safronov | 87 | path := getFullPath(profile.Path, usr) |
c3fc7a22 2022-10-30 Pavel Safronov | 88 | err = monkey.OSChdir(path) |
da15cf49 2022-09-23 Pavel Safronov | 89 | if err != nil { |
687d5626 2022-10-28 Pavel Safronov | 90 | return []error{fmt.Errorf("unable to change directory (1) to %v: %v", path, err)} |
da15cf49 2022-09-23 Pavel Safronov | 91 | } |
| 92 | cwd = path | |
319b8b3f 2022-09-22 Pavel Safronov | 93 | } |
| 94 | ||
| 95 | // determine if we're dealing with files or directories | |
2c2e1241 2022-10-05 Pavel Safronov | 96 | profileType := profile.Type |
d026838a 2022-09-22 Pavel Safronov | 97 | |
687d5626 2022-10-28 Pavel Safronov | 98 | errors := []error{} |
8c15a714 2022-11-04 Pavel Safronov | 99 | 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 Safronov | 108 | |
8c15a714 2022-11-04 Pavel Safronov | 109 | // 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 Safronov | 116 | } |
2bc4bd60 2022-09-23 Pavel Safronov | 117 | |
8c15a714 2022-11-04 Pavel Safronov | 118 | // generate a new name based on time, profile, and existing entities |
10676da3 2022-11-06 Pavel Safronov | 119 | 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 Safronov | 121 | swallowPrePostScriptErrors := profile.RunPostscriptOnFailure && profile.PostScripts != nil && len(profile.PostScripts) > 0 |
| 122 | ||
| 123 | // check newName is created | |
| 124 | if newName == "" { | |
e86c26ae 2022-11-02 Pavel Safronov | 125 | if swallowPrePostScriptErrors { |
8c15a714 2022-11-04 Pavel Safronov | 126 | fmt.Println("No new name was created, but will attempt to run post scripts") |
e86c26ae 2022-11-02 Pavel Safronov | 127 | } else { |
8c15a714 2022-11-04 Pavel Safronov | 128 | errors = append(errors, fmt.Errorf("couldn't find a good new name")) |
687d5626 2022-10-28 Pavel Safronov | 129 | return errors |
fe139e41 2022-09-25 Pavel Safronov | 130 | } |
5e7795dc 2022-10-21 Pavel Safronov | 131 | } |
8c15a714 2022-11-04 Pavel Safronov | 132 | |
| 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 Safronov | 144 | } |
| 145 | ||
| 146 | // execute PostScript, if present | |
9573ca02 2022-11-16 Pavel Safronov | 147 | if profile.PostScripts != nil && len(profile.PostScripts) > 0 { |
| 148 | postPatternData := createPostPatternData(fullPath, prePatternData) | |
| 149 | postScripts, err := createPostScriptArrays(profile, postPatternData) | |
ae89a42a 2022-11-02 Pavel Safronov | 150 | if err != nil { |
9573ca02 2022-11-16 Pavel Safronov | 151 | 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 Safronov | 170 | |
9573ca02 2022-11-16 Pavel Safronov | 171 | 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 Safronov | 174 | } |
| 175 | } | |
| 176 | ||
| 177 | return nil | |
| 178 | } | |
763c260d 2022-10-06 Pavel Safronov | 179 | |
52e9ad7c 2022-11-08 Pavel Safronov | 180 | func 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 Safronov | 192 | func 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 Safronov | 206 | func 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 Safronov | 215 | profileType := profile.Type |
687d5626 2022-10-28 Pavel Safronov | 216 | fullPath := filepath.Join(cwd, newName) |
| 217 | if profileType == FolderType { | |
e86c26ae 2022-11-02 Pavel Safronov | 218 | fmt.Printf("Creating folder: %v\n", fullPath) |
c3fc7a22 2022-10-30 Pavel Safronov | 219 | err := monkey.OSMkdir(fullPath, os.ModePerm) |
687d5626 2022-10-28 Pavel Safronov | 220 | if err != nil { |
| 221 | return "", fmt.Errorf("error creating directory: %v", err) | |
| 222 | } | |
c3fc7a22 2022-10-30 Pavel Safronov | 223 | err = monkey.OSChdir(fullPath) |
687d5626 2022-10-28 Pavel Safronov | 224 | 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 Safronov | 230 | fmt.Printf("Creating %v files in folder: %v\n", len(profile.Files), fullPath) |
10676da3 2022-11-06 Pavel Safronov | 231 | existingFiles := []string{} |
687d5626 2022-10-28 Pavel Safronov | 232 | for index, file := range profile.Files { |
8c15a714 2022-11-04 Pavel Safronov | 233 | filePrePatternData := createPrePatternData(now, projectName, extras) |
10676da3 2022-11-06 Pavel Safronov | 234 | 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 Safronov | 236 | if err != nil { |
| 237 | return "", fmt.Errorf("error creating file name for file index %v : %v", index, err) | |
| 238 | } | |
10676da3 2022-11-06 Pavel Safronov | 239 | existingFiles = append(existingFiles, fileName) |
687d5626 2022-10-28 Pavel Safronov | 240 | subFilePath := filepath.Join(fullPath, fileName) |
5e424f1b 2022-11-12 Pavel Safronov | 241 | 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 Safronov | 250 | fmt.Printf("Creating sub file: %v\n", subFilePath) |
687d5626 2022-10-28 Pavel Safronov | 251 | err = createFile(filePrePatternData, file.File, subFilePath) |
| 252 | if err != nil { | |
886f7fa2 2022-11-02 Pavel Safronov | 253 | return "", fmt.Errorf("error creating file %v: %v", subFilePath, err) |
687d5626 2022-10-28 Pavel Safronov | 254 | } |
| 255 | } | |
| 256 | } | |
| 257 | } else if profileType == FileType { | |
e86c26ae 2022-11-02 Pavel Safronov | 258 | fmt.Printf("Creating file: %v\n", fullPath) |
687d5626 2022-10-28 Pavel Safronov | 259 | err := createFile(prePatternData, profile.File, fullPath) |
5e7795dc 2022-10-21 Pavel Safronov | 260 | if err != nil { |
687d5626 2022-10-28 Pavel Safronov | 261 | return "", err |
5e7795dc 2022-10-21 Pavel Safronov | 262 | } |
| 263 | } else { | |
e86c26ae 2022-11-02 Pavel Safronov | 264 | fmt.Printf("Profile type is script, doing nothing during main phase\n") |
5e7795dc 2022-10-21 Pavel Safronov | 265 | } |
687d5626 2022-10-28 Pavel Safronov | 266 | return fullPath, nil |
5e7795dc 2022-10-21 Pavel Safronov | 267 | } |
| 268 | ||
b0586433 2022-10-21 Pavel Safronov | 269 | func validateProfile(profile *Profile) error { |
763c260d 2022-10-06 Pavel Safronov | 270 | 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 Safronov | 276 | 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 Safronov | 284 | 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 Safronov | 287 | if profile.Type != FileType && profile.Template != "" { |
05f9e8ef 2022-10-06 Pavel Safronov | 288 | 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 Safronov | 290 | 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 Safronov | 296 | 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 Safronov | 299 | if profile.Files == nil { |
5e7795dc 2022-10-21 Pavel Safronov | 300 | profile.Files = []FileInFolder{} |
b0586433 2022-10-21 Pavel Safronov | 301 | } |
763c260d 2022-10-06 Pavel Safronov | 302 | return nil |
| 303 | } |