Skip to content

Commit c918b9f

Browse files
committed
Add code
1 parent b345680 commit c918b9f

File tree

6 files changed

+340
-3
lines changed

6 files changed

+340
-3
lines changed

README.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,48 @@
11
# rke2diff
22

3-
`rke2diff` - TODO: Add tagline here
3+
`rke2diff` - Diff Rancher RKE2 releases! 🚀
4+
5+
**Features:**
6+
* Marks versions as not valid (failed to parse or invalid semver)
47

58
## Install
69

710
1. `make build`
11+
2. `make install`
812

913
## Usage
1014

1115
Example:
1216
```
13-
TODO: Add example here
17+
rke2diff -rke2 v1.28.9+rke2r1 -rke2 v1.29.5+rke2r1
18+
19+
┌──────────────────────────────────┬─────────────────────────────┬─────────────────────────────┐
20+
│ NAME │ V1.28.9+RKE2R1 (2024-04-29) │ V1.29.5+RKE2R1 (2024-05-22) │
21+
├──────────────────────────────────┼─────────────────────────────┼─────────────────────────────┤
22+
│ Containerd │ v1.7.11-k3s2 │ v1.7.11-k3s2 │
23+
│ CoreDNS │ v1.11.1 │ v1.11.1 │
24+
│ Etcd │ v3.5.9-k3s1 │ v3.5.9-k3s1 │
25+
│ Helm-controller │ v0.15.9 │ v0.15.9 │
26+
│ Ingress-Nginx │ nginx-1.9.6-hardened1 ❌ │ nginx-1.9.6-hardened1 ❌ │
27+
│ Kubernetes │ v1.28.9 │ v1.29.5 🚀 │
28+
│ Metrics-server │ v0.7.1 │ v0.7.1 │
29+
│ Runc │ v1.1.12 │ v1.1. ❌ │
30+
│ harvester-cloud-provider │ 0.2.300 │ 0.2.300 │
31+
│ harvester-csi-driver │ 0.1.1700 │ 0.1.1700 │
32+
│ rancher-vsphere-cpi │ 1.7.001 │ 1.7.001 │
33+
│ rancher-vsphere-csi │ 3.1.2-rancher400 │ 3.1.2-rancher400 │
34+
│ rke2-calico │ v3.27.300 │ v3.27.300 │
35+
│ rke2-calico-crd │ v3.27.002 │ v3.27.002 │
36+
│ rke2-canal │ v3.27.3-build2024042301 │ v3.27.3-build2024042301 │
37+
│ rke2-cilium │ 1.15.400 │ 1.15.500 🚀 │
38+
│ rke2-coredns │ 1.29.002 │ 1.29.002 │
39+
│ rke2-ingress-nginx │ 4.9.100 │ 4.9.100 │
40+
│ rke2-metrics-server │ 3.12.002 │ 3.12.002 │
41+
│ rke2-snapshot-controller │ 1.7.202 │ 1.7.202 │
42+
│ rke2-snapshot-controller-crd │ 1.7.202 │ 1.7.202 │
43+
│ rke2-snapshot-validation-webhook │ 1.7.302 │ 1.7.302 │
44+
├──────────────────────────────────┼─────────────────────────────┼─────────────────────────────┤
45+
│ ❌ = VERSION STRING INVALID │ │ │
46+
│ 🚀 = BUMPED │ │ │
47+
└──────────────────────────────────┴─────────────────────────────┴─────────────────────────────┘
1448
```

build/rke2diff

6.16 MB
Binary file not shown.

cmd/rke2diff/main.go

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,201 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
56
"fmt"
7+
"log"
68
"os"
9+
"sort"
710

11+
"github.com/google/go-github/v62/github"
12+
gversion "github.com/hashicorp/go-version"
13+
"github.com/jedib0t/go-pretty/v6/table"
14+
"github.com/jedib0t/go-pretty/v6/text"
815
"github.com/mikejoh/rke2diff/internal/buildinfo"
916
)
1017

1118
type rke2diffOptions struct {
12-
version bool
19+
version bool
20+
rke2Versions rkeVersionSlice
21+
showReleases bool
1322
}
1423

24+
type GitHubProject struct {
25+
Repo string
26+
Owner string
27+
Releases []*github.RepositoryRelease
28+
}
29+
30+
type RKE2Release struct {
31+
Version string
32+
Components []Component
33+
}
34+
35+
type rkeVersionSlice []string
36+
37+
func (s *rkeVersionSlice) String() string {
38+
return fmt.Sprintf("%v", *s)
39+
}
40+
41+
func (s *rkeVersionSlice) Set(value string) error {
42+
*s = append(*s, value)
43+
return nil
44+
}
45+
46+
// Icon constants.
47+
const (
48+
versionParseError = "❌"
49+
bumped = "🚀"
50+
)
51+
1552
func main() {
1653
var rke2diffOpts rke2diffOptions
1754
flag.BoolVar(&rke2diffOpts.version, "version", false, "Print the version number.")
55+
flag.BoolVar(&rke2diffOpts.showReleases, "show-releases", false, "Show all releases.")
56+
flag.Var(&rke2diffOpts.rke2Versions, "rke2", "RKE2 version to compare.")
1857
flag.Parse()
1958

2059
if rke2diffOpts.version {
2160
fmt.Println(buildinfo.Get())
2261
os.Exit(0)
2362
}
63+
64+
if len(rke2diffOpts.rke2Versions) == 0 {
65+
// TODO: Just output the latest release here?
66+
log.Fatal("one RKE2 version is required")
67+
} else if len(rke2diffOpts.rke2Versions) > 2 {
68+
log.Fatal("only two RKE2 versions can be compared")
69+
}
70+
71+
ghClient := github.NewClient(nil)
72+
73+
project := GitHubProject{
74+
Owner: "rancher",
75+
Repo: "rke2",
76+
Releases: []*github.RepositoryRelease{},
77+
}
78+
79+
ctx := context.Background()
80+
81+
// TODO: Use response to tell user how many GH API calls can be done before hitting the rate limit
82+
releases, _, err := ghClient.Repositories.ListReleases(ctx, project.Owner, project.Repo, &github.ListOptions{
83+
PerPage: 1000,
84+
})
85+
if err != nil {
86+
log.Fatal(err)
87+
}
88+
89+
project.Releases = releases
90+
91+
for _, rke2Version := range rke2diffOpts.rke2Versions {
92+
release := findRelease(project.Releases, rke2Version)
93+
if release == nil {
94+
log.Fatalf("release %s not found", rke2Version)
95+
}
96+
}
97+
98+
t := table.NewWriter()
99+
t.SetOutputMirror(os.Stdout)
100+
t.SetStyle(table.StyleLight)
101+
header := table.Row{
102+
"Name",
103+
}
104+
for _, rke2Version := range rke2diffOpts.rke2Versions {
105+
r := getRelease(project.Releases, rke2Version)
106+
header = append(header, fmt.Sprintf("%s (%s)", rke2Version, r.GetPublishedAt().Format("2006-01-02")))
107+
}
108+
t.AppendHeader(header)
109+
t.AppendFooter(table.Row{
110+
fmt.Sprintf("%s = %s\n%s = %s", versionParseError, "Version string invalid", bumped, "Bumped"),
111+
})
112+
t.Style().Title.Align = text.AlignCenter
113+
114+
rows := make(map[string]table.Row)
115+
116+
componentVersionDiffs := make(map[string]map[string]string)
117+
118+
// TODO: Compare version.Version instead of string to get proper version comparison
119+
for _, rke2Version := range rke2diffOpts.rke2Versions {
120+
componentVersionDiffs[rke2Version] = make(map[string]string)
121+
for _, release := range project.Releases {
122+
if !(release.GetTagName() == rke2Version) {
123+
continue
124+
}
125+
126+
htmlBytes := mdToHTML([]byte(release.GetBody()))
127+
components := parseHTMLTable(string(htmlBytes), "h2", "charts-versions")
128+
packagedComponents := parseHTMLTable(string(htmlBytes), "h2", "packaged-component-versions")
129+
130+
if len(components) == 0 {
131+
log.Fatalf("no components found in release %s", release.GetTagName())
132+
}
133+
134+
if len(packagedComponents) == 0 {
135+
log.Fatalf("no packaged components found in release %s", release.GetTagName())
136+
}
137+
138+
rke2Release := RKE2Release{
139+
Version: release.GetTagName(),
140+
Components: append(packagedComponents, components...),
141+
}
142+
143+
for _, component := range rke2Release.Components {
144+
var componentVersion string
145+
146+
if _, ok := rows[component.Name]; !ok {
147+
rows[component.Name] = table.Row{
148+
component.Name,
149+
}
150+
}
151+
152+
if _, ok := componentVersionDiffs[rke2Version][component.Name]; !ok {
153+
componentVersionDiffs[rke2Version][component.Name] = component.Version
154+
}
155+
156+
if componentVersionDiffs[rke2diffOpts.rke2Versions[0]][component.Name] != component.Version {
157+
componentVersion = fmt.Sprintf("%s %s", component.Version, bumped)
158+
} else {
159+
componentVersion = component.Version
160+
}
161+
162+
_, err := gversion.NewVersion(component.Version)
163+
if err != nil {
164+
componentVersion = fmt.Sprintf("%s %s", component.Version, versionParseError)
165+
}
166+
167+
rows[component.Name] = append(rows[component.Name], componentVersion)
168+
}
169+
}
170+
}
171+
172+
keys := make([]string, 0, len(rows))
173+
for k := range rows {
174+
keys = append(keys, k)
175+
}
176+
sort.Strings(keys)
177+
178+
for _, k := range keys {
179+
t.AppendRow(rows[k])
180+
}
181+
182+
t.Render()
183+
}
184+
185+
func findRelease(releases []*github.RepositoryRelease, version string) *github.RepositoryRelease {
186+
for _, release := range releases {
187+
if release.GetTagName() == version {
188+
return release
189+
}
190+
}
191+
return nil
192+
}
193+
194+
func getRelease(releases []*github.RepositoryRelease, version string) *github.RepositoryRelease {
195+
for _, release := range releases {
196+
if release.GetTagName() == version {
197+
return release
198+
}
199+
}
200+
return nil
24201
}

cmd/rke2diff/parse.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"strings"
6+
7+
"github.com/gomarkdown/markdown"
8+
mdhtml "github.com/gomarkdown/markdown/html"
9+
"github.com/gomarkdown/markdown/parser"
10+
"golang.org/x/net/html"
11+
)
12+
13+
type Component struct {
14+
Name string
15+
Version string
16+
URL string
17+
}
18+
19+
func mdToHTML(md []byte) []byte {
20+
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
21+
p := parser.NewWithExtensions(extensions)
22+
doc := p.Parse(md)
23+
24+
htmlFlags := mdhtml.CommonFlags | mdhtml.HrefTargetBlank
25+
opts := mdhtml.RendererOptions{Flags: htmlFlags}
26+
renderer := mdhtml.NewRenderer(opts)
27+
28+
return markdown.Render(doc, renderer)
29+
}
30+
31+
func parseHTMLTable(htmlStr string, startElement string, id string) []Component {
32+
doc, err := html.Parse(strings.NewReader(htmlStr))
33+
if err != nil {
34+
log.Fatal(err)
35+
}
36+
37+
var f func(*html.Node)
38+
var chartVersions []Component
39+
var parseTable bool
40+
41+
f = func(n *html.Node) {
42+
if n.Type == html.ElementNode && n.Data == startElement {
43+
for _, a := range n.Attr {
44+
if a.Key == "id" && a.Val == id {
45+
parseTable = true
46+
}
47+
}
48+
}
49+
50+
if parseTable && n.Type == html.ElementNode && n.Data == "tr" {
51+
var chartVersion Component
52+
tdCount := 0
53+
for c := n.FirstChild; c != nil; c = c.NextSibling {
54+
if c.Type == html.ElementNode && c.Data == "td" {
55+
if tdCount == 0 {
56+
chartVersion.Name = c.FirstChild.Data
57+
} else if tdCount == 1 {
58+
chartVersion.Version = c.FirstChild.FirstChild.Data
59+
chartVersion.URL = c.FirstChild.Attr[0].Val
60+
}
61+
tdCount++
62+
}
63+
}
64+
if tdCount > 0 {
65+
chartVersions = append(chartVersions, chartVersion)
66+
}
67+
}
68+
69+
if n.Type == html.ElementNode && n.Data == "table" && parseTable && len(chartVersions) > 0 {
70+
parseTable = false
71+
}
72+
73+
for c := n.FirstChild; c != nil; c = c.NextSibling {
74+
f(c)
75+
}
76+
}
77+
f(doc)
78+
79+
return chartVersions
80+
}

go.mod

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
11
module github.com/mikejoh/rke2diff
22

33
go 1.22.3
4+
5+
require (
6+
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2
7+
github.com/google/go-github/v62 v62.0.0
8+
github.com/hashicorp/go-version v1.7.0
9+
github.com/jedib0t/go-pretty/v6 v6.5.9
10+
golang.org/x/net v0.26.0
11+
)
12+
13+
require (
14+
github.com/google/go-querystring v1.1.0 // indirect
15+
github.com/mattn/go-runewidth v0.0.15 // indirect
16+
github.com/rivo/uniseg v0.4.7 // indirect
17+
golang.org/x/sys v0.21.0 // indirect
18+
)

go.sum

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 h1:yEt5djSYb4iNtmV9iJGVday+i4e9u6Mrn5iP64HH5QM=
4+
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
5+
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
6+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
7+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
8+
github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4=
9+
github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4=
10+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
11+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
12+
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
13+
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
14+
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
15+
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
16+
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
17+
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
18+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
19+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
20+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
21+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
22+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
23+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
24+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
25+
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
26+
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
27+
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
28+
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
29+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
30+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
31+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)