diff --git a/internal/httpserve/handler/clientendpoins.go b/internal/httpserve/handler/clientendpoins.go index ae6ab90..810da0d 100644 --- a/internal/httpserve/handler/clientendpoins.go +++ b/internal/httpserve/handler/clientendpoins.go @@ -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)) } diff --git a/internal/httpserve/handler/containers.go b/internal/httpserve/handler/containers.go index 521570d..9ed99bb 100644 --- a/internal/httpserve/handler/containers.go +++ b/internal/httpserve/handler/containers.go @@ -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) diff --git a/internal/httpserve/handler/uploadcontainerimage.go b/internal/httpserve/handler/uploadcontainerimage.go index 9dae21a..93007f4 100644 --- a/internal/httpserve/handler/uploadcontainerimage.go +++ b/internal/httpserve/handler/uploadcontainerimage.go @@ -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) } diff --git a/internal/templating/cmdparams/fromcli.go b/internal/templating/cmdparams/fromcli.go index d443b7e..a7c1d2e 100644 --- a/internal/templating/cmdparams/fromcli.go +++ b/internal/templating/cmdparams/fromcli.go @@ -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("") diff --git a/pkg/docker/client.go b/pkg/docker/client.go index eb6e507..80ed2a9 100644 --- a/pkg/docker/client.go +++ b/pkg/docker/client.go @@ -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 } diff --git a/pkg/docker/images.go b/pkg/docker/images.go index 4fc558b..a147389 100644 --- a/pkg/docker/images.go +++ b/pkg/docker/images.go @@ -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" @@ -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