99 "os"
1010 "path/filepath"
1111 "regexp"
12+ "strconv"
1213 "strings"
1314 "sync/atomic"
1415 "time"
@@ -28,6 +29,9 @@ import (
2829 "oras.land/oras-go/v2/registry/remote/retry"
2930)
3031
32+ const annotationDruidFileMode = "gg.druid.file.mode"
33+ const chmodModeMask = os .ModePerm | os .ModeSetuid | os .ModeSetgid | os .ModeSticky
34+
3135type OciClient struct {
3236 credentialStore * CredentialStore
3337 // httpClient optionally overrides the HTTP client used by GetRepo.
@@ -240,6 +244,9 @@ func (c *OciClient) PullSelective(dir string, artifact string, includeData bool,
240244 zap .String ("digest" , desc .Digest .String ()),
241245 zap .String ("progress" , fmt .Sprintf ("%d/%d" , done , total )),
242246 )
247+ if err := applyDescriptorMode (dir , desc ); err != nil {
248+ return err
249+ }
243250 return nil
244251 },
245252 OnCopySkipped : func (ctx context.Context , desc v1.Descriptor ) error {
@@ -337,7 +344,7 @@ func (c *OciClient) CanUpdateTag(current v1.Descriptor, r string, tag string) (b
337344 return false , nil
338345}
339346
340- func (c * OciClient ) packFolders (fs * file.Store , dirs []string , artifactType domain.ArtifactType , path string ) ([]v1.Descriptor , error ) {
347+ func (c * OciClient ) packFolders (fs * file.Store , folder string , dirs []string , artifactType domain.ArtifactType , path string ) ([]v1.Descriptor , error ) {
341348 ctx := context .Background ()
342349
343350 fileDescriptors := make ([]v1.Descriptor , 0 , len (dirs ))
@@ -352,6 +359,7 @@ func (c *OciClient) packFolders(fs *file.Store, dirs []string, artifactType doma
352359 if err != nil {
353360 return []v1.Descriptor {}, err
354361 }
362+ annotateDescriptorMode (folder , fullPath , & fileDescriptor )
355363 fileDescriptors = append (fileDescriptors , fileDescriptor )
356364 logger .Log ().Info ("Packed layer" ,
357365 zap .String ("path" , fullPath ),
@@ -362,6 +370,41 @@ func (c *OciClient) packFolders(fs *file.Store, dirs []string, artifactType doma
362370 return fileDescriptors , nil
363371}
364372
373+ func annotateDescriptorMode (folder string , layerPath string , desc * v1.Descriptor ) {
374+ info , err := os .Stat (filepath .Join (folder , filepath .FromSlash (layerPath )))
375+ if err != nil {
376+ return
377+ }
378+ if desc .Annotations == nil {
379+ desc .Annotations = map [string ]string {}
380+ }
381+ desc .Annotations [annotationDruidFileMode ] = strconv .FormatUint (uint64 (info .Mode ()& chmodModeMask ), 8 )
382+ }
383+
384+ func applyDescriptorMode (root string , desc v1.Descriptor ) error {
385+ modeValue := desc .Annotations [annotationDruidFileMode ]
386+ if modeValue == "" {
387+ return nil
388+ }
389+
390+ path := desc .Annotations ["org.opencontainers.image.path" ]
391+ if path == "" {
392+ path = desc .Annotations ["org.opencontainers.image.title" ]
393+ }
394+ if path == "" {
395+ return nil
396+ }
397+
398+ mode , err := strconv .ParseUint (modeValue , 8 , 32 )
399+ if err != nil {
400+ return fmt .Errorf ("invalid descriptor file mode %q for %s: %w" , modeValue , path , err )
401+ }
402+ if err := os .Chmod (filepath .Join (root , filepath .FromSlash (path )), os .FileMode (mode )); err != nil {
403+ return fmt .Errorf ("failed to chmod %s to %s: %w" , path , modeValue , err )
404+ }
405+ return nil
406+ }
407+
365408// DefaultCategoryMarkdownPattern matches locale markdown basenames such as de-DE.md (used by PushCategory).
366409const DefaultCategoryMarkdownPattern = `^[a-z]{2}-[A-Z]{2}\.md$`
367410
@@ -413,7 +456,7 @@ func (c *OciClient) createMetaDescriptors(fs *file.Store, folder string, fsPath
413456 return nil , fmt .Errorf ("no files matching pattern %q in %s" , namePattern , metaPath )
414457 }
415458
416- return c .packFolders (fs , names , domain .ArtifactTypeScrollMeta , fsPath )
459+ return c .packFolders (fs , folder , names , domain .ArtifactTypeScrollMeta , fsPath )
417460}
418461
419462// pushBlobWithRetry pushes a single blob from src to dst, retrying up to
@@ -584,7 +627,7 @@ func (c *OciClient) Push(folder string, repo string, tag string, overrides map[s
584627 defer fs .Close ()
585628
586629 // Pack scroll FS layers.
587- descriptorsForRoot , err := c .packFolders (fs , fsFileNames , domain .ArtifactTypeScrollFs , "" )
630+ descriptorsForRoot , err := c .packFolders (fs , folder , fsFileNames , domain .ArtifactTypeScrollFs , "" )
588631 if err != nil {
589632 return v1.Descriptor {}, err
590633 }
@@ -641,6 +684,7 @@ func (c *OciClient) Push(folder string, repo string, tag string, overrides map[s
641684 if err != nil {
642685 return v1.Descriptor {}, fmt .Errorf ("failed to pack data chunk %s: %w" , chunk .Name , err )
643686 }
687+ annotateDescriptorMode (folder , layerPath , & desc )
644688 logger .Log ().Info ("Packed layer" ,
645689 zap .String ("path" , layerPath ),
646690 zap .String ("digest" , desc .Digest .String ()),
0 commit comments