mirror of
https://github.com/hauler-dev/hauler.git
synced 2026-05-06 01:08:05 +00:00
add ability to add images from local docker daemon (#551)
signed-off-by: Adam Martin <adam.martin@ranchergovernment.com>
This commit is contained in:
@@ -355,7 +355,10 @@ func addStoreAddImage(rso *flags.StoreRootOpts, ro *flags.CliRootOpts) *cobra.Co
|
||||
hauler store add image rgcrprod.azurecr.us/rancher/rke2-runtime:v1.31.5-rke2r1 --platform linux/amd64 --key carbide-key.pub
|
||||
|
||||
# fetch image and rewrite path
|
||||
hauler store add image busybox --rewrite custom-path/busybox:latest`,
|
||||
hauler store add image busybox --rewrite custom-path/busybox:latest
|
||||
|
||||
# add image from local Docker daemon
|
||||
hauler store add image my-local-app:latest --local`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
@@ -69,6 +69,17 @@ func AddImageCmd(ctx context.Context, o *flags.AddImageOpts, s *store.Layout, re
|
||||
cfg := v1.Image{
|
||||
Name: reference,
|
||||
Rewrite: o.Rewrite,
|
||||
Local: o.Local,
|
||||
}
|
||||
|
||||
if o.Local {
|
||||
if o.Key != "" || o.CertIdentity != "" || o.CertIdentityRegexp != "" {
|
||||
return fmt.Errorf("--local cannot be combined with cosign verification flags (--key, --certificate-identity, --certificate-identity-regexp): signatures are not available from the Docker daemon")
|
||||
}
|
||||
if o.Platform != "" {
|
||||
l.Warnf("--platform is ignored when --local is set: the Docker daemon stores only the host platform image")
|
||||
}
|
||||
return storeLocalImage(ctx, s, cfg, rso, ro, o.Rewrite)
|
||||
}
|
||||
|
||||
// Check if the user provided a key.
|
||||
@@ -95,6 +106,60 @@ func AddImageCmd(ctx context.Context, o *flags.AddImageOpts, s *store.Layout, re
|
||||
return storeImage(ctx, s, cfg, o.Platform, o.ExcludeExtras, rso, ro, o.Rewrite)
|
||||
}
|
||||
|
||||
func storeLocalImage(ctx context.Context, s *store.Layout, i v1.Image, _ *flags.StoreRootOpts, ro *flags.CliRootOpts, rewrite string) error {
|
||||
l := log.FromContext(ctx)
|
||||
|
||||
if !ro.IgnoreErrors {
|
||||
envVar := os.Getenv(consts.HaulerIgnoreErrors)
|
||||
if envVar == "true" {
|
||||
ro.IgnoreErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
l.Infof("adding image [%s] from local Docker daemon to the store", i.Name)
|
||||
|
||||
r, err := name.ParseReference(i.Name)
|
||||
if err != nil {
|
||||
if ro.IgnoreErrors {
|
||||
l.Warnf("unable to parse image [%s]: %v... skipping...", i.Name, err)
|
||||
return nil
|
||||
}
|
||||
l.Errorf("unable to parse image [%s]: %v", i.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.AddLocalImage(ctx, r.Name()); err != nil {
|
||||
if ro.IgnoreErrors {
|
||||
l.Warnf("unable to add image [%s] from Docker daemon to store: %v... skipping...", r.Name(), err)
|
||||
return nil
|
||||
}
|
||||
l.Errorf("unable to add image [%s] from Docker daemon to store: %v", r.Name(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
if rewrite != "" {
|
||||
rawRewrite := rewrite
|
||||
rewrite = strings.TrimPrefix(rewrite, "/")
|
||||
if !strings.Contains(rewrite, ":") {
|
||||
if tag, ok := r.(name.Tag); ok {
|
||||
rewrite = rewrite + ":" + tag.TagStr()
|
||||
} else {
|
||||
return fmt.Errorf("cannot rewrite digest reference [%s] without an explicit tag in the rewrite", r.Name())
|
||||
}
|
||||
}
|
||||
newRef, err := name.ParseReference(rewrite)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse rewrite name [%s]: %w", rewrite, err)
|
||||
}
|
||||
if err := rewriteReference(ctx, s, r, newRef, rawRewrite); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
l.Infof("successfully added image [%s] from local Docker daemon", r.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeImage(ctx context.Context, s *store.Layout, i v1.Image, platform string, excludeExtras bool, rso *flags.StoreRootOpts, ro *flags.CliRootOpts, rewrite string) error {
|
||||
l := log.FromContext(ctx)
|
||||
|
||||
|
||||
@@ -855,3 +855,85 @@ func TestStoreChart_AddImages_IncludeExtras(t *testing.T) {
|
||||
assertArtifactKindInStore(t, s, "test/chart-image:v2", consts.KindAnnotationSboms)
|
||||
})
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// --local flag validation tests
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func TestAddImageCmd_LocalFlagValidation(t *testing.T) {
|
||||
ctx := newTestContext(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *flags.AddImageOpts
|
||||
}{
|
||||
{
|
||||
name: "Local with Key returns error",
|
||||
opts: &flags.AddImageOpts{
|
||||
Local: true,
|
||||
Key: "some.pub",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Local with CertIdentity returns error",
|
||||
opts: &flags.AddImageOpts{
|
||||
Local: true,
|
||||
CertIdentity: "foo@bar.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Local with CertIdentityRegexp returns error",
|
||||
opts: &flags.AddImageOpts{
|
||||
Local: true,
|
||||
CertIdentityRegexp: ".*",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
rso := defaultRootOpts(s.Root)
|
||||
ro := defaultCliOpts()
|
||||
|
||||
err := AddImageCmd(ctx, tc.opts, s, "nginx:latest", rso, ro)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "--local cannot be combined") {
|
||||
t.Errorf("expected '--local cannot be combined' in error, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// storeLocalImage unit tests
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func TestStoreLocalImage_InvalidReference(t *testing.T) {
|
||||
ctx := newTestContext(t)
|
||||
|
||||
t.Run("malformed reference returns error", func(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
rso := defaultRootOpts(s.Root)
|
||||
ro := defaultCliOpts()
|
||||
|
||||
err := storeLocalImage(ctx, s, v1.Image{Name: "INVALID:::ref"}, rso, ro, "")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for malformed reference, got nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("malformed reference with IgnoreErrors returns nil", func(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
rso := defaultRootOpts(s.Root)
|
||||
ro := defaultCliOpts()
|
||||
ro.IgnoreErrors = true
|
||||
|
||||
err := storeLocalImage(ctx, s, v1.Image{Name: "INVALID:::ref"}, rso, ro, "")
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil with IgnoreErrors=true, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
|
||||
a := cfg.GetAnnotations()
|
||||
for _, i := range cfg.Spec.Images {
|
||||
|
||||
if a[consts.ImageAnnotationRegistry] != "" || o.Registry != "" {
|
||||
if !i.Local && (a[consts.ImageAnnotationRegistry] != "" || o.Registry != "") {
|
||||
newRef, _ := reference.Parse(i.Name)
|
||||
newReg := o.Registry
|
||||
if o.Registry == "" && a[consts.ImageAnnotationRegistry] != "" {
|
||||
@@ -331,6 +331,25 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
|
||||
i.Name = newRef.Name()
|
||||
}
|
||||
|
||||
if i.Local {
|
||||
needsPubKeyVerification := a[consts.ImageAnnotationKey] != "" || o.Key != "" || i.Key != ""
|
||||
needsKeylessVerification := a[consts.ImageAnnotationCertIdentityRegexp] != "" || a[consts.ImageAnnotationCertIdentity] != "" ||
|
||||
o.CertIdentityRegexp != "" || o.CertIdentity != "" ||
|
||||
i.CertIdentityRegexp != "" || i.CertIdentity != ""
|
||||
if needsPubKeyVerification || needsKeylessVerification {
|
||||
return fmt.Errorf("image [%s]: --local cannot be combined with cosign verification options", i.Name)
|
||||
}
|
||||
|
||||
rewrite := ""
|
||||
if i.Rewrite != "" {
|
||||
rewrite = i.Rewrite
|
||||
}
|
||||
if err := storeLocalImage(ctx, s, i, rso, ro, rewrite); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
hasAnnotationIdentityOptions := a[consts.ImageAnnotationCertIdentityRegexp] != "" || a[consts.ImageAnnotationCertIdentity] != ""
|
||||
hasCliIdentityOptions := o.CertIdentityRegexp != "" || o.CertIdentity != ""
|
||||
hasImageIdentityOptions := i.CertIdentityRegexp != "" || i.CertIdentity != ""
|
||||
|
||||
6
go.mod
6
go.mod
@@ -112,6 +112,7 @@ require (
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.2 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect
|
||||
@@ -123,11 +124,15 @@ require (
|
||||
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
|
||||
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/cli v29.2.0+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v28.5.2+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.4 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
@@ -224,6 +229,7 @@ require (
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
||||
18
go.sum
18
go.sum
@@ -277,6 +277,8 @@ github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34Pl
|
||||
github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
|
||||
@@ -318,18 +320,26 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
|
||||
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI=
|
||||
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
@@ -706,10 +716,16 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/5
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
@@ -724,6 +740,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mozillazg/docker-credential-acr-helper v0.4.0 h1:Uoh3Z9CcpEDnLiozDx+D7oDgRq7X+R296vAqAumnOcw=
|
||||
github.com/mozillazg/docker-credential-acr-helper v0.4.0/go.mod h1:2kiicb3OlPytmlNC9XGkLvVC+f0qTiJw3f/mhmeeQBg=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
|
||||
@@ -18,6 +18,7 @@ type AddImageOpts struct {
|
||||
Platform string
|
||||
Rewrite string
|
||||
ExcludeExtras bool
|
||||
Local bool
|
||||
}
|
||||
|
||||
func (o *AddImageOpts) AddFlags(cmd *cobra.Command) {
|
||||
@@ -32,6 +33,7 @@ func (o *AddImageOpts) AddFlags(cmd *cobra.Command) {
|
||||
f.StringVarP(&o.Platform, "platform", "p", "", "(Optional) Specify the platform of the image... i.e. linux/amd64 (defaults to all)")
|
||||
f.StringVar(&o.Rewrite, "rewrite", "", "(EXPERIMENTAL & Optional) Rewrite artifact path to specified string")
|
||||
f.BoolVar(&o.ExcludeExtras, "exclude-extras", false, "(Optional) Exclude cosign signatures, attestations, SBOMs, and OCI referrers when pulling the image")
|
||||
f.BoolVar(&o.Local, "local", false, "(Optional) Add image from the local Docker daemon instead of a remote registry")
|
||||
}
|
||||
|
||||
type AddFileOpts struct {
|
||||
|
||||
@@ -39,4 +39,5 @@ type Image struct {
|
||||
Platform string `json:"platform"`
|
||||
Rewrite string `json:"rewrite"`
|
||||
ExcludeExtras bool `json:"exclude-extras"`
|
||||
Local bool `json:"local"`
|
||||
}
|
||||
|
||||
70
pkg/store/ensure_docker_host_test.go
Normal file
70
pkg/store/ensure_docker_host_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnsureDockerHost(t *testing.T) {
|
||||
t.Run("DOCKER_HOST already set", func(t *testing.T) {
|
||||
t.Setenv("DOCKER_HOST", "unix:///some/custom/docker.sock")
|
||||
|
||||
err := ensureDockerHost()
|
||||
if err != nil {
|
||||
t.Errorf("ensureDockerHost() returned unexpected error: %v", err)
|
||||
}
|
||||
if got := os.Getenv("DOCKER_HOST"); got != "unix:///some/custom/docker.sock" {
|
||||
t.Errorf("DOCKER_HOST was modified: got %q, want %q", got, "unix:///some/custom/docker.sock")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no socket found returns error", func(t *testing.T) {
|
||||
if _, err := os.Stat("/var/run/docker.sock"); err == nil {
|
||||
t.Skip("default docker socket exists at /var/run/docker.sock")
|
||||
}
|
||||
|
||||
tmp := t.TempDir()
|
||||
t.Setenv("DOCKER_HOST", "")
|
||||
t.Setenv("HOME", tmp)
|
||||
|
||||
err := ensureDockerHost()
|
||||
if err == nil {
|
||||
t.Fatal("ensureDockerHost() expected error when no socket exists, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no Docker socket found") {
|
||||
t.Errorf("error %q does not contain %q", err.Error(), "no Docker socket found")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sets DOCKER_HOST for Docker Desktop socket", func(t *testing.T) {
|
||||
if _, err := os.Stat("/var/run/docker.sock"); err == nil {
|
||||
t.Skip("default docker socket exists at /var/run/docker.sock")
|
||||
}
|
||||
|
||||
tmp := t.TempDir()
|
||||
t.Setenv("DOCKER_HOST", "")
|
||||
t.Setenv("HOME", tmp)
|
||||
|
||||
sockDir := filepath.Join(tmp, ".docker", "run")
|
||||
if err := os.MkdirAll(sockDir, 0o755); err != nil {
|
||||
t.Fatalf("creating socket dir: %v", err)
|
||||
}
|
||||
sockPath := filepath.Join(sockDir, "docker.sock")
|
||||
f, err := os.Create(sockPath)
|
||||
if err != nil {
|
||||
t.Fatalf("creating socket file: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
if err := ensureDockerHost(); err != nil {
|
||||
t.Fatalf("ensureDockerHost() unexpected error: %v", err)
|
||||
}
|
||||
|
||||
want := "unix://" + sockPath
|
||||
if got := os.Getenv("DOCKER_HOST"); got != want {
|
||||
t.Errorf("DOCKER_HOST = %q, want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
gname "github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/static"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@@ -216,6 +217,56 @@ func (l *Layout) AddImage(ctx context.Context, ref string, platform string, excl
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLocalImage fetches a container image from the local Docker daemon and saves it to the store.
|
||||
// No cosign signatures, attestations, SBOMs, or OCI referrers are fetched (registry-only concepts).
|
||||
func (l *Layout) AddLocalImage(ctx context.Context, ref string) error {
|
||||
parsedRef, err := gname.ParseReference(ref)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing reference %q: %w", ref, err)
|
||||
}
|
||||
|
||||
if err := ensureDockerHost(); err != nil {
|
||||
return fmt.Errorf("failed to locate Docker daemon socket: %w -- is the Docker daemon running?", err)
|
||||
}
|
||||
|
||||
img, err := daemon.Image(parsedRef, daemon.WithContext(ctx))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch image from Docker daemon: %w -- is the Docker daemon running?", err)
|
||||
}
|
||||
|
||||
if _, err := img.Digest(); err != nil {
|
||||
return fmt.Errorf("getting image digest for %q: %w", ref, err)
|
||||
}
|
||||
|
||||
return l.writeImage(parsedRef, img, consts.KindAnnotationImage, "")
|
||||
}
|
||||
|
||||
// ensureDockerHost sets DOCKER_HOST if it is not already set and the default
|
||||
// socket (/var/run/docker.sock) does not exist. Docker Desktop on macOS places
|
||||
// its socket at ~/.docker/run/docker.sock instead of the default path.
|
||||
func ensureDockerHost() error {
|
||||
if os.Getenv("DOCKER_HOST") != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat("/var/run/docker.sock"); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sock := filepath.Join(home, ".docker", "run", "docker.sock")
|
||||
if _, err := os.Stat(sock); err != nil {
|
||||
return fmt.Errorf("no Docker socket found at /var/run/docker.sock or %s", sock)
|
||||
}
|
||||
if err := os.Setenv("DOCKER_HOST", "unix://"+sock); err != nil {
|
||||
return fmt.Errorf("setting DOCKER_HOST: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeImageBlobs writes all blobs for a single image (layers, config, manifest) to the store's
|
||||
// blob directory. It does not add an entry to the OCI index.
|
||||
func (l *Layout) writeImageBlobs(img v1.Image) error {
|
||||
|
||||
Reference in New Issue
Block a user