Skip to content

Commit

Permalink
Add stats sub command to release command (#519)
Browse files Browse the repository at this point in the history
  • Loading branch information
briandowns authored Dec 17, 2024
1 parent 4e3deeb commit 1d22f36
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 2 deletions.
1 change: 1 addition & 0 deletions cmd/release/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ release tag k3s rc v1.29.2
release tag system-agent-installer-k3s rc v1.29.2
release tag k3s ga v1.29.2
release tag system-agent-installer-k3s ga v1.29.2
release stats -r rke2 -s 2024-01-01 -e 2024-12-31
```

#### Cache Permissions and Docker:
Expand Down
196 changes: 196 additions & 0 deletions cmd/release/cmd/stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package cmd

import (
"context"
"errors"
"fmt"
"os"
"sort"
"strings"
"time"

"github.com/briandowns/spinner"
"github.com/google/go-github/v39/github"
"github.com/rancher/ecm-distro-tools/repository"
"github.com/spf13/cobra"
)

var (
repo *string
startDate *string
endDate *string
)

var repoToOwner = map[string]string{
"rke2": "rancher",
"rancher": "rancher",
"k3s": "k3s-io",
}

type monthly struct {
count int
captains []string
tags []string
}

type relStats struct {
count int
monthly map[time.Month]monthly
}

// statsCmd represents the stats command
var statsCmd = &cobra.Command{
Use: "stats",
Short: "Release statistics",
Long: `Retrieve release statistics for a time period.`,
RunE: func(cmd *cobra.Command, args []string) error {
from, err := time.Parse(time.DateOnly, *startDate)
if err != nil {
return err
}

to, err := time.Parse(time.DateOnly, *endDate)
if err != nil {
return err
}

if to.Before(from) {
return errors.New("end date before start date")
}

githubToken := os.Getenv("GITHUB_TOKEN")

ctx := context.Background()
client := repository.NewGithub(ctx, githubToken)

s := spinner.New(spinner.CharSets[31], 100*time.Millisecond)
s.HideCursor = true
s.Start()

var total int
data := make(map[int]relStats)
captains := make(map[string]int)

lo := github.ListOptions{
PerPage: 100,
}
for {
releases, resp, err := client.Repositories.ListReleases(ctx, repoToOwner[*repo], *repo, &lo)
if err != nil {
return err
}

for _, release := range releases {
releaseDate := release.GetCreatedAt().Time
if releaseDate.After(from) && (releaseDate.Before(to) || releaseDate.Equal(to)) {
total++

if _, ok := data[int(release.CreatedAt.Year())]; !ok {
data[int(release.CreatedAt.Year())] = relStats{
count: 1,
monthly: map[time.Month]monthly{
release.CreatedAt.Month(): {
count: 1,
captains: []string{
*release.Author.Login,
},
tags: []string{
*release.Name,
},
},
},
}
continue
}

rs := data[int(release.CreatedAt.Year())]
rs.count++

mon := rs.monthly[release.CreatedAt.Month()]
mon.count++
mon.captains = append(mon.captains, *release.Author.Login)
mon.tags = append(mon.tags, *release.Name)

rs.monthly[release.CreatedAt.Month()] = mon

data[int(release.CreatedAt.Year())] = rs

if release.Author.Login != nil {
if _, ok := captains[*release.Author.Login]; !ok {
captains[*release.Author.Login]++
continue
}
captains[*release.Author.Login]++
}
}
}

if resp.NextPage == 0 {
break
}
lo.Page = resp.NextPage
}

s.Stop()

for year := range data {
fmt.Printf("\n%d:\n", year)

months := make([]int, 0, len(data[year].monthly))
for k := range data[year].monthly {
months = append(months, int(k))
}
sort.Ints(months)

for _, m := range months {
mon := time.Month(m)
tmp := data[year].monthly[mon]
tmp.captains = dedup(tmp.captains)
data[year].monthly[mon] = tmp
captains := strings.Join(data[year].monthly[mon].captains, ", ")
tags := strings.Join(data[year].monthly[mon].tags, ", ")
fmt.Printf(" %-9s\n Count: %3d\n Captains: %s\n Tags: %s\n",
mon, data[year].monthly[mon].count, captains, tags)
}
}

fmt.Printf("\nTotal: %d\n", total)

return nil
},
}

func dedup(slice []string) []string {
seen := make(map[string]struct{})
result := []string{}

for _, val := range slice {
if _, ok := seen[val]; !ok {
seen[val] = struct{}{}
result = append(result, val)
}
}

return result
}

func init() {
rootCmd.AddCommand(statsCmd)

repo = statsCmd.Flags().StringP("repo", "r", "", "repository")
startDate = statsCmd.Flags().StringP("start", "s", "", "start date")
endDate = statsCmd.Flags().StringP("end", "e", "", "end date")

if err := statsCmd.MarkFlagRequired("repo"); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
if err := statsCmd.MarkFlagRequired("start"); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
if err := statsCmd.MarkFlagRequired("end"); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
8 changes: 6 additions & 2 deletions cmd/release/cmd/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,28 @@ var k3sTagSubCmd = &cobra.Command{
if len(args) < 2 {
return errors.New("expected at least two arguments: [ga,rc] [version]")
}

rc, err := releaseTypePreRelease(args[0])
if err != nil {
return err
}

tag := args[1]
k3sRelease, found := rootConfig.K3s.Versions[tag]
if !found {
return errors.New("verify your config file, version not found: " + tag)
}

ctx := context.Background()
ghClient := repository.NewGithub(ctx, rootConfig.Auth.GithubToken)
opts := &repository.CreateReleaseOpts{

opts := repository.CreateReleaseOpts{
Tag: tag,
Repo: "k3s",
Owner: k3sRelease.K3sRepoOwner,
Branch: k3sRelease.ReleaseBranch,
}
return k3s.CreateRelease(ctx, ghClient, &k3sRelease, opts, rc)
return k3s.CreateRelease(ctx, ghClient, &k3sRelease, &opts, rc)
},
}

Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.27.29
github.com/aws/aws-sdk-go-v2/credentials v1.17.29
github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1
github.com/briandowns/spinner v1.23.1
github.com/go-playground/validator/v10 v10.22.0
github.com/spf13/cobra v1.8.0
github.com/urfave/cli/v2 v2.25.7
Expand Down Expand Up @@ -49,17 +50,21 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.8 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.opentelemetry.io/otel/metric v1.25.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
)

Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wI
github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0=
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650=
github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
Expand All @@ -68,6 +70,8 @@ github.com/elazarl/goproxy v0.0.0-20240618083138-03be62527ccb h1:2SoxRauy2IqekRM
github.com/elazarl/goproxy v0.0.0-20240618083138-03be62527ccb/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
Expand Down Expand Up @@ -142,6 +146,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down Expand Up @@ -242,6 +250,7 @@ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down

0 comments on commit 1d22f36

Please sign in to comment.