227 lines
5.2 KiB
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()
|
|
}
|