Skip to content

Commit b4e450a

Browse files
committed
Initial commit
0 parents  commit b4e450a

File tree

9 files changed

+1643
-0
lines changed

9 files changed

+1643
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
docker-inspector-*
2+
cmd/docker-inspector/internal-inspector

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Hans Raaf
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.PHONY: all clean
2+
3+
BINARY_NAME=docker-inspector
4+
INTERNAL_BINARY=internal-inspector
5+
6+
all: clean $(BINARY_NAME)
7+
8+
clean:
9+
rm -f $(BINARY_NAME) cmd/docker-inspector/$(INTERNAL_BINARY)
10+
11+
# Build the internal Linux inspector first
12+
cmd/docker-inspector/$(INTERNAL_BINARY): cmd/internal-inspector/main.go
13+
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o cmd/docker-inspector/$(INTERNAL_BINARY) ./cmd/internal-inspector
14+
15+
# Build the main wrapper for the current platform
16+
$(BINARY_NAME): cmd/docker-inspector/$(INTERNAL_BINARY)
17+
go build -o $(BINARY_NAME) ./cmd/docker-inspector
18+
19+
# Build for specific platforms
20+
.PHONY: darwin linux windows
21+
darwin: clean cmd/docker-inspector/$(INTERNAL_BINARY)
22+
GOOS=darwin GOARCH=amd64 go build -o $(BINARY_NAME)-darwin ./cmd/docker-inspector
23+
24+
linux: clean cmd/docker-inspector/$(INTERNAL_BINARY)
25+
GOOS=linux GOARCH=amd64 go build -o $(BINARY_NAME)-linux ./cmd/docker-inspector
26+
27+
windows: clean cmd/docker-inspector/$(INTERNAL_BINARY)
28+
GOOS=windows GOARCH=amd64 go build -o $(BINARY_NAME).exe ./cmd/docker-inspector

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Docker Image Inspector
2+
3+
A command-line tool to inspect the contents of Docker images without having to manually create containers or extract tar files. The tool creates a temporary container, inspects its filesystem, and cleans up automatically.
4+
5+
## Features
6+
7+
- Cross-platform: Runs on macOS, Linux, and Windows (with Linux containers)
8+
- Inspects any Docker image without modifying it
9+
- Recursive directory listing
10+
- Glob pattern support (including `**/`) for finding specific files
11+
- MD5 checksum calculation for files
12+
- JSON output option for automated processing
13+
- Detailed summaries of files, directories, and sizes
14+
- Clean handling of special filesystems (/proc, /sys, etc.)
15+
- Modification time handling for reliable diffs
16+
17+
## Installation
18+
19+
1. Clone the repository
20+
2. Build for your platform:
21+
```bash
22+
# For macOS
23+
make darwin
24+
25+
# For Linux
26+
make linux
27+
28+
# For Windows
29+
make windows
30+
```
31+
32+
## Usage
33+
34+
Basic usage:
35+
```bash
36+
./docker-inspector nginx:latest
37+
```
38+
39+
With options:
40+
```bash
41+
# Find specific files
42+
./docker-inspector nginx:latest --glob "**/*.conf"
43+
44+
# Calculate MD5 checksums
45+
./docker-inspector nginx:latest --md5
46+
47+
# Output as JSON
48+
./docker-inspector nginx:latest --json > nginx-files.json
49+
50+
# Inspect specific path
51+
./docker-inspector nginx:latest --path /etc/nginx
52+
53+
# Keep container for further inspection
54+
./docker-inspector nginx:latest --keep
55+
```
56+
57+
### Comparing Images
58+
59+
To compare the contents of two images or two runs of the same image:
60+
61+
```bash
62+
# With modification times (might show differences due to container startup)
63+
./docker-inspector nginx:latest --json --md5 > run1.txt
64+
./docker-inspector nginx:latest --json --md5 > run2.txt
65+
diff run1.txt run2.txt
66+
67+
# Without modification times (more reliable for structural comparisons)
68+
./docker-inspector nginx:latest --json --md5 --no-times > run1.txt
69+
./docker-inspector nginx:latest --json --md5 --no-times > run2.txt
70+
diff run1.txt run2.txt
71+
```
72+
73+
Note: Files like /etc/resolv.conf typically show modification time differences between runs due to container startup configuration. Using --md5 helps identify actual content changes regardless of timestamps.
74+
75+
### Options
76+
77+
```
78+
--path Path inside the container to inspect (default: "/")
79+
--json Output in JSON format
80+
--summary Show summary statistics
81+
--glob Glob pattern for matching files (supports **/)
82+
--md5 Calculate MD5 checksums for files
83+
--keep Keep the temporary container after inspection
84+
--no-times Exclude modification times from output (useful for diffs)
85+
```
86+
87+
## How It Works
88+
89+
The tool:
90+
1. Creates a temporary container from the specified image
91+
2. Copies a specialized Linux inspector binary into the container
92+
3. Executes the inspector inside the container
93+
4. Collects and formats the results
94+
5. Automatically cleans up the container (unless --keep is specified)
95+
96+
## Building from Source
97+
98+
Requires:
99+
- Go 1.21 or later
100+
- Docker running with Linux containers
101+
- make
102+
103+
```bash
104+
# Build for current platform
105+
make
106+
107+
# Or for specific platform
108+
make darwin # For macOS
109+
make linux # For Linux
110+
make windows # For Windows
111+
```
112+
113+
## Credits
114+
115+
Most of the implementation work was done by Claude (Anthropic) in a conversation about Docker image inspection requirements and cross-platform Go development. The original concept and requirements were provided by the repository owner.
116+
117+
## License
118+
119+
[MIT-License](LICENSE.txt)

cmd/docker-inspector/main.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package main
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"time"
11+
12+
"github.com/alexflint/go-arg"
13+
)
14+
15+
//go:embed internal-inspector
16+
var internalInspector []byte
17+
18+
type Args struct {
19+
Image string `arg:"positional,required" help:"docker image to inspect"`
20+
Path string `arg:"--path" default:"/" help:"path inside the container to inspect"`
21+
JSON bool `arg:"--json" help:"output in JSON format"`
22+
Summary bool `arg:"--summary" help:"show summary statistics"`
23+
Pattern string `arg:"--glob" help:"glob pattern for matching files (supports **/)"`
24+
MD5 bool `arg:"--md5" help:"calculate MD5 checksums for files"`
25+
Keep bool `arg:"--keep" help:"keep the temporary container after inspection"`
26+
NoTimes bool `arg:"--no-times" help:"exclude modification times from output"`
27+
}
28+
29+
func (Args) Version() string {
30+
return "docker-inspector 1.0.0"
31+
}
32+
33+
func (Args) Description() string {
34+
return "Docker image content inspector - examines files and directories inside a container image"
35+
}
36+
37+
func runInspector(containerID string, args Args) error {
38+
// Create a temporary directory for the inspector
39+
tempDir, err := os.MkdirTemp("", "docker-inspector-*")
40+
if err != nil {
41+
return fmt.Errorf("failed to create temp dir: %v", err)
42+
}
43+
defer os.RemoveAll(tempDir)
44+
45+
// Write the embedded Linux inspector to the temp directory
46+
inspectorPath := filepath.Join(tempDir, "internal-inspector")
47+
if err := os.WriteFile(inspectorPath, internalInspector, 0755); err != nil {
48+
return fmt.Errorf("failed to write inspector: %v", err)
49+
}
50+
51+
// Copy the inspector to the container
52+
copyCmd := exec.Command("docker", "cp", inspectorPath, fmt.Sprintf("%s:/inspect", containerID))
53+
if output, err := copyCmd.CombinedOutput(); err != nil {
54+
return fmt.Errorf("failed to copy inspector to container: %v\n%s", err, output)
55+
}
56+
57+
// Build the command arguments
58+
dockerArgs := []string{"exec", containerID, "/inspect"}
59+
if args.JSON {
60+
dockerArgs = append(dockerArgs, "--json")
61+
}
62+
if args.Summary {
63+
dockerArgs = append(dockerArgs, "--summary")
64+
}
65+
if args.Pattern != "" {
66+
dockerArgs = append(dockerArgs, "--glob", args.Pattern)
67+
}
68+
if args.MD5 {
69+
dockerArgs = append(dockerArgs, "--md5")
70+
}
71+
if args.NoTimes {
72+
dockerArgs = append(dockerArgs, "--no-times")
73+
}
74+
if args.Path != "/" {
75+
dockerArgs = append(dockerArgs, "--path", args.Path)
76+
}
77+
78+
cmd := exec.Command("docker", dockerArgs...)
79+
cmd.Stdout = os.Stdout
80+
cmd.Stderr = os.Stderr
81+
return cmd.Run()
82+
}
83+
84+
func main() {
85+
var args Args
86+
// Set defaults
87+
args.Summary = false
88+
args.Path = "/"
89+
90+
arg.MustParse(&args)
91+
92+
// First, ensure the image exists or can be pulled
93+
pullCmd := exec.Command("docker", "pull", args.Image)
94+
pullCmd.Stderr = os.Stderr
95+
if err := pullCmd.Run(); err != nil {
96+
fmt.Fprintf(os.Stderr, "Failed to pull image %s: %v\n", args.Image, err)
97+
os.Exit(1)
98+
}
99+
100+
// Start a temporary container
101+
startCmd := exec.Command("docker", "run", "-d", "--entrypoint", "sleep", args.Image, "3600")
102+
output, err := startCmd.CombinedOutput()
103+
if err != nil {
104+
fmt.Fprintf(os.Stderr, "Failed to start container: %v\n%s\n", err, output)
105+
os.Exit(1)
106+
}
107+
108+
containerID := strings.TrimSpace(string(output))
109+
if containerID == "" {
110+
fmt.Fprintf(os.Stderr, "Failed to get container ID\n")
111+
os.Exit(1)
112+
}
113+
114+
// Give the container a moment to start
115+
time.Sleep(1 * time.Second)
116+
117+
// Ensure container cleanup unless --keep is specified
118+
if !args.Keep {
119+
defer func() {
120+
stopCmd := exec.Command("docker", "rm", "-f", containerID)
121+
stopCmd.Run()
122+
}()
123+
}
124+
125+
// Run the inspection
126+
if err := runInspector(containerID, args); err != nil {
127+
fmt.Fprintf(os.Stderr, "Inspection failed: %v\n", err)
128+
os.Exit(1)
129+
}
130+
131+
if args.Keep {
132+
fmt.Printf("\nContainer ID: %s\n", containerID)
133+
}
134+
}

0 commit comments

Comments
 (0)