├── LICENSE ├── .gitignore ├── docs ├── install.md ├── index.md ├── contributing.md └── usage.md ├── main.go ├── Makefile ├── .github ├── dependabot.yml ├── workflows │ ├── docs.yml │ └── release.yml └── FUNDING.yml ├── cmd ├── get.go ├── create.go ├── delete.go ├── describe.go ├── tag.go ├── untag.go ├── version.go ├── root.go ├── deleteimages.go ├── createrepo.go ├── getimages.go ├── deleterepositories.go ├── getpullThroughCacheRule.go ├── untagRepo.go ├── describerepositories.go ├── tagRepo.go ├── describeimages.go ├── login.go └── getrepositories.go ├── mkdocs.yml ├── README.md ├── pkg └── awsutil │ ├── types.go │ ├── util.go │ └── ecr.go ├── go.mod └── go.sum /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ecrctl 2 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/surajincloud/ecrctl/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES := $(shell find . -name '*.go') 2 | BINARY := ecrctl 3 | 4 | build: ecrctl 5 | 6 | clean: 7 | @rm -rf $(BINARY) 8 | 9 | $(BINARY): $(SOURCES) 10 | CGO_ENABLED=0 go build -o $(BINARY) -ldflags="-s -w" main.go 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /cmd/get.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // getCmd represents the get command 8 | var getCmd = &cobra.Command{ 9 | Use: "get", 10 | Short: "get", 11 | Long: `get command 12 | For example: 13 | 14 | ecrctl get repositories 15 | `, 16 | } 17 | 18 | func init() { 19 | rootCmd.AddCommand(getCmd) 20 | } 21 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // createCmd represents the create command 8 | var createCmd = &cobra.Command{ 9 | Use: "create", 10 | Short: "Create a resource", 11 | Long: `Describe a resource 12 | For example: 13 | 14 | ecrctl create repository `, 15 | } 16 | 17 | func init() { 18 | rootCmd.AddCommand(createCmd) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/delete.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // deleteCmd represents the delete command 8 | var deleteCmd = &cobra.Command{ 9 | Use: "delete", 10 | Short: "Delete a resource", 11 | Long: `Delete a resource 12 | For example: 13 | 14 | ecrctl delete repository `, 15 | } 16 | 17 | func init() { 18 | rootCmd.AddCommand(deleteCmd) 19 | } 20 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | `ecrctl` is command line utility for [Amazon ECR](https://aws.amazon.com/ecr/). 4 | 5 | 6 | ## Installation 7 | 8 | Check out install page for [installation](./install) instructions. 9 | 10 | ## Usage 11 | 12 | Check out Usage page for [Usage](./usage) instructions. 13 | 14 | ## Contributing 15 | 16 | Check out Contributing page for [Contributing](./contributing) instructions. 17 | -------------------------------------------------------------------------------- /cmd/describe.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // describeCmd represents the describe command 8 | var describeCmd = &cobra.Command{ 9 | Use: "describe", 10 | Short: "Describe a resource", 11 | Long: `Describe a resource 12 | For example: 13 | 14 | ecrctl describe repositories `, 15 | } 16 | 17 | func init() { 18 | rootCmd.AddCommand(describeCmd) 19 | } 20 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you want to contribute to this project in following ways: 4 | 5 | * Try it out and raise an Github issue [here](https://github.com/surajincloud/ecrctl/issues) if, 6 | - you find any bug/issue 7 | - you have any feature request 8 | 9 | * Feel free to raise [Pull request](https://github.com/surajincloud/ecrctl/pulls) to fix the issue/bug or for new feature. 10 | 11 | Thank you :) 12 | -------------------------------------------------------------------------------- /cmd/tag.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // tagCmd represents the tag command 11 | var tagCmd = &cobra.Command{ 12 | Use: "tag", 13 | Short: "tag a resource", 14 | Long: `Describe a resource 15 | For example: 16 | 17 | ecrctl tag repository `, 18 | } 19 | 20 | func init() { 21 | rootCmd.AddCommand(tagCmd) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/untag.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // tagCmd represents the tag command 11 | var untagCmd = &cobra.Command{ 12 | Use: "untag", 13 | Short: "untag a resource", 14 | Long: `Untag a resource 15 | For example: 16 | 17 | ecrctl untag repository `, 18 | } 19 | 20 | func init() { 21 | rootCmd.AddCommand(untagCmd) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // versionCmd represents the version command 10 | var versionCmd = &cobra.Command{ 11 | Use: "version", 12 | Short: "Print the version of ecrctl", 13 | Long: "Print the version of ecrctl", 14 | RunE: version, 15 | } 16 | 17 | func version(cmd *cobra.Command, args []string) error { 18 | fmt.Println("v0.1.0") 19 | return nil 20 | } 21 | 22 | func init() { 23 | rootCmd.AddCommand(versionCmd) 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: docs-site 3 | on: 4 | push: 5 | branches: 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-python@v4 15 | with: 16 | python-version: 3.x 17 | - uses: actions/cache@v3 18 | with: 19 | key: ${{ github.ref }} 20 | path: .cache 21 | - run: pip install mkdocs-material 22 | - run: mkdocs gh-deploy --force 23 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: ecrctl 2 | site_url: https://surajincloud.github.io/ecrctl/ 3 | site_author: Suraj Narwade 4 | theme: 5 | name: material 6 | palette: 7 | - scheme: default 8 | primary: indigo 9 | accent: indigo 10 | toggle: 11 | icon: material/brightness-7 12 | name: Switch to dark mode 13 | - scheme: slate 14 | primary: indigo 15 | accent: indigo 16 | toggle: 17 | icon: material/brightness-4 18 | name: Switch to light mode 19 | 20 | repo_name: surajincloud/ecrctl 21 | repo_url: https://github.com/surajincloud/ecrctl 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ecrctl-release 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | jobs: 8 | release_job: 9 | runs-on: ubuntu-latest 10 | name: goreleaser 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '^1.18' 20 | - name: GoReleaser 21 | uses: goreleaser/goreleaser-action@v5 22 | with: 23 | distribution: goreleaser 24 | version: latest 25 | args: release --rm-dist 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | * Login to ECR 4 | 5 | ``` 6 | ecrctl login 7 | ``` 8 | 9 | * Login to ECR in different region 10 | 11 | ``` 12 | ecrctl login --region 13 | ``` 14 | 15 | Note: You can also set region in environment variable `AWS_REGION` 16 | 17 | * list repositories 18 | 19 | ``` 20 | ecrctl get repositories 21 | ``` 22 | 23 | * list repositories along with their tags 24 | 25 | ``` 26 | ecrctl get repositories --show-tags 27 | ``` 28 | 29 | * filter repositories based on tag 30 | 31 | ``` 32 | ecrctl get repositories --tag key=value 33 | ``` 34 | 35 | you can also use short flag `-t` 36 | 37 | ``` 38 | ecrctl get repositories -t key=value 39 | ``` 40 | 41 | * List images from a given repository 42 | 43 | ``` 44 | ecrctl get images --repo 45 | ``` 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ecrctl 2 | CLI tool for Amazon ECR (inspired from kubectl) 3 | 4 | ## Usage 5 | 6 | * Login to ECR 7 | 8 | ``` 9 | ecrctl login 10 | ``` 11 | 12 | * Login to ECR in different region 13 | 14 | ``` 15 | ecrctl login --region 16 | ``` 17 | 18 | Note: You can also set region in environment variable `AWS_REGION` 19 | 20 | * list repositories 21 | 22 | ``` 23 | ecrctl get repositories 24 | ``` 25 | 26 | * list repositories along with their tags 27 | 28 | ``` 29 | ecrctl get repositories --show-tags 30 | ``` 31 | 32 | * filter repositories based on tag 33 | 34 | ``` 35 | ecrctl get repositories --tag key=value 36 | ``` 37 | 38 | you can also use short flag `-t` 39 | 40 | ``` 41 | ecrctl get repositories -t key=value 42 | ``` 43 | 44 | * List images from a given repository 45 | 46 | ``` 47 | ecrctl get images --repo 48 | ``` 49 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: surajincloud # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /pkg/awsutil/types.go: -------------------------------------------------------------------------------- 1 | package awsutil 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aws/aws-sdk-go-v2/service/ecr/types" 7 | ) 8 | 9 | type RepositoriesTags struct { 10 | Key, Value string 11 | } 12 | 13 | type Repository struct { 14 | Name string 15 | Arn string 16 | Uri string 17 | Age string 18 | CreatedAt *time.Time 19 | Tags []RepositoriesTags 20 | TagMutability types.ImageTagMutability 21 | EncryptionType types.EncryptionType 22 | EncryptionKMSKey string 23 | ScanOnPush bool 24 | } 25 | 26 | type Image struct { 27 | Digest string 28 | Tag string 29 | ArtifactMediaType string 30 | TagORDigest string 31 | CriticalVulnerability int32 32 | HighVulnerability int32 33 | MediumVulnerability int32 34 | Age string 35 | Size string 36 | ScanStatus types.ScanStatus 37 | ScanStatusDesc string 38 | BasicScanFindings []types.ImageScanFinding 39 | EnhancedScanFindings []types.EnhancedImageScanFinding 40 | } 41 | -------------------------------------------------------------------------------- /pkg/awsutil/util.go: -------------------------------------------------------------------------------- 1 | package awsutil 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | "github.com/aws/aws-sdk-go-v2/config" 11 | "k8s.io/apimachinery/pkg/util/duration" 12 | ) 13 | 14 | func GetAWSConfig(ctx context.Context, region string, profile string) (cfg aws.Config, err error) { 15 | if profile != "" { 16 | cfg, err = config.LoadDefaultConfig(ctx, config.WithSharedConfigProfile(profile)) 17 | if err != nil { 18 | return aws.Config{}, err 19 | } 20 | 21 | } else { 22 | cfg, err = config.LoadDefaultConfig(ctx) 23 | if err != nil { 24 | return aws.Config{}, err 25 | } 26 | } 27 | if cfg.Region == "" { 28 | // get region 29 | region, err = GetRegion(region) 30 | if err != nil { 31 | return aws.Config{}, err 32 | } 33 | cfg.Region = region 34 | } 35 | return cfg, nil 36 | } 37 | 38 | func GetRegion(region string) (string, error) { 39 | if region == "" { 40 | region = os.Getenv("AWS_REGION") 41 | if region == "" { 42 | return "", fmt.Errorf("please pass region name with --region or with AWS_REGION environment variable") 43 | } 44 | return region, nil 45 | } 46 | return region, nil 47 | } 48 | 49 | func GetAge(creationTime time.Time) string { 50 | 51 | currentTime := time.Now() 52 | 53 | age := currentTime.Sub(creationTime) 54 | 55 | return duration.HumanDuration(age) 56 | } 57 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // rootCmd represents the base command when called without any subcommands 10 | var rootCmd = &cobra.Command{ 11 | Use: "ecrctl", 12 | Short: "ecrctl - command line utility for Amazon ECR", 13 | Long: `ecrctl - command line utility for Amazon ECR 14 | For example, 15 | ecrctl get repositories 16 | 17 | ecrctl get images --repo your-repo 18 | `, 19 | // Uncomment the following line if your bare application 20 | // has an action associated with it: 21 | // Run: func(cmd *cobra.Command, args []string) { }, 22 | } 23 | 24 | // Execute adds all child commands to the root command and sets flags appropriately. 25 | // This is called by main.main(). It only needs to happen once to the rootCmd. 26 | func Execute() { 27 | err := rootCmd.Execute() 28 | if err != nil { 29 | os.Exit(1) 30 | } 31 | } 32 | 33 | func init() { 34 | // Here you will define your flags and configuration settings. 35 | // Cobra supports persistent flags, which, if defined here, 36 | // will be global for your application. 37 | 38 | // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ecrctl.yaml)") 39 | 40 | // Cobra also supports local flags, which will only run 41 | // when this action is called directly. 42 | // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 43 | } 44 | -------------------------------------------------------------------------------- /cmd/deleteimages.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/aws/aws-sdk-go-v2/service/ecr" 9 | "github.com/aws/aws-sdk-go-v2/service/ecr/types" 10 | "github.com/spf13/cobra" 11 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 12 | ) 13 | 14 | // deleteImagesCmd represents the image command 15 | var deleteImageCmd = &cobra.Command{ 16 | Use: "images", 17 | Aliases: []string{"image"}, 18 | Short: "Delete an ECR image", 19 | Long: `Delete an ECR image 20 | For example: 21 | ecrctl delete image your-image 22 | `, 23 | RunE: deleteImage, 24 | } 25 | 26 | func deleteImage(cmd *cobra.Command, args []string) error { 27 | ctx := context.Background() 28 | // read flag values 29 | region, err := cmd.Flags().GetString("region") 30 | if err != nil { 31 | fmt.Println("no region", err) 32 | } 33 | 34 | profile, err := cmd.Flags().GetString("profile") 35 | if err != nil { 36 | fmt.Println("no profile", err) 37 | } 38 | 39 | if len(args) == 0 { 40 | fmt.Println("please pass image name") 41 | os.Exit(1) 42 | 43 | } 44 | // image Name 45 | imageName := args[0] 46 | 47 | // aws config 48 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 49 | if err != nil { 50 | return err 51 | } 52 | // Create an EKS client using the loaded configuration 53 | client := ecr.NewFromConfig(cfg) 54 | 55 | _, err = client.BatchDeleteImage(ctx, &ecr.BatchDeleteImageInput{ 56 | ImageIds: []types.ImageIdentifier{ 57 | { 58 | ImageTag: &imageName, 59 | }, 60 | }, 61 | }) 62 | 63 | if err != nil { 64 | return err 65 | } 66 | return nil 67 | } 68 | func init() { 69 | deleteCmd.AddCommand(deleteImageCmd) 70 | deleteCmd.PersistentFlags().String("region", "", "region") 71 | deleteCmd.PersistentFlags().String("profile", "", "profile") 72 | deleteCmd.PersistentFlags().String("image", "", "image") 73 | 74 | } 75 | -------------------------------------------------------------------------------- /cmd/createrepo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/aws/aws-sdk-go-v2/aws" 12 | "github.com/aws/aws-sdk-go-v2/service/ecr" 13 | "github.com/spf13/cobra" 14 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 15 | ) 16 | 17 | // repoCmd represents the repo command 18 | var createRepoCmd = &cobra.Command{ 19 | Use: "repository", 20 | Aliases: []string{"repo"}, 21 | Short: "Create an ECR repository", 22 | Long: `Create an ECR repository 23 | For example: 24 | ecrctl create repo your-repo 25 | `, 26 | RunE: createRepo, 27 | } 28 | 29 | func createRepo(cmd *cobra.Command, args []string) error { 30 | ctx := context.Background() 31 | // read flag values 32 | region, err := cmd.Flags().GetString("region") 33 | if err != nil { 34 | fmt.Println("no region", err) 35 | } 36 | 37 | profile, err := cmd.Flags().GetString("profile") 38 | if err != nil { 39 | fmt.Println("no profile", err) 40 | } 41 | 42 | if len(args) == 0 { 43 | fmt.Println("please pass repo name") 44 | os.Exit(1) 45 | 46 | } 47 | // repo Name 48 | repoName := args[0] 49 | 50 | // aws config 51 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 52 | if err != nil { 53 | return err 54 | } 55 | // Create an EKS client using the loaded configuration 56 | client := ecr.NewFromConfig(cfg) 57 | 58 | out, err := client.CreateRepository(ctx, &ecr.CreateRepositoryInput{ 59 | RepositoryName: aws.String(repoName), 60 | }) 61 | if err != nil { 62 | return err 63 | } 64 | fmt.Println("repo created successfully.") 65 | fmt.Println("repo ARN: ", aws.ToString(out.Repository.RepositoryArn)) 66 | fmt.Println("repo URI: ", aws.ToString(out.Repository.RepositoryUri)) 67 | 68 | return nil 69 | 70 | } 71 | func init() { 72 | createCmd.AddCommand(createRepoCmd) 73 | createRepoCmd.PersistentFlags().String("region", "", "region") 74 | createRepoCmd.PersistentFlags().String("profile", "", "profile") 75 | createRepoCmd.PersistentFlags().String("repo", "", "repo") 76 | 77 | } 78 | -------------------------------------------------------------------------------- /cmd/getimages.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "text/tabwriter" 9 | 10 | "github.com/aws/aws-sdk-go-v2/service/ecr" 11 | "github.com/spf13/cobra" 12 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 13 | ) 14 | 15 | // imagesCmd represents the images command 16 | var getImagesCmd = &cobra.Command{ 17 | Use: "images", 18 | Aliases: []string{"image"}, 19 | Short: "Get ECR images", 20 | Long: `List ECR images 21 | Examples: 22 | 23 | # List all images with URI 24 | ecrctl get images --repo 25 | `, 26 | RunE: GetImages, 27 | } 28 | 29 | func GetImages(cmd *cobra.Command, args []string) error { 30 | ctx := context.Background() 31 | // read flag values 32 | region, err := cmd.Flags().GetString("region") 33 | if err != nil { 34 | fmt.Println("no region", err) 35 | } 36 | 37 | profile, err := cmd.Flags().GetString("profile") 38 | if err != nil { 39 | fmt.Println("no profile", err) 40 | } 41 | 42 | repo, err := cmd.Flags().GetString("repo") 43 | if err != nil { 44 | fmt.Println("no repo", err) 45 | } 46 | 47 | // aws config 48 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | // Create an EKS client using the loaded configuration 53 | client := ecr.NewFromConfig(cfg) 54 | imageList, err := awspkg.ListImages(ctx, client, repo) 55 | if err != nil { 56 | return err 57 | } 58 | w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent) 59 | defer w.Flush() 60 | fmt.Fprintln(w, "TAG", "\t", "VULNERABILITIES", "\t", "SIZE", "\t", "AGE") 61 | for _, i := range imageList { 62 | fmt.Fprintln(w, i.Tag, "\t", fmt.Sprintf("C: %d, H: %d, M: %d", i.CriticalVulnerability, i.HighVulnerability, i.MediumVulnerability), "\t", i.Size, "\t", i.Age) 63 | } 64 | return nil 65 | } 66 | func init() { 67 | getCmd.AddCommand(getImagesCmd) 68 | getImagesCmd.PersistentFlags().String("region", "", "region") 69 | getImagesCmd.PersistentFlags().String("profile", "", "profile") 70 | getImagesCmd.PersistentFlags().String("repo", "", "repo") 71 | } 72 | -------------------------------------------------------------------------------- /cmd/deleterepositories.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/aws/aws-sdk-go-v2/aws" 9 | "github.com/aws/aws-sdk-go-v2/service/ecr" 10 | "github.com/spf13/cobra" 11 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 12 | ) 13 | 14 | // repositoryCmd represents the repository command 15 | var deleteRepoCmd = &cobra.Command{ 16 | Use: "repository", 17 | Aliases: []string{"repo"}, 18 | Short: "Delete an ECR repository", 19 | Long: `Delete an ECR repository 20 | For example: 21 | ecrctl delete repo your-repo 22 | `, 23 | RunE: deleteRepo, 24 | } 25 | 26 | func deleteRepo(cmd *cobra.Command, args []string) error { 27 | ctx := context.Background() 28 | // read flag values 29 | region, err := cmd.Flags().GetString("region") 30 | if err != nil { 31 | fmt.Println("no region", err) 32 | } 33 | 34 | profile, err := cmd.Flags().GetString("profile") 35 | if err != nil { 36 | fmt.Println("no profile", err) 37 | } 38 | 39 | if len(args) == 0 { 40 | fmt.Println("please pass repo name") 41 | os.Exit(1) 42 | 43 | } 44 | // repo Name 45 | repoName := args[0] 46 | 47 | // aws config 48 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 49 | if err != nil { 50 | return err 51 | } 52 | // Create an EKS client using the loaded configuration 53 | client := ecr.NewFromConfig(cfg) 54 | 55 | _, err = client.DeleteRepository(ctx, &ecr.DeleteRepositoryInput{ 56 | RepositoryName: aws.String(repoName), 57 | Force: false, 58 | }) 59 | 60 | if err != nil { 61 | fmt.Println("It seems that repository may contains some images.") 62 | fmt.Println("you will need --force flag to force the repository deletion, it will also delete the images from the repository.") 63 | } else { 64 | fmt.Println("repo deleted successfully.") 65 | } 66 | 67 | return nil 68 | } 69 | func init() { 70 | deleteCmd.AddCommand(deleteRepoCmd) 71 | deleteRepoCmd.PersistentFlags().String("region", "", "region") 72 | deleteRepoCmd.PersistentFlags().String("profile", "", "profile") 73 | deleteRepoCmd.PersistentFlags().String("repo", "", "repo") 74 | 75 | } 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/surajincloud/ecrctl 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2 v1.21.0 7 | github.com/aws/aws-sdk-go-v2/config v1.18.39 8 | github.com/aws/aws-sdk-go-v2/service/ecr v1.20.0 9 | github.com/docker/cli v24.0.6+incompatible 10 | github.com/docker/docker v24.0.6+incompatible 11 | github.com/docker/go-units v0.5.0 12 | github.com/spf13/cobra v1.7.0 13 | k8s.io/apimachinery v0.28.2 14 | ) 15 | 16 | require ( 17 | github.com/Microsoft/go-winio v0.6.1 // indirect 18 | github.com/aws/aws-sdk-go-v2/credentials v1.13.37 // indirect 19 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect 20 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect 21 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect 22 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 // indirect 23 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect 24 | github.com/aws/aws-sdk-go-v2/service/sso v1.13.6 // indirect 25 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.6 // indirect 26 | github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 // indirect 27 | github.com/aws/smithy-go v1.14.2 // indirect 28 | github.com/docker/distribution v2.8.2+incompatible // indirect 29 | github.com/docker/docker-credential-helpers v0.7.0 // indirect 30 | github.com/docker/go-connections v0.4.0 // indirect 31 | github.com/gogo/protobuf v1.3.2 // indirect 32 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 33 | github.com/jmespath/go-jmespath v0.4.0 // indirect 34 | github.com/moby/term v0.5.0 // indirect 35 | github.com/morikuni/aec v1.0.0 // indirect 36 | github.com/opencontainers/go-digest v1.0.0 // indirect 37 | github.com/opencontainers/image-spec v1.0.2 // indirect 38 | github.com/pkg/errors v0.9.1 // indirect 39 | github.com/sirupsen/logrus v1.9.0 // indirect 40 | github.com/spf13/pflag v1.0.5 // indirect 41 | github.com/stretchr/testify v1.8.4 // indirect 42 | golang.org/x/mod v0.10.0 // indirect 43 | golang.org/x/net v0.13.0 // indirect 44 | golang.org/x/sys v0.10.0 // indirect 45 | golang.org/x/tools v0.8.0 // indirect 46 | gotest.tools/v3 v3.4.0 // indirect 47 | ) 48 | -------------------------------------------------------------------------------- /cmd/getpullThroughCacheRule.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "text/tabwriter" 9 | 10 | "github.com/aws/aws-sdk-go-v2/service/ecr" 11 | "github.com/spf13/cobra" 12 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 13 | ) 14 | 15 | // pullThroughCacheRuleCmd represents the pullThroughCacheRule command 16 | var getpullThroughCacheRuleCmd = &cobra.Command{ 17 | Use: "pullThroughCacheRule", 18 | Short: "Get ECR Pull through cache rules", 19 | Aliases: []string{"ptc"}, 20 | Long: `Get ECR Pull through cache rules 21 | Examples: 22 | 23 | # List all Pull through cache rules 24 | ecrctl get pullThroughCacheRule 25 | 26 | # List all repositories their tags 27 | ecrctl get pullThroughCacheRule 28 | `, 29 | RunE: getpullThroughCacheRule, 30 | } 31 | 32 | func getpullThroughCacheRule(cmd *cobra.Command, args []string) error { 33 | 34 | ctx := context.Background() 35 | // read flag values 36 | region, err := cmd.Flags().GetString("region") 37 | if err != nil { 38 | fmt.Println("no region", err) 39 | } 40 | 41 | profile, err := cmd.Flags().GetString("profile") 42 | if err != nil { 43 | fmt.Println("no profile", err) 44 | } 45 | 46 | // aws config 47 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | // Create an EKS client using the loaded configuration 52 | client := ecr.NewFromConfig(cfg) 53 | 54 | ptcrs, err := client.DescribePullThroughCacheRules(ctx, &ecr.DescribePullThroughCacheRulesInput{}) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent) 60 | defer w.Flush() 61 | fmt.Fprintln(w, "NAME", "\t", "URL", "\t", "AGE") 62 | 63 | for _, i := range ptcrs.PullThroughCacheRules { 64 | fmt.Fprintln(w, *i.EcrRepositoryPrefix, "\t", *i.UpstreamRegistryUrl, "\t", awspkg.GetAge(*i.CreatedAt)) 65 | } 66 | 67 | return nil 68 | } 69 | func init() { 70 | getCmd.AddCommand(getpullThroughCacheRuleCmd) 71 | getpullThroughCacheRuleCmd.PersistentFlags().String("region", "", "region") 72 | getpullThroughCacheRuleCmd.PersistentFlags().String("profile", "", "profile") 73 | 74 | } 75 | -------------------------------------------------------------------------------- /cmd/untagRepo.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/aws/aws-sdk-go-v2/service/ecr" 9 | "github.com/spf13/cobra" 10 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 11 | ) 12 | 13 | // repoCmd represents the repo command 14 | var untagRepoCmd = &cobra.Command{ 15 | Use: "repository", 16 | Aliases: []string{"repo"}, 17 | Short: "untag an ECR repository", 18 | Long: `untag an ECR repository 19 | For example: 20 | ecrctl untag repo your-repo 21 | `, 22 | RunE: untagRepo, 23 | } 24 | 25 | func untagRepo(cmd *cobra.Command, args []string) error { 26 | ctx := context.Background() 27 | // read flag values 28 | region, err := cmd.Flags().GetString("region") 29 | if err != nil { 30 | fmt.Println("no region", err) 31 | } 32 | 33 | profile, err := cmd.Flags().GetString("profile") 34 | if err != nil { 35 | fmt.Println("no profile", err) 36 | } 37 | 38 | tag, err := cmd.Flags().GetString("tag") 39 | if err != nil { 40 | fmt.Println("no tags", err) 41 | } 42 | 43 | if len(args) == 0 { 44 | fmt.Println("please pass repo name") 45 | os.Exit(1) 46 | 47 | } 48 | // repo Name 49 | repoName := args[0] 50 | 51 | // aws config 52 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 53 | if err != nil { 54 | return err 55 | } 56 | // tag an EKS client using the loaded configuration 57 | client := ecr.NewFromConfig(cfg) 58 | 59 | repoDetail, err := client.DescribeRepositories(ctx, &ecr.DescribeRepositoriesInput{ 60 | RepositoryNames: []string{repoName}, 61 | }) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | _, err = client.UntagResource(ctx, &ecr.UntagResourceInput{ 67 | ResourceArn: repoDetail.Repositories[0].RepositoryArn, 68 | TagKeys: []string{tag}, 69 | }) 70 | if err != nil { 71 | return err 72 | } 73 | fmt.Println("repo untagged successfully.") 74 | 75 | return nil 76 | 77 | } 78 | func init() { 79 | untagCmd.AddCommand(untagRepoCmd) 80 | untagRepoCmd.PersistentFlags().String("region", "", "region") 81 | untagRepoCmd.PersistentFlags().String("profile", "", "profile") 82 | untagRepoCmd.PersistentFlags().String("repo", "", "repo") 83 | untagRepoCmd.PersistentFlags().StringP("tag", "t", "", "tags") 84 | 85 | } 86 | -------------------------------------------------------------------------------- /cmd/describerepositories.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/aws/aws-sdk-go-v2/service/ecr" 10 | "github.com/spf13/cobra" 11 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 12 | ) 13 | 14 | // repositoriesCmd represents the repositories command 15 | var describeRepositoriesCmd = &cobra.Command{ 16 | Use: "repositories", 17 | Aliases: []string{"repo"}, 18 | Short: "Describe a repositories", 19 | Long: `Describe a repositories 20 | For example: 21 | 22 | ecrctl describe repositories 23 | `, 24 | RunE: describeRepo, 25 | } 26 | 27 | func describeRepo(cmd *cobra.Command, args []string) error { 28 | 29 | ctx := context.Background() 30 | // read flag values 31 | region, err := cmd.Flags().GetString("region") 32 | if err != nil { 33 | fmt.Println("no region", err) 34 | } 35 | 36 | profile, err := cmd.Flags().GetString("profile") 37 | if err != nil { 38 | fmt.Println("no profile", err) 39 | } 40 | 41 | if len(args) == 0 { 42 | fmt.Println("please pass repo name") 43 | os.Exit(1) 44 | 45 | } 46 | // repo Name 47 | repoName := args[0] 48 | 49 | // aws config 50 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 51 | if err != nil { 52 | return err 53 | } 54 | // Create an EKS client using the loaded configuration 55 | client := ecr.NewFromConfig(cfg) 56 | 57 | repo, err := awspkg.DescribeRepository(ctx, client, repoName) 58 | if err != nil { 59 | return err 60 | } 61 | fmt.Println("Name: ", repo.Name) 62 | fmt.Println("URI: ", repo.Uri) 63 | fmt.Println("ARN: ", repo.Arn) 64 | fmt.Println("TAG Mutability: ", repo.TagMutability) 65 | fmt.Println("Created At: ", repo.CreatedAt) 66 | fmt.Println("Tags: ", TagsToString(repo.Tags)) 67 | fmt.Println("Scan On Push: ", repo.ScanOnPush) 68 | 69 | return nil 70 | } 71 | 72 | func TagsToString(repoTagList []awspkg.RepositoriesTags) string { 73 | var tagList []string 74 | for _, k := range repoTagList { 75 | tagList = append(tagList, fmt.Sprintf("%s=%s", k.Key, k.Value)) 76 | } 77 | 78 | return strings.Join(tagList, ",") 79 | } 80 | 81 | func init() { 82 | describeCmd.AddCommand(describeRepositoriesCmd) 83 | describeCmd.PersistentFlags().String("region", "", "region") 84 | describeCmd.PersistentFlags().String("profile", "", "profile") 85 | } 86 | -------------------------------------------------------------------------------- /cmd/tagRepo.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/aws/aws-sdk-go-v2/service/ecr" 10 | "github.com/aws/aws-sdk-go-v2/service/ecr/types" 11 | "github.com/spf13/cobra" 12 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 13 | ) 14 | 15 | // repoCmd represents the repo command 16 | var tagRepoCmd = &cobra.Command{ 17 | Use: "repository", 18 | Aliases: []string{"repo"}, 19 | Short: "tag an ECR repository", 20 | Long: `tag an ECR repository 21 | For example: 22 | ecrctl tag repo your-repo 23 | `, 24 | RunE: tagRepo, 25 | } 26 | 27 | func tagRepo(cmd *cobra.Command, args []string) error { 28 | ctx := context.Background() 29 | // read flag values 30 | region, err := cmd.Flags().GetString("region") 31 | if err != nil { 32 | fmt.Println("no region", err) 33 | } 34 | 35 | profile, err := cmd.Flags().GetString("profile") 36 | if err != nil { 37 | fmt.Println("no profile", err) 38 | } 39 | 40 | tags, err := cmd.Flags().GetString("tag") 41 | if err != nil { 42 | fmt.Println("no tags", err) 43 | } 44 | 45 | if len(args) == 0 { 46 | fmt.Println("please pass repo name") 47 | os.Exit(1) 48 | 49 | } 50 | // repo Name 51 | repoName := args[0] 52 | 53 | // aws config 54 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 55 | if err != nil { 56 | return err 57 | } 58 | // tag an EKS client using the loaded configuration 59 | client := ecr.NewFromConfig(cfg) 60 | 61 | repoDetail, err := client.DescribeRepositories(ctx, &ecr.DescribeRepositoriesInput{ 62 | RepositoryNames: []string{repoName}, 63 | }) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | keyValue := strings.Split(tags, "=") 69 | 70 | _, err = client.TagResource(ctx, &ecr.TagResourceInput{ 71 | ResourceArn: repoDetail.Repositories[0].RepositoryArn, 72 | Tags: []types.Tag{ 73 | { 74 | Key: &keyValue[0], 75 | Value: &keyValue[1], 76 | }, 77 | }, 78 | }) 79 | if err != nil { 80 | return err 81 | } 82 | fmt.Println("repo tagged successfully.") 83 | 84 | return nil 85 | 86 | } 87 | func init() { 88 | tagCmd.AddCommand(tagRepoCmd) 89 | tagRepoCmd.PersistentFlags().String("region", "", "region") 90 | tagRepoCmd.PersistentFlags().String("profile", "", "profile") 91 | tagRepoCmd.PersistentFlags().String("repo", "", "repo") 92 | tagRepoCmd.PersistentFlags().StringP("tag", "t", "", "tags") 93 | 94 | } 95 | -------------------------------------------------------------------------------- /cmd/describeimages.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "text/tabwriter" 11 | 12 | "github.com/aws/aws-sdk-go-v2/service/ecr" 13 | "github.com/spf13/cobra" 14 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 15 | ) 16 | 17 | // imagesCmd represents the images command 18 | var describeImagesCmd = &cobra.Command{ 19 | Use: "images", 20 | Aliases: []string{"image"}, 21 | Short: "describe an image", 22 | Long: `describe an image and get more information 23 | For example: 24 | ecrctl describe images your-image --repo your-repo 25 | `, 26 | RunE: describeImages, 27 | } 28 | 29 | func describeImages(cmd *cobra.Command, args []string) error { 30 | ctx := context.Background() 31 | // read flag values 32 | region, err := cmd.Flags().GetString("region") 33 | if err != nil { 34 | fmt.Println("no region", err) 35 | } 36 | 37 | profile, err := cmd.Flags().GetString("profile") 38 | if err != nil { 39 | fmt.Println("no profile", err) 40 | } 41 | 42 | repoName, err := cmd.Flags().GetString("repo") 43 | if err != nil { 44 | fmt.Println("no repo", err) 45 | } 46 | 47 | if len(args) == 0 { 48 | fmt.Println("please pass repo name") 49 | os.Exit(1) 50 | 51 | } 52 | // image Name 53 | imageName := args[0] 54 | 55 | // aws config 56 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 57 | if err != nil { 58 | return err 59 | } 60 | // Create an EKS client using the loaded configuration 61 | client := ecr.NewFromConfig(cfg) 62 | 63 | image, err := awspkg.DescribeImage(ctx, client, imageName, repoName) 64 | if err != nil { 65 | return err 66 | } 67 | fmt.Println("Name: ", imageName) 68 | fmt.Println("Repo: ", repoName) 69 | fmt.Println("Type: ", image.ArtifactMediaType) 70 | fmt.Println("Vulnerabilities:") 71 | w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent) 72 | defer w.Flush() 73 | fmt.Fprintln(w, "TAG", "\t", "SEVERITY", "\t", "URI") 74 | for _, i := range image.BasicScanFindings { 75 | fmt.Fprintln(w, *i.Name, "\t", i.Severity, "\t", *i.Uri) 76 | 77 | } 78 | return nil 79 | } 80 | 81 | func init() { 82 | describeCmd.AddCommand(describeImagesCmd) 83 | describeImagesCmd.PersistentFlags().String("region", "", "region") 84 | describeImagesCmd.PersistentFlags().String("profile", "", "profile") 85 | describeImagesCmd.PersistentFlags().String("repo", "", "repo") 86 | } 87 | -------------------------------------------------------------------------------- /cmd/login.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "log" 9 | "strings" 10 | 11 | "github.com/spf13/cobra" 12 | 13 | "github.com/aws/aws-sdk-go-v2/service/ecr" 14 | 15 | "github.com/docker/cli/cli/config" 16 | "github.com/docker/docker/api/types/registry" 17 | 18 | cliTypes "github.com/docker/cli/cli/config/types" 19 | dockerclient "github.com/docker/docker/client" 20 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 21 | ) 22 | 23 | // loginCmd represents the login command 24 | var loginCmd = &cobra.Command{ 25 | Use: "login", 26 | Short: "Login to ECR", 27 | Long: `Login to ECR. 28 | For example: 29 | ecrctl login 30 | 31 | ecrctl login --region your-region 32 | `, 33 | RunE: Login, 34 | } 35 | 36 | func Login(cmd *cobra.Command, args []string) error { 37 | 38 | ctx := context.Background() 39 | 40 | // read flag values 41 | region, err := cmd.Flags().GetString("region") 42 | if err != nil { 43 | fmt.Println("no region", err) 44 | } 45 | 46 | profile, err := cmd.Flags().GetString("profile") 47 | if err != nil { 48 | fmt.Println("no profile", err) 49 | } 50 | 51 | // aws config 52 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | // Create an ECR client 58 | client := ecr.NewFromConfig(cfg) 59 | 60 | // Get the login token from ECR 61 | loginOutput, err := client.GetAuthorizationToken(ctx, &ecr.GetAuthorizationTokenInput{}) 62 | if err != nil { 63 | fmt.Println("Failed to get authorization token:", err) 64 | return err 65 | } 66 | 67 | authData := loginOutput.AuthorizationData[0] 68 | endpoint := *authData.ProxyEndpoint 69 | token := *authData.AuthorizationToken 70 | 71 | decodedToken, err := base64.StdEncoding.DecodeString(token) 72 | if err != nil { 73 | fmt.Println("Failed to decode authorization token:", err) 74 | return err 75 | } 76 | 77 | password := strings.Split(string(decodedToken), ":")[1] 78 | 79 | // Create a Docker client 80 | dockerClient, err := dockerclient.NewClientWithOpts() 81 | if err != nil { 82 | fmt.Println("Failed to create Docker client:", err) 83 | return err 84 | } 85 | 86 | // Authenticate with the ECR repository 87 | authConfig := registry.AuthConfig{ 88 | Username: "AWS", 89 | Password: password, 90 | ServerAddress: endpoint, 91 | } 92 | _, err = dockerClient.RegistryLogin(ctx, authConfig) 93 | if err != nil { 94 | fmt.Println("Failed to log in to ECR:", err) 95 | return err 96 | } 97 | 98 | // writing credentials to local file (~/.docker/config.json) 99 | var configFileErr io.Writer 100 | localConfigFile := config.LoadDefaultConfigFile(configFileErr) 101 | localConfigFile.AuthConfigs[endpoint] = cliTypes.AuthConfig{ 102 | Username: "AWS", 103 | Password: password, 104 | ServerAddress: endpoint, 105 | } 106 | 107 | err = localConfigFile.Save() 108 | if err != nil { 109 | fmt.Println("Failed saving to file") 110 | return err 111 | } 112 | fmt.Println("Logged in to ECR successfully") 113 | return nil 114 | } 115 | 116 | func init() { 117 | rootCmd.AddCommand(loginCmd) 118 | loginCmd.PersistentFlags().String("region", "", "region") 119 | loginCmd.PersistentFlags().String("profile", "", "profile") 120 | } 121 | -------------------------------------------------------------------------------- /cmd/getrepositories.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | "text/tabwriter" 10 | 11 | "github.com/aws/aws-sdk-go-v2/service/ecr" 12 | "github.com/spf13/cobra" 13 | awspkg "github.com/surajincloud/ecrctl/pkg/awsutil" 14 | ) 15 | 16 | // repositoriesCmd represents the repositories command 17 | var getRepositoriesCmd = &cobra.Command{ 18 | Use: "repositories", 19 | Short: "Get ECR repositories", 20 | Aliases: []string{"repo"}, 21 | Long: `List ECR repositories 22 | Examples: 23 | 24 | # List all repositories with URI 25 | ecrctl get repositories 26 | 27 | # List all repositories their tags 28 | ecrctl get repositories --show-tags 29 | 30 | # List all repositories with given tags 31 | ecrctl get repositories --tag key=value 32 | 33 | or 34 | 35 | ecrctl get repositories -t key=value 36 | `, 37 | RunE: repositories, 38 | } 39 | 40 | func repositories(cmd *cobra.Command, args []string) error { 41 | 42 | ctx := context.Background() 43 | // read flag values 44 | region, err := cmd.Flags().GetString("region") 45 | if err != nil { 46 | fmt.Println("no region", err) 47 | } 48 | 49 | profile, err := cmd.Flags().GetString("profile") 50 | if err != nil { 51 | fmt.Println("no profile", err) 52 | } 53 | 54 | tags, err := cmd.Flags().GetString("tag") 55 | if err != nil { 56 | fmt.Println("no tags", err) 57 | } 58 | 59 | showTags, err := cmd.Flags().GetBool("show-tags") 60 | if err != nil { 61 | fmt.Println("no tags", err) 62 | } 63 | 64 | // aws config 65 | cfg, err := awspkg.GetAWSConfig(ctx, region, profile) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | // Create an EKS client using the loaded configuration 70 | client := ecr.NewFromConfig(cfg) 71 | 72 | repoList, err := awspkg.ListRepos(ctx, client) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | 77 | w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent) 78 | defer w.Flush() 79 | 80 | if !showTags && tags == "" { 81 | fmt.Fprintln(w, "NAME", "\t", "URI") 82 | for _, i := range repoList { 83 | fmt.Fprintln(w, i.Name, "\t", i.Uri) 84 | } 85 | } 86 | 87 | if !showTags && tags != "" { 88 | fmt.Fprintln(w, "NAME", "\t", "URI") 89 | for _, i := range repoList { 90 | var tagList []string 91 | for _, k := range i.Tags { 92 | tagList = append(tagList, fmt.Sprintf("%s=%s", k.Key, k.Value)) 93 | } 94 | tagsStringFormat := strings.Join(tagList, ",") 95 | if tags != "" { 96 | if strings.Contains(tagsStringFormat, tags) { 97 | fmt.Fprintln(w, i.Name, "\t", i.Uri) 98 | } 99 | } else { 100 | fmt.Fprintln(w, i.Name, "\t", i.Uri) 101 | } 102 | } 103 | } 104 | 105 | if showTags && tags == "" { 106 | fmt.Fprintln(w, "NAME", "\t", "URI", "\t", "TAGS") 107 | for _, i := range repoList { 108 | var tagList []string 109 | for _, k := range i.Tags { 110 | tagList = append(tagList, fmt.Sprintf("%s=%s", k.Key, k.Value)) 111 | } 112 | tagsStringFormat := strings.Join(tagList, ",") 113 | if tags != "" { 114 | if strings.Contains(tagsStringFormat, tags) { 115 | fmt.Fprintln(w, i.Name, "\t", i.Uri, "\t", tagsStringFormat) 116 | } 117 | } else { 118 | fmt.Fprintln(w, i.Name, "\t", i.Uri, "\t", tagsStringFormat) 119 | } 120 | } 121 | } 122 | 123 | return err 124 | } 125 | 126 | func init() { 127 | getCmd.AddCommand(getRepositoriesCmd) 128 | getRepositoriesCmd.PersistentFlags().String("region", "", "region") 129 | getRepositoriesCmd.PersistentFlags().String("profile", "", "profile") 130 | getRepositoriesCmd.PersistentFlags().StringP("tag", "t", "", "tags") 131 | getRepositoriesCmd.PersistentFlags().Bool("show-tags", false, "show tags") 132 | } 133 | -------------------------------------------------------------------------------- /pkg/awsutil/ecr.go: -------------------------------------------------------------------------------- 1 | package awsutil 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | "github.com/aws/aws-sdk-go-v2/service/ecr" 11 | "github.com/aws/aws-sdk-go-v2/service/ecr/types" 12 | "github.com/docker/go-units" 13 | ) 14 | 15 | func DescribeImage(ctx context.Context, client *ecr.Client, imageName string, repoName string) (Image, error) { 16 | imageList, err := ListImages(ctx, client, repoName) 17 | if err != nil { 18 | return Image{}, err 19 | } 20 | 21 | for _, i := range imageList { 22 | if i.Tag == imageName { 23 | scans, err := client.DescribeImageScanFindings(ctx, &ecr.DescribeImageScanFindingsInput{ 24 | RepositoryName: &repoName, 25 | ImageId: &types.ImageIdentifier{ 26 | ImageTag: &imageName, 27 | }, 28 | }) 29 | if err != nil { 30 | fmt.Println("error while scan findings") 31 | } 32 | i.BasicScanFindings = scans.ImageScanFindings.Findings 33 | i.EnhancedScanFindings = scans.ImageScanFindings.EnhancedFindings 34 | return i, nil 35 | } 36 | } 37 | return Image{}, nil 38 | } 39 | 40 | func ListImages(ctx context.Context, client *ecr.Client, repoName string) ([]Image, error) { 41 | 42 | describeImagesOutput, err := client.DescribeImages(ctx, &ecr.DescribeImagesInput{ 43 | RepositoryName: aws.String(repoName), 44 | }) 45 | 46 | if err != nil { 47 | return []Image{}, err 48 | } 49 | 50 | // Sort the images in descending order based on the image push timestamp 51 | sort.Slice(describeImagesOutput.ImageDetails, func(i, j int) bool { 52 | return describeImagesOutput.ImageDetails[i].ImagePushedAt.After(*describeImagesOutput.ImageDetails[j].ImagePushedAt) 53 | }) 54 | 55 | var imageList []Image 56 | 57 | for k, i := range describeImagesOutput.ImageDetails { 58 | 59 | if k <= 4 { 60 | 61 | var tags string 62 | if len(i.ImageTags) == 0 { 63 | tags = "untagged" 64 | } else { 65 | tags = strings.Join(i.ImageTags, ",") 66 | } 67 | 68 | size := units.HumanSize(float64(*i.ImageSizeInBytes)) 69 | imageList = append(imageList, Image{ 70 | ArtifactMediaType: *i.ArtifactMediaType, 71 | Digest: *i.ImageDigest, 72 | Tag: tags, 73 | CriticalVulnerability: i.ImageScanFindingsSummary.FindingSeverityCounts["CRITICAL"], 74 | HighVulnerability: i.ImageScanFindingsSummary.FindingSeverityCounts["HIGH"], 75 | MediumVulnerability: i.ImageScanFindingsSummary.FindingSeverityCounts["MEDIUM"], 76 | Age: GetAge(*i.ImagePushedAt), 77 | Size: size, 78 | ScanStatus: i.ImageScanStatus.Status, 79 | ScanStatusDesc: *i.ImageScanStatus.Description, 80 | }) 81 | } 82 | } 83 | return imageList, nil 84 | } 85 | 86 | func DescribeRepository(ctx context.Context, client *ecr.Client, repoName string) (Repository, error) { 87 | repoList, err := ListRepos(ctx, client) 88 | if err != nil { 89 | return Repository{}, err 90 | } 91 | for _, i := range repoList { 92 | if i.Name == repoName { 93 | return i, nil 94 | } 95 | } 96 | return Repository{}, nil 97 | } 98 | 99 | func ListRepos(ctx context.Context, client *ecr.Client) ([]Repository, error) { 100 | var repoList []Repository 101 | output, err := client.DescribeRepositories(ctx, &ecr.DescribeRepositoriesInput{}) 102 | if err != nil { 103 | return []Repository{}, err 104 | } 105 | 106 | for _, i := range output.Repositories { 107 | 108 | repoTags, err := ListTags(ctx, client, *i.RepositoryArn) 109 | if err != nil { 110 | return []Repository{}, err 111 | } 112 | repoList = append(repoList, Repository{ 113 | Name: aws.ToString(i.RepositoryName), 114 | Arn: aws.ToString(i.RepositoryArn), 115 | Uri: aws.ToString(i.RepositoryUri), 116 | Age: GetAge(*i.CreatedAt), 117 | CreatedAt: i.CreatedAt, 118 | Tags: repoTags, 119 | TagMutability: i.ImageTagMutability, 120 | EncryptionType: i.EncryptionConfiguration.EncryptionType, 121 | EncryptionKMSKey: aws.ToString(i.EncryptionConfiguration.KmsKey), 122 | ScanOnPush: i.ImageScanningConfiguration.ScanOnPush, 123 | }) 124 | 125 | } 126 | 127 | return repoList, nil 128 | } 129 | 130 | func ListTags(ctx context.Context, client *ecr.Client, repoArn string) (repoTags []RepositoriesTags, err error) { 131 | out, err := client.ListTagsForResource(ctx, &ecr.ListTagsForResourceInput{ 132 | ResourceArn: &repoArn, 133 | }) 134 | if err != nil { 135 | return []RepositoriesTags{}, err 136 | } 137 | 138 | for _, k := range out.Tags { 139 | repoTags = append(repoTags, RepositoriesTags{ 140 | Key: aws.ToString(k.Key), 141 | Value: aws.ToString(k.Value), 142 | }) 143 | } 144 | return repoTags, nil 145 | 146 | } 147 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 2 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 3 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 4 | github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= 5 | github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= 6 | github.com/aws/aws-sdk-go-v2/config v1.18.39 h1:oPVyh6fuu/u4OiW4qcuQyEtk7U7uuNBmHmJSLg1AJsQ= 7 | github.com/aws/aws-sdk-go-v2/config v1.18.39/go.mod h1:+NH/ZigdPckFpgB1TRcRuWCB/Kbbvkxc/iNAKTq5RhE= 8 | github.com/aws/aws-sdk-go-v2/credentials v1.13.37 h1:BvEdm09+ZEh2XtN+PVHPcYwKY3wIeB6pw7vPRM4M9/U= 9 | github.com/aws/aws-sdk-go-v2/credentials v1.13.37/go.mod h1:ACLrdkd4CLZyXOghZ8IYumQbcooAcp2jo/s2xsFH8IM= 10 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg= 11 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8= 12 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g= 13 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= 14 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI= 15 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= 16 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 h1:GPUcE/Yq7Ur8YSUk6lVkoIMWnJNO0HT18GUzCWCgCI0= 17 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo= 18 | github.com/aws/aws-sdk-go-v2/service/ecr v1.20.0 h1:Qw8H7V55d2P1d/a9+cLgAcdez4GtP6l30KQAeYqx9vY= 19 | github.com/aws/aws-sdk-go-v2/service/ecr v1.20.0/go.mod h1:pGwmNL8hN0jpBfKfTbmu+Rl0bJkDhaGl+9PQLrZ4KLo= 20 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI= 21 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o= 22 | github.com/aws/aws-sdk-go-v2/service/sso v1.13.6 h1:2PylFCfKCEDv6PeSN09pC/VUiRd10wi1VfHG5FrW0/g= 23 | github.com/aws/aws-sdk-go-v2/service/sso v1.13.6/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4= 24 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.6 h1:pSB560BbVj9ZlJZF4WYj5zsytWHWKxg+NgyGV4B2L58= 25 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.6/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4= 26 | github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 h1:CQBFElb0LS8RojMJlxRSo/HXipvTZW2S44Lt9Mk2aYQ= 27 | github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU= 28 | github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= 29 | github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= 30 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 31 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 33 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= 35 | github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 36 | github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= 37 | github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 38 | github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= 39 | github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 40 | github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= 41 | github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= 42 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 43 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 44 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 45 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 46 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 47 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 48 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 49 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 50 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 51 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 52 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 53 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 54 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 55 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 56 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 57 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 58 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 59 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 60 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 61 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 62 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 63 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 64 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 65 | github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= 66 | github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 67 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 68 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 69 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 70 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 71 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 72 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 73 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 74 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 75 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 76 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 77 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 78 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 79 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 80 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 81 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 82 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 83 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 84 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 85 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 86 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 87 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 88 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 89 | golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= 90 | golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 91 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 92 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 93 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 94 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 95 | golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= 96 | golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 97 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 98 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 99 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 100 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 101 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 102 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 105 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 107 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 109 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 110 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 111 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 112 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 113 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 114 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 115 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 116 | golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= 117 | golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= 118 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 119 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 120 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 121 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 122 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 123 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 124 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 125 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 126 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 127 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 128 | gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= 129 | gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= 130 | k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= 131 | k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= 132 | --------------------------------------------------------------------------------