1package docker
2
3import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "log"
10 "os"
11 "regexp"
12 "strings"
13
14 "github.com/docker/docker/api/types"
15 "github.com/docker/docker/api/types/image"
16)
17
18func ListContainerImages() ([]image.Summary, error) {
19 // Check if the Docker client has been initialized
20 CheckIfInitialized()
21
22 // List images using the Docker client
23 images, err := dockerCli.ImageList(context.Background(), image.ListOptions{})
24 if err != nil {
25 return nil, err
26 }
27
28 return images, nil
29}
30
31// DeleteContainerImage deletes an image from the Docker engine
32func DeleteContainerImage(imageID string) error {
33 _, err := dockerCli.ImageRemove(context.Background(), imageID, image.RemoveOptions{})
34 if err != nil {
35 return err
36 }
37
38 return nil
39}
40
41// CheckIfImageExists checks if an image exists in the Docker engine
42func CheckIfImageExists(imageID string) (bool, error) {
43 // Get the image information using the Docker client
44 _, err := GetImageInfo(imageID)
45 if err != nil {
46 return false, nil
47 }
48
49 return true, nil
50}
51
52func GetImageIDByName(imageName string) (string, error) {
53 images, err := dockerCli.ImageList(context.Background(), image.ListOptions{})
54 if err != nil {
55 return "", fmt.Errorf("failed to list images: %w", err)
56 }
57
58 normalizedName, tag := normalizeImageName(imageName)
59 fmt.Printf("Searching for image: %s:%s\n", normalizedName, tag)
60
61 tagPattern := createTagPattern(tag)
62
63 for _, img := range images {
64 for _, repoTag := range img.RepoTags {
65 normalizedRepoTag, repoTagValue := normalizeImageName(repoTag)
66 fmt.Printf("Comparing with: %s:%s\n", normalizedRepoTag, repoTagValue)
67
68 // Check for exact match (including with docker.io/ prefix)
69 if repoTag == imageName || repoTag == "docker.io/"+imageName {
70 return img.ID, nil
71 }
72
73 // Check for localhost match
74 if repoTag == "localhost/"+imageName {
75 return img.ID, nil
76 }
77
78 // Check for match with normalized name and tag
79 if (normalizedRepoTag == normalizedName ||
80 normalizedRepoTag == "docker.io/"+normalizedName) &&
81 tagPattern.MatchString(repoTagValue) {
82 return img.ID, nil
83 }
84 }
85 }
86
87 return "", fmt.Errorf("image not found: %s", imageName)
88}
89
90func normalizeImageName(name string) (string, string) {
91 // Split off the tag if present
92 parts := strings.SplitN(name, ":", 2)
93 normalizedName := parts[0]
94 tag := "latest"
95 if len(parts) > 1 {
96 tag = parts[1]
97 }
98
99 // Remove any leading "docker.io/" if present
100 normalizedName = strings.TrimPrefix(normalizedName, "docker.io/")
101
102 // Handle repository names with multiple segments
103 segments := strings.Split(normalizedName, "/")
104 if len(segments) > 1 {
105 // If there are multiple segments, join all but the last
106 normalizedName = strings.Join(segments[:len(segments)-1], "/") + "/" + segments[len(segments)-1]
107 }
108
109 return normalizedName, tag
110}
111
112func createTagPattern(tag string) *regexp.Regexp {
113 if tag == "" || tag == "latest" {
114 // Match any tag if the requested tag is empty or "latest"
115 return regexp.MustCompile(`.*`)
116 }
117
118 // Escape special regex characters in the tag
119 escapedTag := regexp.QuoteMeta(tag)
120
121 // Create a pattern that matches the tag exactly or as a prefix
122 // This allows matching "1.0" with "1.0.1", "1.0-alpine", etc.
123 return regexp.MustCompile(`^` + escapedTag + `($|[\.-])`)
124}
125
126// ImportImageToEngine imports an image to the Docker engine and returns the image ID
127func ImportImageToEngine(imageFilePath string) (string, error) {
128 log.Printf("Starting to import image from file: %s", imageFilePath)
129
130 imageFile, err := os.Open(imageFilePath)
131 if err != nil {
132 log.Printf("Failed to open image file: %v", err)
133 return "", fmt.Errorf("failed to open image file: %w", err)
134 }
135 defer imageFile.Close()
136
137 log.Printf("Image file opened successfully")
138
139 resp, err := dockerCli.ImageLoad(context.Background(), imageFile, true)
140 if err != nil {
141 log.Printf("Failed to load image: %v", err)
142 return "", fmt.Errorf("failed to import image: %w", err)
143 }
144 defer resp.Body.Close()
145
146 log.Printf("Image load initiated, parsing response")
147
148 var imageName string
149 scanner := bufio.NewScanner(resp.Body)
150 for scanner.Scan() {
151 line := scanner.Text()
152 log.Printf("Response line: %s", line)
153
154 var message struct {
155 Stream string `json:"stream"`
156 }
157 if err := json.Unmarshal([]byte(line), &message); err != nil {
158 log.Printf("Failed to unmarshal JSON: %v", err)
159 continue // Skip lines that aren't JSON
160 }
161
162 log.Printf("Parsed message stream: %s", message.Stream)
163
164 if strings.HasPrefix(message.Stream, "Loaded image: ") {
165 imageName = strings.TrimSpace(strings.TrimPrefix(message.Stream, "Loaded image: "))
166 log.Printf("Found image name: %s", imageName)
167 break
168 }
169 }
170
171 if err := scanner.Err(); err != nil {
172 log.Printf("Error reading response: %v", err)
173 return "", fmt.Errorf("error reading response: %w", err)
174 }
175
176 if imageName == "" {
177 log.Printf("Failed to find image name in import response")
178 return "", fmt.Errorf("failed to find image name in import response")
179 }
180
181 // Now we need to get the image ID from the image name
182 imageID, err := GetImageIDByName(imageName)
183 if err != nil {
184 log.Printf("Failed to get image ID for name %s: %v", imageName, err)
185 return "", fmt.Errorf("failed to get image ID: %w", err)
186 }
187
188 log.Printf("Successfully imported image with ID: %s", imageID)
189 return imageID, nil
190}
191
192// ExportImageFromEngine exports an image from the Docker engine and returns it as an io.Reader
193func ExportImageFromEngine(imageID string) (io.ReadCloser, error) {
194 // Check if the Docker client has been initialized
195
196 // Get the image information using the Docker client
197 imageInfo, err := GetImageInfo(imageID)
198 if err != nil {
199 return nil, err
200 }
201
202 if len(imageInfo.RepoTags) == 0 {
203 return nil, fmt.Errorf("image has no tag")
204 }
205
206 // Export the image using the Docker client
207 imageReader, err := dockerCli.ImageSave(context.Background(), []string{imageInfo.RepoTags[0]})
208 if err != nil {
209 return nil, fmt.Errorf("failed to export image: %w", err)
210 }
211
212 return imageReader, nil
213}
214
215// From an ID, get the all the information about the image
216func GetImageInfo(imageID string) (*types.ImageInspect, error) {
217 // Get the image information using the Docker client
218 imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), imageID)
219 if err != nil {
220 return nil, err
221 }
222
223 return &imageInfo, nil
224}
225
226// GetImageSize returns the size of an image
227func GetImageSize(imageID string) (int64, error) {
228 // Get the image information using the Docker client
229 imageInfo, err := GetImageInfo(imageID)
230 if err != nil {
231 return 0, err
232 }
233
234 return imageInfo.Size, nil
235}
236
237// GetImageTag returns the tag of an image
238func GetImageTag(imageID string) (string, error) {
239 // Get the image information using the Docker client
240 imageInfo, err := GetImageInfo(imageID)
241 if err != nil {
242 return "", err
243 }
244
245 return imageInfo.RepoTags[0], nil
246}
247
248// GetImageName returns the name of an image
249func GetImageName(imageID string) (string, error) {
250 // Get the image information using the Docker client
251 imageInfo, err := GetImageInfo(imageID)
252 if err != nil {
253 return "", err
254 }
255
256 return imageInfo.RepoDigests[0], nil
257}
258
259// WhoAmI attempts to identify the Docker image digest of the container running this code.
260func WhoAmI() (string, error) {
261 gordonImage := "ghcr.io/bnema/gordon:latest"
262
263 // Get the image information using the Docker client
264 imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), gordonImage)
265 if err != nil {
266 return "", err
267 }
268
269 return imageInfo.ID, nil
270}
271
272func GetImageSizeFromReader(imageID string) (int64, error) {
273 // Export the image using the Docker client
274 imageReader, err := dockerCli.ImageSave(context.Background(), []string{imageID})
275 if err != nil {
276 return 0, fmt.Errorf("failed to export image: %w", err)
277 }
278
279 // Read the entire stream to get the actual size
280 actualSize := int64(0)
281 buf := make([]byte, 1024) // A buffer for reading the stream
282 for {
283 n, err := imageReader.Read(buf)
284 actualSize += int64(n)
285 if err != nil {
286 if err == io.EOF {
287 break // End of file is reached
288 }
289 return 0, fmt.Errorf("failed to read image: %w", err)
290 }
291 }
292
293 return actualSize, nil
294}
295
296// list all images
297func GetAllImages() ([]image.Summary, error) {
298 // List images using the Docker client
299 images, err := dockerCli.ImageList(context.Background(), image.ListOptions{})
300 if err != nil {
301 return nil, err
302 }
303
304 return images, nil
305}