package installer import ( "fmt" "os/exec" "strings" "time" ) // DockerInstaller handles Docker image updates type DockerInstaller struct{} // NewDockerInstaller creates a new Docker installer func NewDockerInstaller() (*DockerInstaller, error) { // Check if docker is available first if _, err := exec.LookPath("docker"); err != nil { return nil, err } return &DockerInstaller{}, nil } // IsAvailable checks if Docker is available on this system func (i *DockerInstaller) IsAvailable() bool { _, err := exec.LookPath("docker") return err == nil } // Update pulls a new image using docker CLI func (i *DockerInstaller) Update(imageName, targetVersion string) (*InstallResult, error) { startTime := time.Now() // Pull the new image fmt.Printf("Pulling Docker image: %s...\n", imageName) pullCmd := exec.Command("sudo", "docker", "pull", imageName) output, err := pullCmd.CombinedOutput() if err != nil { return &InstallResult{ Success: false, ErrorMessage: fmt.Sprintf("Failed to pull Docker image: %v\nStdout: %s", err, string(output)), Stdout: string(output), Stderr: "", ExitCode: getExitCode(err), DurationSeconds: int(time.Since(startTime).Seconds()), Action: "pull", }, fmt.Errorf("docker pull failed: %w", err) } fmt.Printf("Successfully pulled image: %s\n", string(output)) duration := int(time.Since(startTime).Seconds()) return &InstallResult{ Success: true, Stdout: string(output), Stderr: "", ExitCode: 0, DurationSeconds: duration, Action: "pull", ContainersUpdated: []string{}, // Would find and recreate containers in a real implementation }, nil } // UpdatePackage updates a specific Docker image (alias for Update method) func (i *DockerInstaller) UpdatePackage(imageName string) (*InstallResult, error) { // Docker uses same logic for updating as installing return i.Update(imageName, "") } // Install installs a Docker image (alias for Update) func (i *DockerInstaller) Install(imageName string) (*InstallResult, error) { return i.Update(imageName, "") } // InstallMultiple installs multiple Docker images func (i *DockerInstaller) InstallMultiple(imageNames []string) (*InstallResult, error) { if len(imageNames) == 0 { return &InstallResult{ Success: false, ErrorMessage: "No images specified for installation", }, fmt.Errorf("no images specified") } startTime := time.Now() var allOutput strings.Builder var errors []string for _, imageName := range imageNames { fmt.Printf("Pulling Docker image: %s...\n", imageName) pullCmd := exec.Command("sudo", "docker", "pull", imageName) output, err := pullCmd.CombinedOutput() allOutput.WriteString(string(output)) if err != nil { errors = append(errors, fmt.Sprintf("Failed to pull %s: %v", imageName, err)) } else { fmt.Printf("Successfully pulled image: %s\n", imageName) } } duration := int(time.Since(startTime).Seconds()) if len(errors) > 0 { return &InstallResult{ Success: false, ErrorMessage: fmt.Sprintf("Docker pull errors: %v", strings.Join(errors, "; ")), Stdout: allOutput.String(), Stderr: "", ExitCode: 1, DurationSeconds: duration, Action: "pull_multiple", }, fmt.Errorf("docker pull failed for some images") } return &InstallResult{ Success: true, Stdout: allOutput.String(), Stderr: "", ExitCode: 0, DurationSeconds: duration, Action: "pull_multiple", ContainersUpdated: imageNames, }, nil } // Upgrade is not applicable for Docker in the same way func (i *DockerInstaller) Upgrade() (*InstallResult, error) { return &InstallResult{ Success: false, ErrorMessage: "Docker upgrade not implemented - use specific image updates", ExitCode: 1, DurationSeconds: 0, Action: "upgrade", }, fmt.Errorf("docker upgrade not implemented") } // DryRun for Docker images checks if the image can be pulled without actually pulling it func (i *DockerInstaller) DryRun(imageName string) (*InstallResult, error) { startTime := time.Now() // Check if image exists locally inspectCmd := exec.Command("sudo", "docker", "image", "inspect", imageName) output, err := inspectCmd.CombinedOutput() if err == nil { // Image exists locally duration := int(time.Since(startTime).Seconds()) return &InstallResult{ Success: true, Stdout: fmt.Sprintf("Docker image %s is already available locally", imageName), Stderr: string(output), ExitCode: 0, DurationSeconds: duration, Dependencies: []string{}, // Docker doesn't have traditional dependencies IsDryRun: true, Action: "dry_run", }, nil } // Image doesn't exist locally, check if it exists in registry // Use docker manifest command to check remote availability manifestCmd := exec.Command("sudo", "docker", "manifest", "inspect", imageName) manifestOutput, manifestErr := manifestCmd.CombinedOutput() duration := int(time.Since(startTime).Seconds()) if manifestErr != nil { return &InstallResult{ Success: false, ErrorMessage: fmt.Sprintf("Docker image %s not found locally or in remote registry", imageName), Stdout: string(output), Stderr: string(manifestOutput), ExitCode: getExitCode(manifestErr), DurationSeconds: duration, Dependencies: []string{}, IsDryRun: true, Action: "dry_run", }, fmt.Errorf("docker image not found") } return &InstallResult{ Success: true, Stdout: fmt.Sprintf("Docker image %s is available for download", imageName), Stderr: string(manifestOutput), ExitCode: 0, DurationSeconds: duration, Dependencies: []string{}, // Docker doesn't have traditional dependencies IsDryRun: true, Action: "dry_run", }, nil } // GetPackageType returns type of packages this installer handles func (i *DockerInstaller) GetPackageType() string { return "docker_image" }