(_ context.Context, w io.Writer, digest string)
| 206 | } |
| 207 | |
| 208 | func (b *Backend) Download(_ context.Context, w io.Writer, digest string) error { |
| 209 | if digest == "" { |
| 210 | return errors.New("digest is empty") |
| 211 | } |
| 212 | |
| 213 | ref, err := name.ParseReference(b.resourcePath(digest)) |
| 214 | if err != nil { |
| 215 | return fmt.Errorf("parsing reference: %w", err) |
| 216 | } |
| 217 | |
| 218 | img, err := remote.Image(ref, remote.WithAuthFromKeychain(b.keychain)) |
| 219 | if err != nil { |
| 220 | return fmt.Errorf("getting image: %w", err) |
| 221 | } |
| 222 | |
| 223 | if err := validateImage(img, digest); err != nil { |
| 224 | return fmt.Errorf("validating image: %w", err) |
| 225 | } |
| 226 | |
| 227 | // Download the layer with the same digest, not relying on the image name |
| 228 | l, err := img.LayerByDiffID(v1.Hash{Algorithm: "sha256", Hex: digest}) |
| 229 | if err != nil { |
| 230 | return fmt.Errorf("getting layer with hash sha256:%s: %w", digest, err) |
| 231 | } |
| 232 | |
| 233 | // Do not uncompress since we want the raw stored data |
| 234 | rc, err := l.Compressed() |
| 235 | if err != nil { |
| 236 | return fmt.Errorf("extracting data from layer: %w", err) |
| 237 | } |
| 238 | |
| 239 | defer rc.Close() |
| 240 | // 1MB buffer |
| 241 | buf := make([]byte, 1<<20) |
| 242 | _, err = io.CopyBuffer(w, rc, buf) |
| 243 | if err != nil { |
| 244 | return fmt.Errorf("copying data from layer: %w", err) |
| 245 | } |
| 246 | |
| 247 | return nil |
| 248 | } |
| 249 | |
| 250 | // validateImage checks that the image was crafted by chainloop and contains the expected content |
| 251 | func validateImage(img v1.Image, digest string) error { |
nothing calls this directly
no test coverage detected