@@ -42,19 +42,22 @@ import (
4242)
4343
4444type ProjectService struct {
45- db * database.DB
46- settingsService * SettingsService
47- eventService * EventService
48- imageService * ImageService
49- dockerService * DockerClientService
50- buildService * BuildService
51- config * config.Config
45+ db * database.DB
46+ settingsService * SettingsService
47+ eventService * EventService
48+ imageService * ImageService
49+ dockerService * DockerClientService
50+ buildService * BuildService
51+ config * config.Config
52+ registryCredentialsProvider registryCredentialsProviderInternal
5253
5354 composeNameCacheMu sync.RWMutex
5455 composeNameToProjID map [string ]string
5556 composeCache * cache.KeyedCache [string , composeCacheEntry ]
5657}
5758
59+ type registryCredentialsProviderInternal func (context.Context ) ([]containerregistry.Credential , error )
60+
5861type composeCacheEntry struct {
5962 composePath string
6063 composeMtime time.Time
@@ -75,6 +78,27 @@ func NewProjectService(db *database.DB, settingsService *SettingsService, eventS
7578 }
7679}
7780
81+ func (s * ProjectService ) WithRegistryCredentialsProvider (provider func (context.Context ) ([]containerregistry.Credential , error )) * ProjectService {
82+ if s == nil {
83+ return nil
84+ }
85+ s .registryCredentialsProvider = provider
86+ return s
87+ }
88+
89+ func (s * ProjectService ) resolveRegistryCredentialsInternal (ctx context.Context ) ([]containerregistry.Credential , error ) {
90+ if s == nil || s .registryCredentialsProvider == nil {
91+ return nil , nil
92+ }
93+
94+ credentials , err := s .registryCredentialsProvider (ctx )
95+ if err != nil {
96+ return nil , fmt .Errorf ("get enabled registry credentials: %w" , err )
97+ }
98+
99+ return credentials , nil
100+ }
101+
78102func (s * ProjectService ) getPathMapper (ctx context.Context ) * projects.PathMapper {
79103 configuredPath := s .settingsService .GetStringSetting (ctx , "projectsDirectory" , "/app/data/projects" )
80104
@@ -203,7 +227,6 @@ const (
203227)
204228
205229var (
206- composePullProjectServicesInternal = projects .ComposePull
207230 composeStopProjectServicesInternal = projects .ComposeStop
208231 composeUpProjectServicesInternal = projects .ComposeUp
209232)
@@ -686,17 +709,29 @@ func (s *ProjectService) reconcilePulledImageRefsInternal(ctx context.Context, i
686709 }
687710}
688711
689- func (s * ProjectService ) composePullSelectedServicesInternal (ctx context.Context , compProj * composetypes.Project , servicesToUpdate []string ) error {
712+ func (s * ProjectService ) composePullSelectedServicesInternal (
713+ ctx context.Context ,
714+ compProj * composetypes.Project ,
715+ servicesToUpdate []string ,
716+ user models.User ,
717+ credentials []containerregistry.Credential ,
718+ ) error {
690719 if compProj == nil {
691720 return nil
692721 }
693722
694- imageRefsToReconcile := buildSelectedProjectImageRefsInternal (compProj , servicesToUpdate )
695- if err := composePullProjectServicesInternal (ctx , compProj , servicesToUpdate ); err != nil {
696- return err
723+ imageRefsToPull := buildSelectedProjectImageRefsInternal (compProj , servicesToUpdate )
724+ if len (imageRefsToPull ) == 0 {
725+ return nil
726+ }
727+
728+ progressWriter , _ := ctx .Value (projects.ProgressWriterKey {}).(io.Writer )
729+ for _ , imageRef := range imageRefsToPull {
730+ if err := s .pullAndReconcileImageInternal (ctx , imageRef , progressWriter , user , credentials ); err != nil {
731+ return err
732+ }
697733 }
698734
699- s .reconcilePulledImageRefsInternal (ctx , imageRefsToReconcile )
700735 return nil
701736}
702737
@@ -707,6 +742,10 @@ func (s *ProjectService) pullAndReconcileImageInternal(
707742 user models.User ,
708743 credentials []containerregistry.Credential ,
709744) error {
745+ if s == nil || s .imageService == nil {
746+ return errors .New ("image service not available" )
747+ }
748+
710749 settings := s .settingsService .GetSettingsConfig ()
711750
712751 pullCtx , pullCancel := timeouts .WithTimeout (ctx , settings .DockerImagePullTimeout .AsInt (), timeouts .DefaultDockerImagePull )
@@ -761,9 +800,17 @@ func (s *ProjectService) UpdateProjectServices(ctx context.Context, projectID st
761800 return err
762801 }
763802
803+ credentials , err := s .resolveRegistryCredentialsInternal (ctx )
804+ if err != nil {
805+ if statusErr := s .updateProjectStatusInternal (ctx , projectID , previousStatus ); statusErr != nil {
806+ slog .ErrorContext (ctx , "UpdateProjectServices: failed to restore project status after credential lookup failure" , "projectID" , projectID , "error" , statusErr )
807+ }
808+ return fmt .Errorf ("resolve registry credentials: %w" , err )
809+ }
810+
764811 // 3. Pull images for specific services
765812 writeProjectProgressInternal (ctx , "Pulling updated service images" , 20 , "pull" )
766- if err := s .composePullSelectedServicesInternal (ctx , compProj , servicesToUpdate ); err != nil {
813+ if err := s .composePullSelectedServicesInternal (ctx , compProj , servicesToUpdate , user , credentials ); err != nil {
767814 if statusErr := s .updateProjectStatusInternal (ctx , projectID , previousStatus ); statusErr != nil {
768815 slog .ErrorContext (ctx , "UpdateProjectServices: failed to restore project status after compose pull failure" , "projectID" , projectID , "error" , statusErr )
769816 }
0 commit comments