|
8 | 8 | "sort" |
9 | 9 | "time" |
10 | 10 |
|
| 11 | + "github.com/distribution/reference" |
11 | 12 | "github.com/onkernel/hypeman/lib/oapi" |
12 | 13 | ) |
13 | 14 |
|
@@ -60,23 +61,28 @@ func (m *manager) ListImages(ctx context.Context) ([]oapi.Image, error) { |
60 | 61 | } |
61 | 62 |
|
62 | 63 | func (m *manager) CreateImage(ctx context.Context, req oapi.CreateImageRequest) (*oapi.Image, error) { |
63 | | - if imageExists(m.dataDir, req.Name) { |
| 64 | + normalizedName, err := normalizeImageName(req.Name) |
| 65 | + if err != nil { |
| 66 | + return nil, fmt.Errorf("invalid image name: %w", err) |
| 67 | + } |
| 68 | + |
| 69 | + if imageExists(m.dataDir, normalizedName) { |
64 | 70 | return nil, ErrAlreadyExists |
65 | 71 | } |
66 | 72 |
|
67 | 73 | meta := &imageMetadata{ |
68 | | - Name: req.Name, |
| 74 | + Name: normalizedName, |
69 | 75 | Status: StatusPending, |
70 | 76 | Request: &req, |
71 | 77 | CreatedAt: time.Now(), |
72 | 78 | } |
73 | 79 |
|
74 | | - if err := writeMetadata(m.dataDir, req.Name, meta); err != nil { |
| 80 | + if err := writeMetadata(m.dataDir, normalizedName, meta); err != nil { |
75 | 81 | return nil, fmt.Errorf("write initial metadata: %w", err) |
76 | 82 | } |
77 | 83 |
|
78 | | - queuePos := m.queue.Enqueue(req.Name, req, func() { |
79 | | - m.buildImage(context.Background(), req.Name, req) |
| 84 | + queuePos := m.queue.Enqueue(normalizedName, req, func() { |
| 85 | + m.buildImage(context.Background(), normalizedName, req) |
80 | 86 | }) |
81 | 87 |
|
82 | 88 | img := meta.toOAPI() |
@@ -179,22 +185,45 @@ func (m *manager) RecoverInterruptedBuilds() { |
179 | 185 | } |
180 | 186 |
|
181 | 187 | func (m *manager) GetImage(ctx context.Context, name string) (*oapi.Image, error) { |
182 | | - meta, err := readMetadata(m.dataDir, name) |
| 188 | + normalizedName, err := normalizeImageName(name) |
| 189 | + if err != nil { |
| 190 | + return nil, fmt.Errorf("invalid image name: %w", err) |
| 191 | + } |
| 192 | + |
| 193 | + meta, err := readMetadata(m.dataDir, normalizedName) |
183 | 194 | if err != nil { |
184 | 195 | return nil, err |
185 | 196 | } |
186 | 197 |
|
187 | 198 | img := meta.toOAPI() |
188 | 199 |
|
189 | 200 | if meta.Status == StatusPending { |
190 | | - img.QueuePosition = m.queue.GetPosition(name) |
| 201 | + img.QueuePosition = m.queue.GetPosition(normalizedName) |
191 | 202 | } |
192 | 203 |
|
193 | 204 | return img, nil |
194 | 205 | } |
195 | 206 |
|
196 | 207 | func (m *manager) DeleteImage(ctx context.Context, name string) error { |
197 | | - return deleteImage(m.dataDir, name) |
| 208 | + normalizedName, err := normalizeImageName(name) |
| 209 | + if err != nil { |
| 210 | + return fmt.Errorf("invalid image name: %w", err) |
| 211 | + } |
| 212 | + return deleteImage(m.dataDir, normalizedName) |
| 213 | +} |
| 214 | + |
| 215 | +// normalizeImageName validates and normalizes an OCI image reference |
| 216 | +// Examples: alpine → docker.io/library/alpine:latest |
| 217 | +// nginx:1.0 → docker.io/library/nginx:1.0 |
| 218 | +func normalizeImageName(name string) (string, error) { |
| 219 | + named, err := reference.ParseNormalizedNamed(name) |
| 220 | + if err != nil { |
| 221 | + return "", err |
| 222 | + } |
| 223 | + |
| 224 | + // Ensure it has a tag (add :latest if missing) |
| 225 | + tagged := reference.TagNameOnly(named) |
| 226 | + return tagged.String(), nil |
198 | 227 | } |
199 | 228 |
|
200 | 229 |
|
0 commit comments