0.1.9 85724f1

images.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close