From 9e752991d6e17ff06b64af10b193007e629a7878 Mon Sep 17 00:00:00 2001 From: Deepak Dahiya <59823596+t-dedah@users.noreply.github.com> Date: Fri, 24 Jun 2022 16:08:15 +0530 Subject: [PATCH] Refactorization (#5) * Completed List cmd and added API calls * Minor comments and add delete code to pass linting * Typo in descriptions * Minor comments * Validations * Validations-1 * improved branch flag validation * removed build * working after refactory with bad names * Command working, test not working * Corrected creation of service * Finalized structure using service * Deleted tests * cleanup * cleanup * cleanup * removed space with tab * aligned types in model.go * Update model.go * resolved comments * Refactor * removed long descriptions * Added page --- cmd/client.go | 88 ------------------------------ cmd/delete.go | 20 +++---- cmd/list.go | 109 ++++++++++++++++++++----------------- cmd/root.go | 7 ++- go.mod | 9 ++- {cmd => internal}/utils.go | 17 +++--- service/actions_cache.go | 68 +++++++++++++++++++++++ types/model.go | 27 +++++++++ 8 files changed, 188 insertions(+), 157 deletions(-) delete mode 100644 cmd/client.go rename {cmd => internal}/utils.go (72%) create mode 100644 service/actions_cache.go create mode 100644 types/model.go diff --git a/cmd/client.go b/cmd/client.go deleted file mode 100644 index 51c8000..0000000 --- a/cmd/client.go +++ /dev/null @@ -1,88 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "net/url" - - gh "github.com/cli/go-gh" - "github.com/cli/go-gh/pkg/api" - ghRepo "github.com/cli/go-gh/pkg/repository" -) - -type cacheInfo struct { - Key string - Ref string - LastAccessedAt string - Size float64 -} - -func getCacheUsage(repo ghRepo.Repository) float64 { - client, err := getRestClient(repo) - if err != nil { - log.Fatal(err) - } - pathComponent := fmt.Sprintf("repos/%s/%s/actions/cache/usage", repo.Owner(), repo.Name()) - var apiResults map[string]interface{} - err = client.Get(pathComponent, &apiResults) - if err != nil { - log.Fatal(err) - } - - cacheSizeResult := apiResults["active_caches_size_in_bytes"].(float64) - return cacheSizeResult -} - -func listCaches(repo ghRepo.Repository, queryParams url.Values) []cacheInfo { - client, err := getRestClient(repo) - if err != nil { - log.Fatal(err) - } - pathComponent := fmt.Sprintf("repos/%s/%s/actions/caches", repo.Owner(), repo.Name()) - var apiResults map[string]interface{} - err = client.Get(pathComponent+"?"+queryParams.Encode(), &apiResults) - if err != nil { - log.Fatal(err) - } - - actionsCachesResult := apiResults["actions_caches"].([]interface{}) - - var caches []cacheInfo - for _, item := range actionsCachesResult { - caches = append(caches, cacheInfo{ - Key: item.(map[string]interface{})["key"].(string), - Ref: item.(map[string]interface{})["ref"].(string), - LastAccessedAt: item.(map[string]interface{})["last_accessed_at"].(string), - Size: item.(map[string]interface{})["size_in_bytes"].(float64), - }) - } - return caches -} - -func deleteCaches(repo ghRepo.Repository, queryParams url.Values) float64 { - client, err := getRestClient(repo) - if err != nil { - log.Fatal(err) - } - pathComponent := fmt.Sprintf("repos/%s/%s/actions/caches", repo.Owner(), repo.Name()) - var apiResults map[string]interface{} - err = client.Delete(pathComponent+"?"+queryParams.Encode(), &apiResults) - if err != nil { - log.Fatal(err) - } - - totalDeletedCachesResult := apiResults["total_count"].(float64) - return totalDeletedCachesResult -} - -func getRestClient(repo ghRepo.Repository) (api.RESTClient, error) { - opts := api.ClientOptions{ - Host: repo.Host(), - Headers: map[string]string{"User-Agent": fmt.Sprintf("gh-actions-cache/%s/%s", VERSION, COMMAND) }, - } - client, err := gh.RESTClient(&opts) - if err != nil { - return nil, err - } - return client, nil -} diff --git a/cmd/delete.go b/cmd/delete.go index f428536..0de064d 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -1,9 +1,9 @@ package cmd import ( - "log" - "github.com/spf13/cobra" + // "github.com/actions/gh-actions-cache/internal" + // "github.com/actions/gh-actions-cache/client" ) func init() { @@ -19,16 +19,16 @@ var deleteCmd = &cobra.Command{ Long: `Delete cache by key`, Run: func(cmd *cobra.Command, args []string) { COMMAND = "delete" - r, _ := cmd.Flags().GetString("repo") - branch, _ := cmd.Flags().GetString("branch") + // r, _ := cmd.Flags().GetString("repo") + // branch, _ := cmd.Flags().GetString("branch") - repo, err := getRepo(r) - if err != nil { - log.Fatal(err) - } + // repo, err := getRepo(r) + // if err != nil { + // log.Fatal(err) + // } - queryParams := generateQueryParams(branch, 30, "", "", "") - deleteCaches(repo, queryParams) + // queryParams := generateQueryParams(branch, 30, "", "", "") + // deleteCaches(repo, queryParams) }, } diff --git a/cmd/list.go b/cmd/list.go index 48f2862..ea729d6 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -4,60 +4,71 @@ import ( "fmt" "log" + "github.com/actions/gh-actions-cache/internal" + "github.com/actions/gh-actions-cache/service" "github.com/spf13/cobra" ) -func init() { - rootCmd.AddCommand(listCmd) - listCmd.Flags().StringP("repo", "R", "", "Select another repository for finding actions cache.") - listCmd.Flags().StringP("branch", "B", "", "Filter by branch") - listCmd.Flags().IntP("limit", "", 30, "Maximum number of items to fetch (default is 30, max limit is 100)") - listCmd.Flags().StringP("key", "", "", "Filter by key") - listCmd.Flags().StringP("order", "", "", "Order of caches returned (asc/desc)") - listCmd.Flags().StringP("sort", "", "", "Sort fetched caches (last-used/size/created-at)") - listCmd.SetHelpTemplate(getListHelp()) +type InputFlags struct { + repo string + branch string + limit int + key string + order string + sort string } -var listCmd = &cobra.Command{ - Use: "list", - Short: "Lists the actions cache", - Long: `Lists the actions cache`, - Run: func(cmd *cobra.Command, args []string) { - COMMAND = "list" +func NewCmdList() *cobra.Command { + COMMAND = "list" - if len(args) != 0 { - fmt.Printf("Invalid argument(s). Expected 0 received %d\n", len(args)) - fmt.Println(getListHelp()) - return - } + f := InputFlags{} - r, _ := cmd.Flags().GetString("repo") - branch, _ := cmd.Flags().GetString("branch") - limit, _ := cmd.Flags().GetInt("limit") - key, _ := cmd.Flags().GetString("key") - order, _ := cmd.Flags().GetString("order") - sort, _ := cmd.Flags().GetString("sort") + var listCmd = &cobra.Command { + Use: "list", + Short: "Lists the actions cache", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 0 { + fmt.Printf("Invalid argument(s). Expected 0 received %d\n", len(args)) + fmt.Println(getListHelp()) + return + } - repo, err := getRepo(r) - if err != nil { - log.Fatal(err) - } + repo, err := internal.GetRepo(f.repo) + if err != nil { + log.Fatal(err) + } - validateInputs(sort, order, limit) + validateInputs(f) - if branch == "" && key == "" { - totalCacheSize := getCacheUsage(repo) - fmt.Printf("Total caches size %s\n\n", formatCacheSize(totalCacheSize)) - } + artifactCache := service.NewArtifactCache(repo, COMMAND, VERSION) - queryParams := generateQueryParams(branch, limit, key, order, sort) - caches := listCaches(repo, queryParams) + if f.branch == "" && f.key == "" { + totalCacheSize := artifactCache.GetCacheUsage() + fmt.Printf("Total caches size %s\n\n", internal.FormatCacheSize(totalCacheSize)) + } - fmt.Printf("Showing %d of %d cache entries in %s/%s\n\n", displayedEntriesCount(len(caches), limit), len(caches), repo.Owner(), repo.Name()) - for _, cache := range caches { - fmt.Printf("%s\t [%s]\t %s\t %s\n", cache.Key, formatCacheSize(cache.Size), cache.Ref, cache.LastAccessedAt) - } - }, + queryParams := internal.GenerateQueryParams(f.branch, f.limit, f.key, f.order, f.sort, 1) + listCacheResponse := artifactCache.ListCaches(queryParams) + + totalCaches := listCacheResponse.TotalCount + caches := listCacheResponse.ActionsCaches + + fmt.Printf("Showing %d of %d cache entries in %s/%s\n\n", displayedEntriesCount(len(caches), f.limit), totalCaches, repo.Owner(), repo.Name()) + for _, cache := range caches { + fmt.Printf("%s\t [%s]\t %s\t %s\n", cache.Key, internal.FormatCacheSize(cache.SizeInBytes), cache.Ref, cache.LastAccessedAt) + } + }, + } + + listCmd.Flags().StringVarP(&f.repo, "repo", "R", "", "Select another repository for finding actions cache.") + listCmd.Flags().StringVarP(&f.branch, "branch", "B", "", "Filter by branch") + listCmd.Flags().IntVarP(&f.limit, "limit", "", 30, "Maximum number of items to fetch (default is 30, max limit is 100)") + listCmd.Flags().StringVarP(&f.key, "key", "", "", "Filter by key") + listCmd.Flags().StringVarP(&f.order, "order", "", "", "Order of caches returned (asc/desc)") + listCmd.Flags().StringVarP(&f.sort, "sort", "", "", "Sort fetched caches (last-used/size/created-at)") + listCmd.SetHelpTemplate(getListHelp()) + + return listCmd } func displayedEntriesCount(totalCaches int, limit int) int { @@ -67,17 +78,17 @@ func displayedEntriesCount(totalCaches int, limit int) int { return limit } -func validateInputs(sort string, order string, limit int){ - if order != "" && order != "asc" && order != "desc"{ - log.Fatal(fmt.Errorf(fmt.Sprintf("%s is not a valid value for order flag. Allowed values: asc/desc", order))) +func validateInputs(input InputFlags) { + if input.order != "" && input.order != "asc" && input.order != "desc" { + log.Fatal(fmt.Errorf(fmt.Sprintf("%s is not a valid value for order flag. Allowed values: asc/desc", input.order))) } - if sort != "" && sort != "last-used" && sort != "size" && sort != "created-at"{ - log.Fatal(fmt.Errorf(fmt.Sprintf("%s is not a valid value for sort flag. Allowed values: last-used/size/created-at", sort))) + if input.sort != "" && input.sort != "last-used" && input.sort != "size" && input.sort != "created-at" { + log.Fatal(fmt.Errorf(fmt.Sprintf("%s is not a valid value for sort flag. Allowed values: last-used/size/created-at", input.sort))) } - if limit < 1{ - log.Fatal(fmt.Errorf(fmt.Sprintf("%d is not a valid value for limit flag. Allowed values: > 1", limit))) + if input.limit < 1 || input.limit > 100 { + log.Fatal(fmt.Errorf(fmt.Sprintf("%d is not a valid value for limit flag. Allowed values: 1-100", input.limit))) } } diff --git a/cmd/root.go b/cmd/root.go index bbb8736..4eddf0a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,15 +7,16 @@ import ( ) const VERSION = "0.0.1" + var COMMAND string = "" var rootCmd = &cobra.Command{ Use: "gh-actions-cache", Short: "Works with GitHub Actions Cache. ", - Long: `Works with GitHub Actions Cache.`, } func Execute() { + addCommandsToRoot() err := rootCmd.Execute() if err != nil { os.Exit(1) @@ -26,6 +27,10 @@ func init() { rootCmd.SetHelpTemplate(getRootHelp()) } +func addCommandsToRoot() { + rootCmd.AddCommand(NewCmdList()) +} + func getRootHelp() string { return ` gh-actions-cache: Works with GitHub Actions Cache. diff --git a/go.mod b/go.mod index bcc2aad..e3f554f 100644 --- a/go.mod +++ b/go.mod @@ -2,14 +2,19 @@ module github.com/actions/gh-actions-cache go 1.18 -require github.com/cli/go-gh v0.0.3 +require ( + github.com/cli/go-gh v0.0.3 + github.com/spf13/cobra v1.4.0 + github.com/stretchr/testify v1.7.0 +) require ( github.com/cli/safeexec v1.0.0 // indirect github.com/cli/shurcooL-graphql v0.0.1 // indirect + github.com/davecgh/go-spew v1.1.0 // indirect github.com/henvic/httpretty v0.0.6 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/spf13/cobra v1.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/cmd/utils.go b/internal/utils.go similarity index 72% rename from cmd/utils.go rename to internal/utils.go index fdc0b61..e6769a0 100644 --- a/cmd/utils.go +++ b/internal/utils.go @@ -1,4 +1,4 @@ -package cmd +package internal import ( "fmt" @@ -15,14 +15,14 @@ const GB_IN_BYTES = 1024 * 1024 * 1024 var SORT_INPUT_TO_QUERY_MAP = map[string]string{ "created-at": "created_at", - "last-used": "last_accessed_at", - "size": "size_in_bytes", + "last-used": "last_accessed_at", + "size": "size_in_bytes", } -func generateQueryParams(branch string, limit int, key string, order string, sort string) url.Values { +func GenerateQueryParams(branch string, limit int, key string, order string, sort string, page int) url.Values { query := url.Values{} if branch != "" { - if strings.HasPrefix(branch, "refs/"){ + if strings.HasPrefix(branch, "refs/") { query.Add("ref", branch) } else { query.Add("ref", fmt.Sprintf("refs/heads/%s", branch)) @@ -40,11 +40,14 @@ func generateQueryParams(branch string, limit int, key string, order string, sor if sort != "" { query.Add("sort", SORT_INPUT_TO_QUERY_MAP[sort]) } + if page > 1 { + query.Add("page", strconv.Itoa(page)) + } return query } -func getRepo(r string) (ghRepo.Repository, error) { +func GetRepo(r string) (ghRepo.Repository, error) { if r != "" { return ghRepo.Parse(r) } @@ -52,7 +55,7 @@ func getRepo(r string) (ghRepo.Repository, error) { return gh.CurrentRepository() } -func formatCacheSize(size_in_bytes float64) string { +func FormatCacheSize(size_in_bytes float64) string { if size_in_bytes < 1024 { return fmt.Sprintf("%.2f B", size_in_bytes) } diff --git a/service/actions_cache.go b/service/actions_cache.go new file mode 100644 index 0000000..a6f3aed --- /dev/null +++ b/service/actions_cache.go @@ -0,0 +1,68 @@ +package service + +import ( + "fmt" + "log" + "net/url" + + "github.com/actions/gh-actions-cache/types" + gh "github.com/cli/go-gh" + "github.com/cli/go-gh/pkg/api" + ghRepo "github.com/cli/go-gh/pkg/repository" +) + +type ArtifactCacheService interface { + GetCacheUsage() float64 + ListCaches(queryParams url.Values) types.ListApiResponse + DeleteCaches(queryParams url.Values) int +} + +type ArtifactCache struct { + HttpClient api.RESTClient + repo ghRepo.Repository +} + +func NewArtifactCache(repo ghRepo.Repository, command string, version string) ArtifactCacheService { + opts := api.ClientOptions{ + Host: repo.Host(), + Headers: map[string]string{"User-Agent": fmt.Sprintf("gh-actions-cache/%s/%s", version, command)}, + } + restClient, err := gh.RESTClient(&opts) + if err != nil { + log.Fatal(err) + } + return &ArtifactCache{HttpClient: restClient, repo: repo} +} + +func (a *ArtifactCache) GetCacheUsage() float64 { + pathComponent := fmt.Sprintf("repos/%s/%s/actions/cache/usage", a.repo.Owner(), a.repo.Name()) + var apiResults types.RepoLevelUsageApiResponse + err := a.HttpClient.Get(pathComponent, &apiResults) + if err != nil { + log.Fatal(err) + } + + return apiResults.ActiveCacheSizeInBytes +} + +func (a *ArtifactCache) ListCaches(queryParams url.Values) types.ListApiResponse { + pathComponent := fmt.Sprintf("repos/%s/%s/actions/caches", a.repo.Owner(), a.repo.Name()) + var apiResults types.ListApiResponse + err := a.HttpClient.Get(pathComponent+"?"+queryParams.Encode(), &apiResults) + if err != nil { + log.Fatal(err) + } + + return apiResults +} + +func (a *ArtifactCache) DeleteCaches(queryParams url.Values) int { + pathComponent := fmt.Sprintf("repos/%s/%s/actions/caches", a.repo.Owner(), a.repo.Name()) + var apiResults types.DeleteApiResponse + err := a.HttpClient.Delete(pathComponent+"?"+queryParams.Encode(), &apiResults) + if err != nil { + log.Fatal(err) + } + + return apiResults.TotalCount +} diff --git a/types/model.go b/types/model.go new file mode 100644 index 0000000..653a8f5 --- /dev/null +++ b/types/model.go @@ -0,0 +1,27 @@ +package types + +type RepoLevelUsageApiResponse struct { + FullName string `json:"full_name"` + ActiveCacheSizeInBytes float64 `json:"active_caches_size_in_bytes"` + ActiveCacheCount float64 `json:"active_caches_count"` +} + +type ListApiResponse struct { + TotalCount int `json:"total_count"` + ActionsCaches []ActionsCache `json:"actions_caches"` +} + +type DeleteApiResponse struct { + TotalCount int `json:"total_count"` + ActionsCaches []ActionsCache `json:"actions_caches"` +} + +type ActionsCache struct { + Id int `json:"id"` + Ref string `json:"ref"` + Key string `json:"key"` + Version string `json:"version"` + LastAccessedAt string `json:"last_accessed_at"` + CreatedAt string `json:"created_at"` + SizeInBytes float64 `json:"size_in_bytes"` +}