Skip to content

Commit

Permalink
Backend: Refactor image import process and improve error handling
Browse files Browse the repository at this point in the history
- Update ImportImageToEngine to return image ID
- Modify PostDeploy to use new ImportImageToEngine return value
- Remove redundant GetImageIDByName call in PostDeploy
- Enhance GetImageIDByName with more robust image name matching
- Add logging throughout image import process
- Improve error handling and cleanup in case of failures
- Update FromPayloadStructToCmdParams to accept imageID parameter
  • Loading branch information
bnema committed Aug 28, 2024
1 parent fe7fdda commit 2c1a852
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 39 deletions.
20 changes: 10 additions & 10 deletions internal/httpserve/handler/clientendpoins.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,32 +90,32 @@ func PostDeploy(c echo.Context, a *server.App) error {
fmt.Printf("Image saved successfully to: %s\n", imagePath)

// Import the tar in docker
err = docker.ImportImageToEngine(imagePath)
imageID, err := docker.ImportImageToEngine(imagePath)
if err != nil {
store.RemoveFromStorage(imagePath) // Clean up the saved image if import fails
return sendJsonError(c, fmt.Errorf("failed to import image: %v", err))
}

if imageID == "" {
return sendJsonError(c, fmt.Errorf("imported image ID is empty"))
}

// Debug
fmt.Printf("Image ID: %s\n", imageID)

// Remove the image from the storage
err = store.RemoveFromStorage(imagePath)
if err != nil {
return sendJsonError(c, fmt.Errorf("failed to remove temporary image file: %v", err))
}

// append localhost to the image name since it's a manually imported image and not from a registry
payload.ImageName = "localhost/" + payload.ImageName

// Get the image ID
imageID, err := docker.GetImageIDByName(payload.ImageName)
if err != nil {
return sendJsonError(c, fmt.Errorf("failed to get image ID: %v", err))
}
fmt.Printf("Image imported successfully: %s\n", payload.ImageName)

// Update the payload with the image ID
payload.ImageID = imageID

// Create the container using cmdparams.FromPayloadStructToCmdParams
params, err := cmdparams.FromPayloadStructToCmdParams(payload, a)
params, err := cmdparams.FromPayloadStructToCmdParams(payload, a, imageID)
if err != nil {
return sendJsonError(c, fmt.Errorf("failed to create command parameters: %v", err))
}
Expand Down
1 change: 0 additions & 1 deletion internal/httpserve/handler/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ func ImageManagerComponent(c echo.Context, a *server.App) error {

// ImageManagerDelete handles the /image-manager/delete route
func ImageManagerDelete(c echo.Context, a *server.App) error {
//
ShortID := c.Param("ID")

imageID, exists := safelyInteractWithIDMap(Fetch, ShortID)
Expand Down
2 changes: 1 addition & 1 deletion internal/httpserve/handler/uploadcontainerimage.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func UploadImagePOSTHandler(c echo.Context, a *server.App) error {
}

// Import the image into Docker
err = docker.ImportImageToEngine(saveInPath)
_, err = docker.ImportImageToEngine(saveInPath)
if err != nil {
return fmt.Errorf("failed to import image to Docker: %v", err)
}
Expand Down
7 changes: 1 addition & 6 deletions internal/templating/cmdparams/fromcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@ import (
)

// FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
func FromPayloadStructToCmdParams(ppl *common.DeployPayload, a *server.App) (docker.ContainerCommandParams, error) {

imageID, err := docker.GetImageIDByName(ppl.ImageName)
if err != nil {
return docker.ContainerCommandParams{}, err
}
func FromPayloadStructToCmdParams(ppl *common.DeployPayload, a *server.App, imageID string) (docker.ContainerCommandParams, error) {

// volume empty for now
volumeSlice := ParseVolumeSlice("")
Expand Down
4 changes: 2 additions & 2 deletions pkg/docker/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ func CheckIfInitialized() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

containers, err := dockerCli.ContainerList(ctx, container.ListOptions{})
_, err := dockerCli.ContainerList(ctx, container.ListOptions{})
if err != nil {
return fmt.Errorf("cannot connect to Docker daemon: %s", err)
}
fmt.Printf("Found %d containers\n", len(containers))

return nil
}

Expand Down
140 changes: 121 additions & 19 deletions pkg/docker/images.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package docker

import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"regexp"
"strings"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
Expand Down Expand Up @@ -45,46 +50,143 @@ func CheckIfImageExists(imageID string) (bool, error) {
}

func GetImageIDByName(imageName string) (string, error) {

images, err := dockerCli.ImageList(context.Background(), image.ListOptions{})
if err != nil {
return "", fmt.Errorf("failed to list images: %w", err)
}

// Search for the image we just loaded
var imageID string
for _, image := range images {
for _, tag := range image.RepoTags {
if tag == imageName {
imageID = image.ID
normalizedName, tag := normalizeImageName(imageName)
fmt.Printf("Searching for image: %s:%s\n", normalizedName, tag)

tagPattern := createTagPattern(tag)

for _, img := range images {
for _, repoTag := range img.RepoTags {
normalizedRepoTag, repoTagValue := normalizeImageName(repoTag)
fmt.Printf("Comparing with: %s:%s\n", normalizedRepoTag, repoTagValue)

// Check for exact match (including with docker.io/ prefix)
if repoTag == imageName || repoTag == "docker.io/"+imageName {
return img.ID, nil
}

// Check for localhost match
if repoTag == "localhost/"+imageName {
return img.ID, nil
}

// Check for match with normalized name and tag
if (normalizedRepoTag == normalizedName ||
normalizedRepoTag == "docker.io/"+normalizedName) &&
tagPattern.MatchString(repoTagValue) {
return img.ID, nil
}
}
}

if imageID == "" {
return "", fmt.Errorf("image not found")
return "", fmt.Errorf("image not found: %s", imageName)
}

func normalizeImageName(name string) (string, string) {
// Split off the tag if present
parts := strings.SplitN(name, ":", 2)
normalizedName := parts[0]
tag := "latest"
if len(parts) > 1 {
tag = parts[1]
}

// Remove any leading "docker.io/" if present
normalizedName = strings.TrimPrefix(normalizedName, "docker.io/")

// Handle repository names with multiple segments
segments := strings.Split(normalizedName, "/")
if len(segments) > 1 {
// If there are multiple segments, join all but the last
normalizedName = strings.Join(segments[:len(segments)-1], "/") + "/" + segments[len(segments)-1]
}

return imageID, nil
return normalizedName, tag
}

func createTagPattern(tag string) *regexp.Regexp {
if tag == "" || tag == "latest" {
// Match any tag if the requested tag is empty or "latest"
return regexp.MustCompile(`.*`)
}

// Escape special regex characters in the tag
escapedTag := regexp.QuoteMeta(tag)

// Create a pattern that matches the tag exactly or as a prefix
// This allows matching "1.0" with "1.0.1", "1.0-alpine", etc.
return regexp.MustCompile(`^` + escapedTag + `($|[\.-])`)
}

// ImportImageToEngine imports an image to the Docker engine
func ImportImageToEngine(imageFilePath string) error {
// Open the image file
// ImportImageToEngine imports an image to the Docker engine and returns the image ID
func ImportImageToEngine(imageFilePath string) (string, error) {
log.Printf("Starting to import image from file: %s", imageFilePath)

imageFile, err := os.Open(imageFilePath)
if err != nil {
return fmt.Errorf("failed to open image file: %w", err)
log.Printf("Failed to open image file: %v", err)
return "", fmt.Errorf("failed to open image file: %w", err)
}
defer imageFile.Close()

// Import the image using the Docker client
importedImage, err := dockerCli.ImageLoad(context.Background(), imageFile, false)
log.Printf("Image file opened successfully")

resp, err := dockerCli.ImageLoad(context.Background(), imageFile, true)
if err != nil {
return fmt.Errorf("failed to import image: %w", err)
log.Printf("Failed to load image: %v", err)
return "", fmt.Errorf("failed to import image: %w", err)
}
defer importedImage.Body.Close()
defer resp.Body.Close()

return nil
log.Printf("Image load initiated, parsing response")

var imageName string
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
log.Printf("Response line: %s", line)

var message struct {
Stream string `json:"stream"`
}
if err := json.Unmarshal([]byte(line), &message); err != nil {
log.Printf("Failed to unmarshal JSON: %v", err)
continue // Skip lines that aren't JSON
}

log.Printf("Parsed message stream: %s", message.Stream)

if strings.HasPrefix(message.Stream, "Loaded image: ") {
imageName = strings.TrimSpace(strings.TrimPrefix(message.Stream, "Loaded image: "))
log.Printf("Found image name: %s", imageName)
break
}
}

if err := scanner.Err(); err != nil {
log.Printf("Error reading response: %v", err)
return "", fmt.Errorf("error reading response: %w", err)
}

if imageName == "" {
log.Printf("Failed to find image name in import response")
return "", fmt.Errorf("failed to find image name in import response")
}

// Now we need to get the image ID from the image name
imageID, err := GetImageIDByName(imageName)
if err != nil {
log.Printf("Failed to get image ID for name %s: %v", imageName, err)
return "", fmt.Errorf("failed to get image ID: %w", err)
}

log.Printf("Successfully imported image with ID: %s", imageID)
return imageID, nil
}

// ExportImageFromEngine exports an image from the Docker engine and returns it as an io.Reader
Expand Down

0 comments on commit 2c1a852

Please sign in to comment.