-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from atc0005/i4-initial-prototype
Initial Prototype
- Loading branch information
Showing
11 changed files
with
970 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
*.test | ||
testing/ | ||
*.exe | ||
|
||
# When building on non-Windows platform | ||
elbow |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,113 @@ | ||
# elbow | ||
|
||
Elbow, Elbow grease. | ||
|
||
- [elbow](#elbow) | ||
- [Purpose](#purpose) | ||
- [Gotchas](#gotchas) | ||
- [Setup test environment](#setup-test-environment) | ||
- [Examples](#examples) | ||
- [Overview](#overview) | ||
- [Prune `.war` files from each branch recursively, keep newest 2](#prune-war-files-from-each-branch-recursively-keep-newest-2) | ||
- [Build and run from test area, no options](#build-and-run-from-test-area-no-options) | ||
- [References](#references) | ||
- [Configuration object](#configuration-object) | ||
- [Sorting files](#sorting-files) | ||
- [Path/File Existence](#pathfile-existence) | ||
- [Slice management](#slice-management) | ||
|
||
## Purpose | ||
|
||
Prune content matching specific patterns, either in a single directory or | ||
recursively through a directory tree. The primary goal is to use this | ||
application from a cron job to perform routine pruning of generated files that | ||
would otherwise completely clog a filesystem. | ||
|
||
## Gotchas | ||
|
||
- File extensions are *case-sensitive* | ||
- File name patterns are *case-sensitive* | ||
- File name patterns, much like shell globs, can match more than you might | ||
wish. Test carefully and do not provide the `--remove` flag until you are | ||
ready to actually prune the content. | ||
|
||
## Setup test environment | ||
|
||
1. Launch container, VM or WSL instance | ||
1. `cd /path/to/create/test/files` | ||
1. `touch $(cat /path/to/this/repo/testing/sample_files_list_dev_web_app_server.txt)` | ||
1. `cd /path/to/this/repo` | ||
1. `go build` | ||
|
||
See next section for examples of running the app against the test files. | ||
|
||
## Examples | ||
|
||
### Overview | ||
|
||
The following steps illustrate a rough, overall idea of what this application | ||
is intended to do. The steps illustrate building and running the application | ||
from within an Ubuntu Linux Subsystem for Windows (WSL) instance. The `/t` | ||
volume is present on the Windows host. | ||
|
||
The file extension used in the examples is for a `WAR` file that is generated | ||
on a build system that our team maintains. The idea is that this application | ||
could be run as a cron job to help ensure that only X copies (the most recent) | ||
for each of three branches remain on the build box. | ||
|
||
There are better aproaches to managing those build artifacts, but that is the | ||
problem that this tool seeks to solve in a simple way. | ||
|
||
### Prune `.war` files from each branch recursively, keep newest 2 | ||
|
||
```ShellSession | ||
cd /mnt/t/github/elbow; go build; cp -vf elbow /tmp/; cd /tmp/; ./elbow --path /tmp --extension ".war" --pattern "-master-" --keep 2 --recurse --remove | ||
cd /mnt/t/github/elbow; go build; cp -vf elbow /tmp/; cd /tmp/; ./elbow --path /tmp --extension ".war" --pattern "-masterqa-" --keep 2 --recurse --remove | ||
cd /mnt/t/github/elbow; go build; cp -vf elbow /tmp/; cd /tmp/; ./elbow --path /tmp --extension ".war" --pattern "-masterdev-" --keep 2 --recurse --remove | ||
``` | ||
|
||
```ShellSession | ||
cd /mnt/t/github/elbow; go build; cp -vf elbow /tmp/; cd /tmp/; ./elbow -p /tmp -e ".war" -fp "-master-" -k 2 -r | ||
cd /mnt/t/github/elbow; go build; cp -vf elbow /tmp/; cd /tmp/; ./elbow -p /tmp -e ".war" -fp "-masterqa-" -k 2 -r | ||
cd /mnt/t/github/elbow; go build; cp -vf elbow /tmp/; cd /tmp/; ./elbow -p /tmp -e ".war" -fp "-masterdev-" -k 2 -r | ||
``` | ||
|
||
Leave off `--remove` to display what *would* be removed. | ||
|
||
### Build and run from test area, no options | ||
|
||
This results in Help text being displayed. At a minimum, the path to process | ||
has to be provided for the application to proceed. | ||
|
||
```ShellSession | ||
cd /mnt/t/github/elbow; go build; cp -vf elbow /tmp/; cd /tmp/; ./elbow | ||
``` | ||
|
||
## References | ||
|
||
The following unordered list of sites/examples provided guidance while | ||
developing this application. Depending on when consulted, the original code | ||
written based on that guidance may no longer be present in the active version | ||
of this application. | ||
|
||
### Configuration object | ||
|
||
- <https://github.com/go-sql-driver/mysql/blob/877a9775f06853f611fb2d4e817d92479242d1cd/dsn.go#L67> | ||
- <https://github.com/aws/aws-sdk-go/blob/10878ad0389c5b3069815112ce888b191c8cd325/aws/config.go#L251> | ||
- <https://github.com/aws/aws-sdk-go/blob/master/aws/config.go> | ||
- <https://github.com/aws/aws-sdk-go/blob/10878ad0389c5b3069815112ce888b191c8cd325/awstesting/integration/performance/s3GetObject/config.go#L25> | ||
- <https://github.com/aws/aws-sdk-go/blob/10878ad0389c5b3069815112ce888b191c8cd325/awstesting/integration/performance/s3GetObject/main.go#L25> | ||
|
||
### Sorting files | ||
|
||
- <https://stackoverflow.com/questions/46746862/list-files-in-a-directory-sorted-by-creation-time> | ||
|
||
### Path/File Existence | ||
|
||
- <https://gist.github.com/mattes/d13e273314c3b3ade33f> | ||
|
||
### Slice management | ||
|
||
- <https://yourbasic.org/golang/delete-element-slice/> | ||
- <https://stackoverflow.com/questions/37334119/how-to-delete-an-element-from-a-slice-in-golang> | ||
- <https://github.com/golang/go/wiki/SliceTricks> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/integrii/flaggy" | ||
) | ||
|
||
// Config represents a collection of configuration settings for this | ||
// application. Config is created as early as possible upon application | ||
// startup. | ||
type Config struct { | ||
FilePattern string | ||
FileExtensions []string | ||
StartPath string | ||
RecursiveSearch bool | ||
FilesToKeep int | ||
KeepOldest bool | ||
Remove bool | ||
} | ||
|
||
// NewConfig returns a new Config pointer that can be chained with builder | ||
// methods to set multiple configuration values inline without using pointers. | ||
func NewConfig() *Config { | ||
|
||
// Explicitly initialize with intended defaults | ||
return &Config{ | ||
StartPath: "", | ||
FilePattern: "", | ||
// NOTE: This creates an empty slice (not nil since there is an | ||
// underlying array of zero length) FileExtensions: []string{}, | ||
// | ||
// Leave at default value of nil slice instead by not providing a | ||
// value here | ||
// FileExtensions: []string, | ||
FilesToKeep: 0, | ||
RecursiveSearch: false, | ||
KeepOldest: false, | ||
Remove: false, | ||
} | ||
|
||
} | ||
|
||
// SetupFlags applies settings provided by command-line flags | ||
// TODO: Pull out | ||
func (c *Config) SetupFlags(appName string, appDesc string) *Config { | ||
|
||
flaggy.SetName(appName) | ||
flaggy.SetDescription(appDesc) | ||
|
||
flaggy.DefaultParser.ShowHelpOnUnexpected = true | ||
|
||
// Add flags | ||
flaggy.String(&c.StartPath, "p", "path", "Path to process") | ||
flaggy.String(&c.FilePattern, "fp", "pattern", "Substring pattern to compare filenames against. Wildcards are not supported.") | ||
flaggy.StringSlice(&c.FileExtensions, "e", "extension", "Limit search to specified file extension. Specify as needed to match multiple required extensions.") | ||
flaggy.Int(&c.FilesToKeep, "k", "keep", "Keep specified number of matching files") | ||
flaggy.Bool(&c.RecursiveSearch, "r", "recurse", "Perform recursive search into subdirectories") | ||
flaggy.Bool(&c.KeepOldest, "ko", "keep-old", "Keep oldest files instead of newer") | ||
flaggy.Bool(&c.Remove, "rm", "remove", "Remove matched files") | ||
|
||
// Parse the flags | ||
flaggy.Parse() | ||
|
||
// https://github.com/atc0005/elbow/issues/2#issuecomment-524032239 | ||
// | ||
// For flags, you can easily just check the value after calling | ||
// flaggy.Parse(). If the value is set to something other than the | ||
// default, then the caller supplied it. If it was the default value (set | ||
// by you or the language), then it was not used. | ||
|
||
return c | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module github.com/atc0005/elbow | ||
|
||
go 1.12 | ||
|
||
require ( | ||
github.com/integrii/flaggy v1.2.2 | ||
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad | ||
github.com/stretchr/testify v1.4.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/integrii/flaggy v1.2.2 h1:SzL5kyEaW+Cb3RLxGG1ch9FFDLQPB6QuMdYoNu5JIo0= | ||
github.com/integrii/flaggy v1.2.2/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad h1:j5pg/OewZJyE6i3hIG4v3eQUvUyFdQkC8Nd/mjaEkxE= | ||
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad/go.mod h1:ozniNEFS3j1qCwHKdvraMn1WJOsUxHd7lYfukEIS4cs= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= | ||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | ||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
|
||
"github.com/integrii/flaggy" | ||
) | ||
|
||
func main() { | ||
|
||
// DEBUG | ||
// TODO: Enable this once leveled logging has been implemented. | ||
//defaultConfig := NewConfig() | ||
//fmt.Printf("Default configuration:\t%+v\n", defaultConfig) | ||
|
||
appName := "Elbow" | ||
appDesc := "Prune content matching specific patterns, either in a single directory or recursively through a directory tree." | ||
|
||
config := NewConfig().SetupFlags(appName, appDesc) | ||
|
||
// DEBUG | ||
// TODO: Enable this once leveled logging has been implemented. | ||
//fmt.Printf("Our configuration:\t%+v\n", config) | ||
|
||
// DEBUG | ||
log.Println("Confirm that requested path actually exists") | ||
if !pathExists(config.StartPath) { | ||
flaggy.ShowHelpAndExit(fmt.Sprintf("Error processing requested path: %q", config.StartPath)) | ||
} | ||
|
||
// INFO | ||
log.Println("Processing path:", config.StartPath) | ||
|
||
matches, err := processPath(config) | ||
|
||
// TODO | ||
// How to handle errors from gathering removal candidates? | ||
// Add optional flag to allow ignoring errors, fail immediately otherwise? | ||
if err != nil { | ||
log.Println("error:", err) | ||
} | ||
|
||
// NOTE: If this sort order changes, make sure to update the later logic | ||
// which retains the top or bottom X items (specific flag to preserve X | ||
// number of files while pruning the others) | ||
matches.sortByModTimeAsc() | ||
|
||
// DEBUG | ||
log.Printf("Length of matches slice: %d\n", len(matches)) | ||
|
||
// DEBUG | ||
log.Println("Early exit if no matching files were found.") | ||
if len(matches) <= 0 { | ||
|
||
// INFO | ||
fmt.Printf("No matches found in path %q for files with substring pattern of %q and with extensions %v\n", | ||
config.StartPath, config.FilePattern, config.FileExtensions) | ||
|
||
// TODO: Not finding something is a valid outcome, so "normal" exit | ||
// code? | ||
os.Exit(0) | ||
} | ||
|
||
var filesToPrune FileMatches | ||
|
||
// DEBUG | ||
log.Printf("%d total items in matches", len(matches)) | ||
log.Printf("%d items to keep per config.FilesToKeep", config.FilesToKeep) | ||
|
||
if config.KeepOldest { | ||
// DEBUG | ||
log.Println("Keeping older files") | ||
log.Println("start at specified number to keep, go until end of slice") | ||
filesToPrune = matches[config.FilesToKeep:] | ||
} else { | ||
// DEBUG | ||
log.Println("Keeping newer files") | ||
log.Println("start at beginning, go until specified number to keep") | ||
filesToPrune = matches[:(len(matches) - config.FilesToKeep)] | ||
} | ||
|
||
// DEBUG, INFO? | ||
log.Printf("%d items to prune", len(filesToPrune)) | ||
|
||
log.Println("Prune specified files, do NOT ignore errors") | ||
// TODO: Add support for ignoring errors (though I cannot immediately | ||
// think of a good reason to do so) | ||
removalResults, err := cleanPath(filesToPrune, false, config) | ||
|
||
// Show what we WERE able to successfully remove | ||
// TODO: Refactor this into a function to handle displaying results? | ||
log.Printf("%d files successfully removed\n", len(removalResults.SuccessfulRemovals)) | ||
log.Println("----------------------------") | ||
for _, file := range removalResults.SuccessfulRemovals { | ||
log.Println("*", file.Name()) | ||
} | ||
|
||
log.Printf("%d files failed to remove\n", len(removalResults.FailedRemovals)) | ||
log.Println("----------------------------") | ||
for _, file := range removalResults.FailedRemovals { | ||
log.Println("*", file.Name()) | ||
} | ||
|
||
// Determine if we need to display error, exit with unsuccessful error code | ||
if err != nil { | ||
log.Fatalf("Errors encountered while processing %s: %s", config.StartPath, err) | ||
} | ||
|
||
log.Printf("%s successfully completed.", appName) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package main | ||
|
||
import "testing" | ||
|
||
func TestMain(t *testing.T) { | ||
|
||
defaultConfig := NewConfig() | ||
|
||
var emptySlice = []string{} | ||
var nilSlice []string | ||
|
||
t.Logf("%v\n", emptySlice) | ||
t.Log(len(emptySlice)) | ||
t.Log("emptySlice is nil:", emptySlice == nil) | ||
t.Log("-------------------------") | ||
|
||
t.Logf("%v\n", nilSlice) | ||
t.Log(len(nilSlice)) | ||
t.Log("nilSlice is nil:", nilSlice == nil) | ||
t.Log("-------------------------") | ||
|
||
t.Logf("%v\n", defaultConfig.FileExtensions) | ||
t.Log(len(defaultConfig.FileExtensions)) | ||
t.Log("defaultConfig.FileExtensions is nil:", defaultConfig.FileExtensions == nil) | ||
|
||
} |
Oops, something went wrong.