RF-Swift/go/rfswift/dock/dockerhub.go

227 lines
5.2 KiB
Go

package dock
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"runtime"
"sort"
"strings"
"time"
"golang.org/x/crypto/ssh/terminal"
rfutils "penthertz/rfswift/rfutils"
)
type Tag struct {
Name string `json:"name"`
Images []Image `json:"images"`
TagLastPushed time.Time `json:"tag_last_pushed"`
}
type Image struct {
Architecture string `json:"architecture"`
Digest string `json:"digest"`
}
type TagList struct {
Results []Tag `json:"results"`
}
func getArchitecture() string {
switch runtime.GOARCH {
case "amd64":
return "amd64"
case "arm64":
return "arm64"
case "riscv64":
return "riscv64"
case "arm":
return "arm"
default:
return ""
}
}
func getRemoteImageCreationDate(repo, tag, architecture string) (time.Time, error) {
url := fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags/?page_size=100", repo)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(url)
if err != nil {
return time.Time{}, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return time.Time{}, fmt.Errorf("tag not found")
} else if resp.StatusCode != http.StatusOK {
return time.Time{}, fmt.Errorf("failed to get tags: %s", resp.Status)
}
var tagList TagList
if err := json.NewDecoder(resp.Body).Decode(&tagList); err != nil {
return time.Time{}, err
}
for _, t := range tagList.Results {
if t.Name == tag {
for _, image := range t.Images {
if image.Architecture == architecture {
return t.TagLastPushed, nil
}
}
}
}
return time.Time{}, fmt.Errorf("tag not found")
}
func getLatestDockerHubTags(repo string, architecture string) ([]Tag, error) {
url := fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags/?page_size=100", repo)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get tags: %s", resp.Status)
}
var tagList TagList
if err := json.NewDecoder(resp.Body).Decode(&tagList); err != nil {
return nil, err
}
var filteredTags []Tag
for _, tag := range tagList.Results {
for _, image := range tag.Images {
if image.Architecture == architecture {
filteredTags = append(filteredTags, tag)
break
}
}
}
// Sort tags by pushed date from latest to oldest
sort.Slice(filteredTags, func(i, j int) bool {
return filteredTags[i].TagLastPushed.After(filteredTags[j].TagLastPushed)
})
// Remove duplicate tags, keeping only the latest
uniqueTags := make(map[string]Tag)
for _, tag := range filteredTags {
if _, exists := uniqueTags[tag.Name]; !exists {
uniqueTags[tag.Name] = tag
}
}
// Convert map to slice
var latestTags []Tag
for _, tag := range uniqueTags {
latestTags = append(latestTags, tag)
}
// Sort the tags again to ensure they are in the correct order after deduplication
sort.Slice(latestTags, func(i, j int) bool {
return latestTags[i].TagLastPushed.After(latestTags[j].TagLastPushed)
})
return latestTags, nil
}
func ListDockerImagesRepo() {
repo := "penthertz/rfswift"
architecture := getArchitecture()
if architecture == "" {
log.Fatalf("Unsupported architecture: %s", runtime.GOARCH)
}
tags, err := getLatestDockerHubTags(repo, architecture)
if err != nil {
log.Fatalf("Error getting tags: %v", err)
}
rfutils.ClearScreen()
headers := []string{"Tag", "Pushed Date", "Image", "Architecture"}
tableData := [][]string{}
for _, tag := range tags {
for _, image := range tag.Images {
if image.Architecture == architecture {
tableData = append(tableData, []string{
tag.Name,
tag.TagLastPushed.Format(time.RFC3339),
fmt.Sprintf("%s:%s", repo, tag.Name),
image.Architecture,
})
break
}
}
}
width, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
width = 80 // default width if terminal size cannot be determined
}
columnWidths := make([]int, len(headers))
for i, header := range headers {
columnWidths[i] = len(header)
}
for _, row := range tableData {
for i, col := range row {
if len(col) > columnWidths[i] {
columnWidths[i] = len(col)
}
}
}
// Adjust column widths to fit the terminal width
totalWidth := len(headers) + 1 // Adding 1 for the left border
for _, w := range columnWidths {
totalWidth += w + 2 // Adding 2 for padding
}
if totalWidth > width {
excess := totalWidth - width
for i := range columnWidths {
reduction := excess / len(columnWidths)
if columnWidths[i] > reduction {
columnWidths[i] -= reduction
excess -= reduction
}
}
totalWidth = width
}
blue := "\033[34m"
white := "\033[37m"
reset := "\033[0m"
title := "💿 Official Images"
fmt.Printf("%s%s%s%s%s\n", blue, strings.Repeat(" ", 2), title, strings.Repeat(" ", totalWidth-2-len(title)), reset)
fmt.Print(white)
printHorizontalBorder(columnWidths, "┌", "┬", "┐")
printRow(headers, columnWidths, "│")
printHorizontalBorder(columnWidths, "├", "┼", "┤")
for i, row := range tableData {
printRow(row, columnWidths, "│")
if i < len(tableData)-1 {
printHorizontalBorder(columnWidths, "├", "┼", "┤")
}
}
printHorizontalBorder(columnWidths, "└", "┴", "┘")
fmt.Print(reset)
fmt.Println()
}