feat: 优化非 Docker 命令的易用性,修复带 digest 镜像无法按名称查找
- docker save/rmi:镜像名不再拼接 digest({{.Digest}} 对多数镜像输出
sha256:…,导致 alpine:3.20 被存成 alpine:3.20@sha256:… 而查不到),
digest 单独保存;同时修正自定义 --format 输出中 IMAGE ID 表头未被过滤
- diagnose:目标不存在时给出相似名称候选,无相似时列出当前存在的对象
- net.pids:无进程占用时向 stderr 提示且 stdout 保持为空,新增 --pid-file
- net.ports:默认输出跨平台一致的端口表格,--raw 保留原生命令输出
- disk.usage:路径可省略(默认当前目录),新增 --depth N 控制层级
This commit is contained in:
@@ -74,6 +74,7 @@ type imageRef struct {
|
||||
ID string // image ID, e.g. "sha256:abc..."
|
||||
CreatedAt string // human-readable creation time, as docker lists it
|
||||
Size string // human-readable size, as docker lists it
|
||||
Digest string // content digest, e.g. "sha256:abc..."; may be empty
|
||||
Repository string
|
||||
Tag string
|
||||
}
|
||||
@@ -92,10 +93,12 @@ func listImages(ctx context.Context) ([]imageRef, error) {
|
||||
|
||||
var refs []imageRef
|
||||
for _, line := range splitLines(out) {
|
||||
if strings.HasPrefix(line, "REPOSITORY") {
|
||||
fields := strings.Split(line, "\t")
|
||||
// With kopsc's own --format the header starts with "IMAGE ID", while
|
||||
// docker's default header starts with "REPOSITORY"; skip either.
|
||||
if isImageHeader(line, fields) {
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(line, "\t")
|
||||
ref := imageRef{ID: strings.TrimSpace(fields[0])}
|
||||
if len(fields) >= 3 {
|
||||
repo, tag := strings.TrimSpace(fields[1]), strings.TrimSpace(fields[2])
|
||||
@@ -111,16 +114,34 @@ func listImages(ctx context.Context) ([]imageRef, error) {
|
||||
}
|
||||
ref.Repository = repo
|
||||
ref.Tag = tag
|
||||
ref.Name = formatImageName(repo, tag, digest)
|
||||
ref.Digest = digest
|
||||
ref.Name = formatImageName(repo, tag)
|
||||
}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
// formatImageName renders repository[:tag][@digest]. "<none>" is how docker
|
||||
// groups untagged images.
|
||||
func formatImageName(repo, tag, digest string) string {
|
||||
// isImageHeader reports whether a line is docker's column header rather than
|
||||
// an image row. Headers never carry a real 12-character hex ID.
|
||||
func isImageHeader(line string, fields []string) bool {
|
||||
if strings.HasPrefix(line, "REPOSITORY") || strings.HasPrefix(line, "IMAGE ID") {
|
||||
return true
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return false
|
||||
}
|
||||
return !looksLikeImageID(fields[0])
|
||||
}
|
||||
|
||||
// formatImageName renders repository[:tag]. "<none>" is how docker groups
|
||||
// untagged images.
|
||||
//
|
||||
// The digest is deliberately not part of the name: `docker save` and friends
|
||||
// are addressed by repository:tag (alpine:3.20), and appending "@sha256:..." —
|
||||
// which `docker images --format {{.Digest}}` prints for most pulled images —
|
||||
// made every such image impossible to look up by the name users type.
|
||||
func formatImageName(repo, tag string) string {
|
||||
if repo == "<none>" {
|
||||
return ""
|
||||
}
|
||||
@@ -128,9 +149,6 @@ func formatImageName(repo, tag, digest string) string {
|
||||
if tag != "" && tag != "<none>" {
|
||||
name += ":" + tag
|
||||
}
|
||||
if digest != "" && digest != "<none>" {
|
||||
name += "@" + digest
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user