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
This commit is contained in:
Deepak Dahiya
2022-06-24 16:08:15 +05:30
committed by GitHub
parent 08af75f371
commit 9e752991d6
8 changed files with 188 additions and 157 deletions

View File

@@ -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
}

View File

@@ -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)
},
}

View File

@@ -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)))
}
}

View File

@@ -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.

9
go.mod
View File

@@ -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

View File

@@ -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)
}

68
service/actions_cache.go Normal file
View File

@@ -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
}

27
types/model.go Normal file
View File

@@ -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"`
}