package main import ( "encoding/base64" "encoding/json" "fmt" "github.com/joho/godotenv" "net/http" "os" "os/exec" "strings" ) var ( registryName string registryURL string registryUsername string registryPassword string ) func main() { // Load .env file err := godotenv.Load() if err != nil { fmt.Println("Error loading .env file:", err) return } // Read values from environment registryName = os.Getenv("REGISTRY_NAME") if registryName == "" { fmt.Println("REGISTRY_NAME not found in .env") return } registryURL = "https://" + registryName registryUsername = os.Getenv("REGISTRY_USERNAME") registryPassword = os.Getenv("REGISTRY_PASSWORD") if registryUsername == "" || registryPassword == "" { fmt.Println("REGISTRY_USERNAME or REGISTRY_PASSWORD not found in .env") return } if len(os.Args) < 2 { fmt.Println("Invalid command. Usage: registry [images|tags|rm|push|tag] ") return } switch os.Args[1] { case "images": registryImages() case "tags": if len(os.Args) < 3 { fmt.Println("Error: Please provide an image name.") return } registryTags(os.Args[2]) case "rm": if len(os.Args) < 3 { fmt.Println("Error: Please provide an image name and optionally a tag (e.g., my-image or my-image:latest).") return } registryRm(os.Args[2]) case "push": if len(os.Args) < 3 { fmt.Println("Error: Please provide an image name to push.") return } registryPush(os.Args[2]) case "tag": if len(os.Args) < 4 { fmt.Println("Error: Please provide both source and target image names.") return } registryTag(os.Args[2], os.Args[3]) default: fmt.Println("Invalid command. Usage: registry [images|tags|rm|push|tag] ") } } func registryImages() { type catalog struct { Repositories []string `json:"repositories"` } resp, err := http.Get(registryURL + "/v2/_catalog") if err != nil { fmt.Println("Error fetching images:", err) return } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { fmt.Println("No images found in the registry.") return } var cat catalog if err := json.NewDecoder(resp.Body).Decode(&cat); err != nil { fmt.Println("Error decoding response:", err) return } if len(cat.Repositories) == 0 { fmt.Println("No images found in the registry.") } else { for _, img := range cat.Repositories { fmt.Println(img) } } } func registryTags(imageName string) { type tagList struct { Tags []string `json:"tags"` } resp, err := http.Get(fmt.Sprintf("%s/v2/%s/tags/list", registryURL, imageName)) if err != nil { fmt.Println("Error fetching tags:", err) return } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { fmt.Printf("Error: Image '%s' not found in the registry.\n", imageName) return } var tl tagList if err := json.NewDecoder(resp.Body).Decode(&tl); err != nil { fmt.Println("Error decoding response:", err) return } if len(tl.Tags) == 0 { fmt.Printf("No tags found for image: %s\n", imageName) } else { for _, tag := range tl.Tags { fmt.Println(tag) } } } func registryRm(imageArg string) { parts := strings.SplitN(imageArg, ":", 2) imageName := parts[0] tag := "" if len(parts) > 1 { tag = parts[1] } if tag == "" { deleteImageByName(imageName) } else { deleteImageByTag(imageName, tag) } } func deleteImageByName(imageName string) { type tagList struct { Tags []string `json:"tags"` } resp, err := http.Get(fmt.Sprintf("%s/v2/%s/tags/list", registryURL, imageName)) if err != nil { fmt.Println("Error fetching tags:", err) return } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { fmt.Printf("Error: Image '%s' not found in the registry.\n", imageName) return } var tl tagList if err := json.NewDecoder(resp.Body).Decode(&tl); err != nil { fmt.Println("Error decoding response:", err) return } for _, tag := range tl.Tags { deleteImageByTag(imageName, tag) } } func deleteImageByTag(imageName, tag string) { client := &http.Client{} req, err := http.NewRequest("HEAD", fmt.Sprintf("%s/v2/%s/manifests/%s", registryURL, imageName, tag), nil) if err != nil { fmt.Println("Error creating request:", err) return } req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") resp, err := client.Do(req) if err != nil { fmt.Println("Error fetching manifest:", err) return } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { fmt.Printf("Error: Image '%s:%s' not found in the registry.\n", imageName, tag) return } digest := resp.Header.Get("Docker-Content-Digest") req, err = http.NewRequest("DELETE", fmt.Sprintf("%s/v2/%s/manifests/%s", registryURL, imageName, digest), nil) if err != nil { fmt.Println("Error creating delete request:", err) return } // Join username and password for Basic Auth registryCredentials := registryUsername + ":" + registryPassword req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(registryCredentials))) resp, err = client.Do(req) if err != nil { fmt.Println("Error deleting image:", err) return } defer resp.Body.Close() if resp.StatusCode == http.StatusAccepted { fmt.Printf("Deleted image: %s:%s\n", imageName, tag) } else { fmt.Printf("Failed to delete image: %s:%s (status code: %d)\n", imageName, tag, resp.StatusCode) } } func registryPush(imageName string) { if !strings.HasPrefix(imageName, registryName) { imageName = registryName + "/" + imageName } cmd := exec.Command("docker", "push", imageName) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Println("Error pushing image:", err) } } func registryTag(sourceImage, targetImage string) { if !strings.HasPrefix(targetImage, registryName) { targetImage = registryName + "/" + targetImage } cmd := exec.Command("docker", "tag", sourceImage, targetImage) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Println("Error tagging image:", err) } }