/
image_status.go
147 lines (137 loc) · 4.24 KB
/
image_status.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package server
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
istorage "github.com/containers/image/v5/storage"
"github.com/cri-o/cri-o/internal/log"
pkgstorage "github.com/cri-o/cri-o/internal/storage"
json "github.com/json-iterator/go"
specs "github.com/opencontainers/image-spec/specs-go/v1"
types "k8s.io/cri-api/pkg/apis/runtime/v1"
)
// ImageStatus returns the status of the image.
func (s *Server) ImageStatus(ctx context.Context, req *types.ImageStatusRequest) (*types.ImageStatusResponse, error) {
ctx, span := log.StartSpan(ctx)
defer span.End()
img := req.Image
if img == nil || img.Image == "" {
return nil, fmt.Errorf("no image specified")
}
log.Infof(ctx, "Checking image status: %s", img.Image)
status, err := s.storageImageStatus(ctx, *img)
if err != nil {
return nil, err
}
if status == nil {
log.Infof(ctx, "Image %s not found", img.Image)
return &types.ImageStatusResponse{}, nil
}
// Ensure that size is already defined
var size uint64
if status.Size == nil {
size = 0
} else {
size = *status.Size
}
resp := &types.ImageStatusResponse{
Image: &types.Image{
Id: status.ID.IDStringForOutOfProcessConsumptionOnly(),
RepoTags: status.RepoTags,
RepoDigests: status.RepoDigests,
Size_: size,
Spec: &types.ImageSpec{
Annotations: status.Annotations,
},
},
}
if req.Verbose {
info, err := createImageInfo(status)
if err != nil {
return nil, fmt.Errorf("creating image info: %w", err)
}
resp.Info = info
}
uid, username := getUserFromImage(status.User)
if uid != nil {
resp.Image.Uid = &types.Int64Value{Value: *uid}
}
resp.Image.Username = username
log.Infof(ctx, "Image status: %v", resp)
return resp, nil
}
// storageImageStatus calls ImageStatus for a k8s ImageSpec.
// Returns (nil, nil) if image was not found.
func (s *Server) storageImageStatus(ctx context.Context, spec types.ImageSpec) (*pkgstorage.ImageResult, error) {
if id := s.StorageImageServer().HeuristicallyTryResolvingStringAsIDPrefix(spec.Image); id != nil {
status, err := s.StorageImageServer().ImageStatusByID(s.config.SystemContext, *id)
if err != nil {
if errors.Is(err, istorage.ErrNoSuchImage) {
log.Infof(ctx, "Image %s not found", spec.Image)
return nil, nil
}
log.Warnf(ctx, "Error getting status from %s: %v", spec.Image, err)
return nil, err
}
return status, nil
}
potentialMatches, err := s.StorageImageServer().CandidatesForPotentiallyShortImageName(s.config.SystemContext, spec.Image)
if err != nil {
return nil, err
}
var lastErr error
for _, name := range potentialMatches {
status, err := s.StorageImageServer().ImageStatusByName(s.config.SystemContext, name)
if err != nil {
if errors.Is(err, istorage.ErrNoSuchImage) {
log.Debugf(ctx, "Can't find %s", name)
continue
}
log.Warnf(ctx, "Error getting status from %s: %v", name, err)
lastErr = err
continue
}
return status, nil
}
if lastErr != nil {
return nil, lastErr
}
// CandidatesForPotentiallyShortImageName returns at least one value if it doesn't fail.
// So, if we got here, there was at least one ErrNoSuchImage, and no other errors.
log.Infof(ctx, "Image %s not found", spec.Image)
return nil, nil
}
// getUserFromImage gets uid or user name of the image user.
// If user is numeric, it will be treated as uid; or else, it is treated as user name.
func getUserFromImage(user string) (id *int64, username string) {
// return both empty if user is not specified in the image.
if user == "" {
return nil, ""
}
// split instances where the id may contain user:group
user = strings.Split(user, ":")[0]
// user could be either uid or user name. Try to interpret as numeric uid.
uid, err := strconv.ParseInt(user, 10, 64)
if err != nil {
// If user is non numeric, assume it's user name.
return nil, user
}
// If user is a numeric uid.
return &uid, ""
}
func createImageInfo(result *pkgstorage.ImageResult) (map[string]string, error) {
info := struct {
Labels map[string]string `json:"labels,omitempty"`
ImageSpec *specs.Image `json:"imageSpec"`
}{
result.Labels,
result.OCIConfig,
}
bytes, err := json.Marshal(info)
if err != nil {
return nil, fmt.Errorf("marshal data: %v: %w", info, err)
}
return map[string]string{"info": string(bytes)}, nil
}