Refactor project structure

This commit is contained in:
Illya Marchenko 2024-09-30 19:31:22 +03:00
parent cb85168cbd
commit 8834dd8aec
Signed by: stuzer05
GPG Key ID: A6ABAAA9268F9F4F
7 changed files with 272 additions and 211 deletions

40
cmd/images.go Normal file
View File

@ -0,0 +1,40 @@
package cmd
import (
"encoding/json"
"fmt"
"gitea.stuzer.link/stuzer05/docker-registry-manager/internal/app"
"net/http"
)
func RegistryImages(app *app.App) {
type catalog struct {
Repositories []string `json:"repositories"`
}
resp, err := http.Get(app.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)
}
}
}

22
cmd/push.go Normal file
View File

@ -0,0 +1,22 @@
package cmd
import (
"fmt"
"gitea.stuzer.link/stuzer05/docker-registry-manager/internal/app"
"os"
"os/exec"
"strings"
)
func RegistryPush(app *app.App, imageName string) {
if !strings.HasPrefix(imageName, app.RegistryName) {
imageName = app.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)
}
}

101
cmd/rm.go Normal file
View File

@ -0,0 +1,101 @@
package cmd
import (
"encoding/base64"
"encoding/json"
"fmt"
"gitea.stuzer.link/stuzer05/docker-registry-manager/internal/app"
"net/http"
"strings"
)
func RegistryRm(app *app.App, imageArg string) {
parts := strings.SplitN(imageArg, ":", 2)
imageName := parts[0]
tag := ""
if len(parts) > 1 {
tag = parts[1]
}
if tag == "" {
deleteImageByName(app, imageName)
} else {
deleteImageByTag(app, imageName, tag)
}
}
func deleteImageByName(app *app.App, imageName string) {
type tagList struct {
Tags []string `json:"tags"`
}
resp, err := http.Get(fmt.Sprintf("%s/v2/%s/tags/list", app.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(app, imageName, tag)
}
}
func deleteImageByTag(app *app.App, imageName, tag string) {
client := &http.Client{}
req, err := http.NewRequest("HEAD", fmt.Sprintf("%s/v2/%s/manifests/%s", app.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", app.RegistryURL, imageName, digest), nil)
if err != nil {
fmt.Println("Error creating delete request:", err)
return
}
// Join username and password for Basic Auth
registryCredentials := app.RegistryUsername + ":" + app.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)
}
}

22
cmd/tag.go Normal file
View File

@ -0,0 +1,22 @@
package cmd
import (
"fmt"
"gitea.stuzer.link/stuzer05/docker-registry-manager/internal/app"
"os"
"os/exec"
"strings"
)
func RegistryTag(app *app.App, sourceImage, targetImage string) {
if !strings.HasPrefix(targetImage, app.RegistryName) {
targetImage = app.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)
}
}

40
cmd/tags.go Normal file
View File

@ -0,0 +1,40 @@
package cmd
import (
"encoding/json"
"fmt"
"gitea.stuzer.link/stuzer05/docker-registry-manager/internal/app"
"net/http"
)
func RegistryTags(app *app.App, imageName string) {
type tagList struct {
Tags []string `json:"tags"`
}
resp, err := http.Get(fmt.Sprintf("%s/v2/%s/tags/list", app.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)
}
}
}

33
internal/app/app.go Normal file
View File

@ -0,0 +1,33 @@
package app
import (
"fmt"
"os"
)
type App struct {
RegistryName string
RegistryURL string
RegistryUsername string
RegistryPassword string
}
func NewApp() (*App, error) {
registryName := os.Getenv("REGISTRY_NAME")
if registryName == "" {
return nil, fmt.Errorf("REGISTRY_NAME not found in .env")
}
registryUsername := os.Getenv("REGISTRY_USERNAME")
registryPassword := os.Getenv("REGISTRY_PASSWORD")
if registryUsername == "" || registryPassword == "" {
return nil, fmt.Errorf("REGISTRY_USERNAME or REGISTRY_PASSWORD not found in .env")
}
return &App{
RegistryName: registryName,
RegistryURL: "https://" + registryName,
RegistryUsername: registryUsername,
RegistryPassword: registryPassword,
}, nil
}

225
main.go
View File

@ -1,46 +1,30 @@
package main package main
import ( import (
"encoding/base64"
"encoding/json"
"fmt" "fmt"
"github.com/joho/godotenv"
"net/http"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
)
var ( "gitea.stuzer.link/stuzer05/docker-registry-manager/cmd"
registryName string "gitea.stuzer.link/stuzer05/docker-registry-manager/internal/app"
registryURL string
registryUsername string "github.com/joho/godotenv"
registryPassword string
) )
func main() { func main() {
// Load .env file // Load .env file
_, b, _, _ := runtime.Caller(0) // @refactor _, b, _, _ := runtime.Caller(0)
err := godotenv.Load(filepath.Join(filepath.Dir(b), ".env")) err := godotenv.Load(filepath.Join(filepath.Dir(b), ".env"))
if err != nil { if err != nil {
fmt.Println("Error loading .env file:", err) fmt.Println("Error loading .env file:", err)
return return
} }
// Read values from environment // Initialize app
registryName = os.Getenv("REGISTRY_NAME") app, err := app.NewApp()
if registryName == "" { if err != nil {
fmt.Println("REGISTRY_NAME not found in .env") fmt.Println("Error initializing app:", err)
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 return
} }
@ -51,213 +35,32 @@ func main() {
switch os.Args[1] { switch os.Args[1] {
case "images": case "images":
registryImages() cmd.RegistryImages(app)
case "tags": case "tags":
if len(os.Args) < 3 { if len(os.Args) < 3 {
fmt.Println("Error: Please provide an image name.") fmt.Println("Error: Please provide an image name.")
return return
} }
registryTags(os.Args[2]) cmd.RegistryTags(app, os.Args[2])
case "rm": case "rm":
if len(os.Args) < 3 { 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).") fmt.Println("Error: Please provide an image name and optionally a tag (e.g., my-image or my-image:latest).")
return return
} }
registryRm(os.Args[2]) cmd.RegistryRm(app, os.Args[2])
case "push": case "push":
if len(os.Args) < 3 { if len(os.Args) < 3 {
fmt.Println("Error: Please provide an image name to push.") fmt.Println("Error: Please provide an image name to push.")
return return
} }
registryPush(os.Args[2]) cmd.RegistryPush(app, os.Args[2])
case "tag": case "tag":
if len(os.Args) < 4 { if len(os.Args) < 4 {
fmt.Println("Error: Please provide both source and target image names.") fmt.Println("Error: Please provide both source and target image names.")
return return
} }
registryTag(os.Args[2], os.Args[3]) cmd.RegistryTag(app, os.Args[2], os.Args[3])
default: default:
fmt.Println("Invalid command. Usage: registry [images|tags|rm|push|tag] <args>") fmt.Println("Invalid command. Usage: registry [images|tags|rm|push|tag] <args>")
} }
} }
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)
}
}