-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TT-1218] add 3 new tiny tools for the compatibility pipeline (#987)
- Loading branch information
Showing
25 changed files
with
1,141 additions
and
9 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
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
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
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
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,5 @@ | ||
lint: | ||
golangci-lint --color=always run ./... --fix -v | ||
|
||
test_unit: | ||
go test -timeout 5m -json -cover -covermode=count -coverprofile=unit-test-coverage.out ./... 2>&1 | tee /tmp/gotest.log |
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,61 @@ | ||
# AWS ECR Image Fetcher | ||
|
||
This Go script fetches the latest image tags from an AWS ECR repository based on specified criteria such as a grep string and optional semantic version constraints. It sorts and filters the tags and returns a specified number of the latest ones (according to semantic versioning). | ||
|
||
## Usage | ||
|
||
```bash | ||
go run main.go <repository_name> <grep_string> <count> [<semver_constraints>] | ||
``` | ||
|
||
### Example | ||
|
||
```bash | ||
go run main.go 'my-repo' '^v[0-9]+\.[0-9]+\.[0-9]+$' 5 '>=1.0.0, <2.0.0' | ||
``` | ||
|
||
## Command Line Arguments | ||
|
||
- `<repository_name>`: The name of the ECR repository. | ||
- `<grep_string>`: A regex string to filter the tags. | ||
- `<count>`: The number of latest tags to return. | ||
- `[<semver_constraints>]`: Optional semantic version constraints to filter tags further. | ||
|
||
## Output | ||
|
||
- The script prints the specified number of latest image tags from the repository that match the given criteria, formatted as `repository_name:tag`, e.g.`hyperledger/besu:24.3,hyperledger/besu:24.2`. | ||
- If the number of matching tags is less than the requested count, a warning is printed to stderr, and the available tags are returned. | ||
|
||
## Error Handling | ||
|
||
The script will panic and display error messages in the following scenarios: | ||
|
||
- Insufficient command line arguments. | ||
- Empty or invalid `repository_name`, `grep_string`, or `count`. | ||
- Invalid regex for `grep_string`. | ||
- Invalid integer value for `count`. | ||
- Invalid semantic version constraints. | ||
- Errors in fetching or parsing the image details from AWS ECR. | ||
|
||
## Detailed Steps | ||
|
||
1. **Argument Parsing and Validation**: | ||
- The script checks that at least 4 command line arguments are provided. | ||
- Validates the format and values of `repository_name`, `grep_string`, and `count`. | ||
- Optionally validates semantic version constraints if provided. | ||
2. **Fetch Image Details**: | ||
- Constructs and executes an AWS CLI command to describe images in the specified ECR repository. | ||
- Parses the JSON output to extract image tags. | ||
3. **Filter and Sort Tags**: | ||
- Filters tags based on the `grep_string` regex. | ||
- Optionally filters tags based on semantic version constraints. | ||
- Sorts the tags in descending order. | ||
4. **Return Latest Tags**: | ||
- Constructs the output of the latest tags formatted as `repository_name:tag`. | ||
- Prints the result to stdout. | ||
|
||
## Notes | ||
|
||
- Ensure AWS CLI is configured and you have the necessary permissions to access the ECR repository. | ||
- The environment variable `AWS_REGION` must be set to the appropriate AWS region. | ||
- Semantic version constraints are optional and can be used to further filter the tags. |
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,16 @@ | ||
module github.com/smartcontractkit/chainlink-testing-framework/tools/latestecrimages | ||
|
||
go 1.21.7 | ||
|
||
require ( | ||
github.com/Masterminds/semver/v3 v3.2.1 | ||
github.com/itchyny/gojq v0.12.16 | ||
github.com/stretchr/testify v1.9.0 | ||
) | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/itchyny/timefmt-go v0.1.6 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // 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,16 @@ | ||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= | ||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g= | ||
github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM= | ||
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= | ||
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= | ||
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
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,176 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"regexp" | ||
"sort" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/Masterminds/semver/v3" | ||
|
||
"github.com/itchyny/gojq" | ||
) | ||
|
||
func fetchImageDetails(repositoryName string) ([]byte, error) { | ||
// #nosec G204 | ||
cmd := exec.Command("aws", "ecr", "describe-images", "--repository-name", repositoryName, "--region", os.Getenv("AWS_REGION"), "--output", "json", "--query", "imageDetails[?imageTags!=`null` && imageTags!=`[]`]") | ||
return cmd.Output() | ||
} | ||
|
||
func parseImageTags(output []byte, grepString string, constraints *semver.Constraints) ([]string, error) { | ||
var imageDetails []interface{} | ||
if err := json.Unmarshal(output, &imageDetails); err != nil { | ||
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) | ||
} | ||
|
||
query, err := gojq.Parse(".[] | .imageTags[0]") | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse gojq query: %w", err) | ||
} | ||
|
||
var tags []string | ||
iter := query.Run(imageDetails) | ||
for { | ||
tag, ok := iter.Next() | ||
if !ok { | ||
break | ||
} | ||
if tagStr, ok := tag.(string); ok { | ||
tags = append(tags, tagStr) | ||
} else if err, ok := tag.(error); ok { | ||
return nil, fmt.Errorf("failed to run gojq query: %w", err) | ||
} | ||
} | ||
|
||
sort.Sort(sort.Reverse(sort.StringSlice(tags))) | ||
|
||
re, err := regexp.Compile(grepString) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to compile regex: %w", err) | ||
} | ||
|
||
var filteredTags []string | ||
for _, tag := range tags { | ||
if re.MatchString(tag) { | ||
ignore := false | ||
if constraints != nil { | ||
version, err := semver.NewVersion(tag) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse version: %w", err) | ||
} | ||
if !constraints.Check(version) { | ||
ignore = true | ||
} | ||
} | ||
|
||
if !ignore { | ||
filteredTags = append(filteredTags, tag) | ||
} | ||
} | ||
} | ||
|
||
return filteredTags, nil | ||
} | ||
|
||
func getLatestImages(fetchFunc func(string) ([]byte, error), repositoryName, grepString string, count int, constraints *semver.Constraints) (string, error) { | ||
output, err := fetchFunc(repositoryName) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to describe images: %w", err) | ||
} | ||
|
||
tags, err := parseImageTags(output, grepString, constraints) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to parse image tags: %w", err) | ||
} | ||
|
||
constraintsText := "(none)" | ||
if constraints != nil { | ||
constraintsText = constraints.String() | ||
} | ||
|
||
if len(tags) == 0 { | ||
panic(fmt.Errorf("error: no tags found for repository '%s' given the version constraints '%s'", repositoryName, constraintsText)) | ||
} | ||
|
||
if count > len(tags) { | ||
_, _ = fmt.Fprintf(os.Stderr, "Warning: failed to find %d tags given the version constraints '%s'. Found only %d tag(s)\n", count, constraintsText, len(tags)) | ||
count = len(tags) | ||
} | ||
|
||
var imagesArr []string | ||
for i := 0; i < count; i++ { | ||
imagesArr = append(imagesArr, fmt.Sprintf("%s:%s", repositoryName, tags[i])) | ||
} | ||
|
||
images := strings.Join(imagesArr, ",") | ||
return images, nil | ||
} | ||
|
||
func main() { | ||
if err := validateInputs(); err != nil { | ||
panic(err) | ||
} | ||
|
||
repositoryName := os.Args[1] | ||
grepString := os.Args[2] | ||
count, err := strconv.Atoi(os.Args[3]) | ||
if err != nil { | ||
panic(fmt.Errorf("error: count must be an integer, but %s is not an integer", os.Args[3])) | ||
} | ||
|
||
var constraints *semver.Constraints | ||
if len(os.Args) == 5 { | ||
constraints, err = semver.NewConstraint(os.Args[4]) | ||
if err != nil { | ||
panic(fmt.Errorf("error: invalid semver constraint: %v", err)) | ||
} | ||
} | ||
|
||
images, err := getLatestImages(fetchImageDetails, repositoryName, grepString, count, constraints) | ||
if err != nil { | ||
panic(fmt.Errorf("error getting latest images: %v", err)) | ||
} | ||
|
||
fmt.Println(images) | ||
} | ||
|
||
func validateInputs() error { | ||
if len(os.Args) < 4 { | ||
return errors.New("usage: <repository_name> <grep_string> <count> [<ignored_tags>]") | ||
} | ||
|
||
if os.Args[1] == "" { | ||
return errors.New("error: repository_name cannot be empty") | ||
} | ||
|
||
if os.Args[2] == "" { | ||
return errors.New("error: grep_string cannot be empty") | ||
} | ||
|
||
if _, err := regexp.Compile(os.Args[2]); err != nil { | ||
return errors.New("error: grep_string is not a valid regex") | ||
} | ||
|
||
if _, err := strconv.Atoi(os.Args[3]); err != nil { | ||
return fmt.Errorf("error: count must be an integer, but %s is not an integer", os.Args[3]) | ||
} | ||
|
||
if len(os.Args) == 5 && os.Args[4] != "" { | ||
for _, semVerConstraint := range strings.Split(os.Args[4], ",") { | ||
if semVerConstraint == "" { | ||
return errors.New("error: semver constraint cannot be empty") | ||
} | ||
_, err := semver.NewConstraint(semVerConstraint) | ||
if err != nil { | ||
return fmt.Errorf("error: invalid semver constraint: %v", err) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.