Skip to content

Commit 9ac1291

Browse files
committed
fix: decouple context for activities (#2754)
1 parent e3bcd0b commit 9ac1291

27 files changed

Lines changed: 519 additions & 150 deletions

backend/api/api.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package api
22

33
import (
4+
"context"
45
"net/http"
56
"reflect"
67
"strings"
@@ -138,6 +139,7 @@ func capitalizeFirst(s string) string {
138139

139140
// Services holds all service dependencies needed by Huma handlers.
140141
type Services struct {
142+
AppContext context.Context
141143
User *services.UserService
142144
Auth *services.AuthService
143145
Oidc *services.OidcService
@@ -347,9 +349,11 @@ func registerHandlers(api huma.API, svc *Services) {
347349
var vulnerabilitySvc *services.VulnerabilityService
348350
var dashboardSvc *services.DashboardService
349351
var roleSvc *services.RoleService
352+
var appCtx context.Context
350353
var cfg *config.Config
351354

352355
if svc != nil {
356+
appCtx = svc.AppContext
353357
userSvc = svc.User
354358
authSvc = svc.Auth
355359
oidcSvc = svc.Oidc
@@ -388,37 +392,38 @@ func registerHandlers(api huma.API, svc *Services) {
388392
roleSvc = svc.Role
389393
cfg = svc.Config
390394
}
395+
handlerAppCtx := handlers.NewActivityAppContext(appCtx)
391396
handlers.RegisterHealth(api)
392397
handlers.RegisterAuth(api, userSvc, authSvc, oidcSvc)
393398
handlers.RegisterApiKeys(api, apiKeySvc)
394399
handlers.RegisterRoles(api, roleSvc)
395400
handlers.RegisterAppImages(api, appImagesSvc)
396401
handlers.RegisterUsers(api, userSvc, authSvc)
397-
handlers.RegisterProjects(api, projectSvc, activitySvc)
402+
handlers.RegisterProjects(api, projectSvc, activitySvc, handlerAppCtx)
398403
handlers.RegisterVersion(api, versionSvc)
399404
handlers.RegisterEvents(api, eventSvc, apiKeySvc)
400405
handlers.RegisterActivities(api, activitySvc, environmentSvc)
401406
handlers.RegisterOidc(api, authSvc, oidcSvc, roleSvc, userSvc, cfg)
402407
handlers.RegisterEnvironments(api, environmentSvc, settingsSvc, apiKeySvc, eventSvc, cfg)
403408
handlers.RegisterContainerRegistries(api, containerRegistrySvc, environmentSvc)
404409
handlers.RegisterTemplates(api, templateSvc, environmentSvc)
405-
handlers.RegisterImages(api, dockerSvc, imageSvc, imageUpdateSvc, settingsSvc, buildSvc, activitySvc)
410+
handlers.RegisterImages(api, dockerSvc, imageSvc, imageUpdateSvc, settingsSvc, buildSvc, activitySvc, handlerAppCtx)
406411
handlers.RegisterBuildWorkspaces(api, buildWorkspaceSvc)
407-
handlers.RegisterImageUpdates(api, imageUpdateSvc, imageSvc)
412+
handlers.RegisterImageUpdates(api, imageUpdateSvc, imageSvc, handlerAppCtx)
408413
handlers.RegisterSettings(api, settingsSvc, settingsSearchSvc, environmentSvc, cfg)
409414
handlers.RegisterJobSchedules(api, jobScheduleSvc, environmentSvc)
410-
handlers.RegisterVolumes(api, dockerSvc, volumeSvc, activitySvc)
411-
handlers.RegisterContainers(api, containerSvc, dockerSvc, settingsSvc, activitySvc)
415+
handlers.RegisterVolumes(api, dockerSvc, volumeSvc, activitySvc, handlerAppCtx)
416+
handlers.RegisterContainers(api, containerSvc, dockerSvc, settingsSvc, activitySvc, handlerAppCtx)
412417
handlers.RegisterPorts(api, portSvc)
413-
handlers.RegisterNetworks(api, networkSvc, dockerSvc, activitySvc)
418+
handlers.RegisterNetworks(api, networkSvc, dockerSvc, activitySvc, handlerAppCtx)
414419
handlers.RegisterSwarm(api, swarmSvc, environmentSvc, eventSvc, cfg)
415420
handlers.RegisterNotifications(api, notificationSvc, cfg)
416-
handlers.RegisterUpdater(api, updaterSvc)
421+
handlers.RegisterUpdater(api, updaterSvc, handlerAppCtx)
417422
handlers.RegisterCustomize(api, customizeSearchSvc)
418-
handlers.RegisterSystem(api, dockerSvc, systemSvc, systemUpgradeSvc, cfg, activitySvc)
423+
handlers.RegisterSystem(api, dockerSvc, systemSvc, systemUpgradeSvc, cfg, activitySvc, handlerAppCtx)
419424
handlers.RegisterGitRepositories(api, gitRepositorySvc)
420425
handlers.RegisterGitOpsSyncs(api, gitOpsSyncSvc)
421426
handlers.RegisterWebhooks(api, webhookSvc)
422-
handlers.RegisterVulnerability(api, vulnerabilitySvc)
427+
handlers.RegisterVulnerability(api, vulnerabilitySvc, handlerAppCtx)
423428
handlers.RegisterDashboard(api, dashboardSvc)
424429
}

backend/api/handlers/containers.go

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type ContainerHandler struct {
3030
dockerService *services.DockerClientService
3131
settingsService *services.SettingsService
3232
activityService *services.ActivityService
33+
appCtx context.Context
3334
}
3435

3536
// Paginated response
@@ -142,12 +143,13 @@ type SetAutoUpdateOutput struct {
142143
Body ContainerActionResponse
143144
}
144145

145-
func RegisterContainers(api huma.API, containerSvc *services.ContainerService, dockerSvc *services.DockerClientService, settingsSvc *services.SettingsService, activitySvc *services.ActivityService) {
146+
func RegisterContainers(api huma.API, containerSvc *services.ContainerService, dockerSvc *services.DockerClientService, settingsSvc *services.SettingsService, activitySvc *services.ActivityService, appCtx ActivityAppContext) {
146147
h := &ContainerHandler{
147148
containerService: containerSvc,
148149
dockerService: dockerSvc,
149150
settingsService: settingsSvc,
150151
activityService: activitySvc,
152+
appCtx: appCtx.contextInternal(),
151153
}
152154

153155
huma.Register(api, huma.Operation{
@@ -622,12 +624,13 @@ func (h *ContainerHandler) StartContainer(ctx context.Context, input *ContainerA
622624
return nil, huma.Error401Unauthorized("not authenticated")
623625
}
624626

625-
activityID := activitylib.StartHandlerActivityForUser(ctx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerStart, "container", input.ContainerID, input.ContainerID, user, "Starting container", "Container start requested", models.JSON{"containerID": input.ContainerID})
626-
if err := h.containerService.StartContainer(ctx, input.ContainerID, *user); err != nil {
627-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container started", err)
627+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
628+
activityID := activitylib.StartHandlerActivityForUser(runtimeCtx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerStart, "container", input.ContainerID, input.ContainerID, user, "Starting container", "Container start requested", models.JSON{"containerID": input.ContainerID})
629+
if err := h.containerService.StartContainer(runtimeCtx, input.ContainerID, *user); err != nil {
630+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container started", err)
628631
return nil, huma.Error500InternalServerError((&common.ContainerStartError{Err: err}).Error())
629632
}
630-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container started", nil)
633+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container started", nil)
631634

632635
return &ContainerActionOutput{
633636
Body: ContainerActionResponse{
@@ -647,12 +650,13 @@ func (h *ContainerHandler) StopContainer(ctx context.Context, input *ContainerAc
647650
return nil, huma.Error401Unauthorized("not authenticated")
648651
}
649652

650-
activityID := activitylib.StartHandlerActivityForUser(ctx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerStop, "container", input.ContainerID, input.ContainerID, user, "Stopping container", "Container stop requested", models.JSON{"containerID": input.ContainerID})
651-
if err := h.containerService.StopContainer(ctx, input.ContainerID, *user); err != nil {
652-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container stopped", err)
653+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
654+
activityID := activitylib.StartHandlerActivityForUser(runtimeCtx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerStop, "container", input.ContainerID, input.ContainerID, user, "Stopping container", "Container stop requested", models.JSON{"containerID": input.ContainerID})
655+
if err := h.containerService.StopContainer(runtimeCtx, input.ContainerID, *user); err != nil {
656+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container stopped", err)
653657
return nil, huma.Error500InternalServerError((&common.ContainerStopError{Err: err}).Error())
654658
}
655-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container stopped", nil)
659+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container stopped", nil)
656660

657661
return &ContainerActionOutput{
658662
Body: ContainerActionResponse{
@@ -672,12 +676,13 @@ func (h *ContainerHandler) RestartContainer(ctx context.Context, input *Containe
672676
return nil, huma.Error401Unauthorized("not authenticated")
673677
}
674678

675-
activityID := activitylib.StartHandlerActivityForUser(ctx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerRestart, "container", input.ContainerID, input.ContainerID, user, "Restarting container", "Container restart requested", models.JSON{"containerID": input.ContainerID})
676-
if err := h.containerService.RestartContainer(ctx, input.ContainerID, *user); err != nil {
677-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container restarted", err)
679+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
680+
activityID := activitylib.StartHandlerActivityForUser(runtimeCtx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerRestart, "container", input.ContainerID, input.ContainerID, user, "Restarting container", "Container restart requested", models.JSON{"containerID": input.ContainerID})
681+
if err := h.containerService.RestartContainer(runtimeCtx, input.ContainerID, *user); err != nil {
682+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container restarted", err)
678683
return nil, huma.Error500InternalServerError((&common.ContainerRestartError{Err: err}).Error())
679684
}
680-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container restarted", nil)
685+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container restarted", nil)
681686

682687
return &ContainerActionOutput{
683688
Body: ContainerActionResponse{
@@ -697,20 +702,21 @@ func (h *ContainerHandler) RedeployContainer(ctx context.Context, input *Contain
697702
return nil, huma.Error401Unauthorized("not authenticated")
698703
}
699704

700-
activityID := activitylib.StartHandlerActivityForUser(ctx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerRedeploy, "container", input.ContainerID, input.ContainerID, user, "Starting redeploy", "Container redeploy requested", models.JSON{"containerID": input.ContainerID})
701-
activityWriter := activitylib.NewWriter(ctx, h.activityService, activityID, io.Discard, "Redeploying container")
702-
redeployCtx := context.WithValue(ctx, projects.ProgressWriterKey{}, activityWriter)
705+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
706+
activityID := activitylib.StartHandlerActivityForUser(runtimeCtx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerRedeploy, "container", input.ContainerID, input.ContainerID, user, "Starting redeploy", "Container redeploy requested", models.JSON{"containerID": input.ContainerID})
707+
activityWriter := activitylib.NewWriter(runtimeCtx, h.activityService, activityID, io.Discard, "Redeploying container")
708+
redeployCtx := context.WithValue(runtimeCtx, projects.ProgressWriterKey{}, activityWriter)
703709
newContainerID, err := h.containerService.RedeployContainer(redeployCtx, input.ContainerID, *user)
704710
if err != nil {
705711
activitylib.FlushWriter(activityWriter)
706-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container redeploy failed", err)
712+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container redeploy failed", err)
707713
return nil, huma.Error500InternalServerError((&common.ContainerRedeployError{Err: err}).Error())
708714
}
709715
activitylib.FlushWriter(activityWriter)
710-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container redeployed", nil)
716+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container redeployed", nil)
711717

712718
// Fetch full container details to return (consistent with other endpoints)
713-
details, inspectErr := h.containerService.GetContainerDetails(ctx, newContainerID)
719+
details, inspectErr := h.containerService.GetContainerDetails(runtimeCtx, newContainerID)
714720
if inspectErr == nil {
715721
details.ActivityID = utils.StringPtrFromTrimmed(activityID)
716722

@@ -745,12 +751,13 @@ func (h *ContainerHandler) DeleteContainer(ctx context.Context, input *DeleteCon
745751
return nil, huma.Error401Unauthorized("not authenticated")
746752
}
747753

748-
activityID := activitylib.StartHandlerActivityForUser(ctx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerDelete, "container", input.ContainerID, input.ContainerID, user, "Deleting container", "Container delete requested", models.JSON{"containerID": input.ContainerID, "force": input.Force, "removeVolumes": input.RemoveVolumes})
749-
if err := h.containerService.DeleteContainer(ctx, input.ContainerID, input.Force, input.RemoveVolumes, *user); err != nil {
750-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container deleted", err)
754+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
755+
activityID := activitylib.StartHandlerActivityForUser(runtimeCtx, h.activityService, input.EnvironmentID, models.ActivityTypeContainerDelete, "container", input.ContainerID, input.ContainerID, user, "Deleting container", "Container delete requested", models.JSON{"containerID": input.ContainerID, "force": input.Force, "removeVolumes": input.RemoveVolumes})
756+
if err := h.containerService.DeleteContainer(runtimeCtx, input.ContainerID, input.Force, input.RemoveVolumes, *user); err != nil {
757+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container deleted", err)
751758
return nil, huma.Error500InternalServerError((&common.ContainerDeleteError{Err: err}).Error())
752759
}
753-
activitylib.CompleteHandlerActivity(ctx, h.activityService, activityID, "Container deleted", nil)
760+
activitylib.CompleteHandlerActivity(runtimeCtx, h.activityService, activityID, "Container deleted", nil)
754761

755762
return &DeleteContainerOutput{
756763
Body: ContainerActionResponse{

backend/api/handlers/helpers.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,25 @@ import (
1111
"github.com/getarcaneapp/arcane/backend/pkg/remenv"
1212
)
1313

14+
// ActivityAppContext carries the app lifecycle context through handler registration.
15+
type ActivityAppContext struct {
16+
ctx context.Context
17+
}
18+
19+
// NewActivityAppContext wraps the app lifecycle context for handler constructors.
20+
func NewActivityAppContext(ctx context.Context) ActivityAppContext {
21+
return ActivityAppContext{ctx: ctx}
22+
}
23+
24+
// ContextInternal returns the wrapped app lifecycle context.
25+
func (c ActivityAppContext) ContextInternal() context.Context {
26+
return c.contextInternal()
27+
}
28+
29+
func (c ActivityAppContext) contextInternal() context.Context {
30+
return c.ctx
31+
}
32+
1433
// buildPaginationParamsInternal converts query parameters to pagination.QueryParams.
1534
// A limit of -1 means "show all items" (no pagination).
1635
func buildPaginationParamsInternal(start, limit int, sortCol, sortDir, search string) pagination.QueryParams {

backend/api/handlers/image_updates.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/getarcaneapp/arcane/backend/internal/common"
1111
"github.com/getarcaneapp/arcane/backend/internal/services"
1212
"github.com/getarcaneapp/arcane/backend/pkg/authz"
13+
"github.com/getarcaneapp/arcane/backend/pkg/utils"
1314
"github.com/getarcaneapp/arcane/types/base"
1415
imagetypes "github.com/getarcaneapp/arcane/types/image"
1516
"github.com/getarcaneapp/arcane/types/imageupdate"
@@ -18,6 +19,7 @@ import (
1819
type ImageUpdateHandler struct {
1920
imageUpdateService *services.ImageUpdateService
2021
imageService *services.ImageService
22+
appCtx context.Context
2123
}
2224

2325
type CheckImageUpdateInput struct {
@@ -74,10 +76,11 @@ type GetUpdateSummaryOutput struct {
7476
}
7577

7678
// RegisterImageUpdates registers image update endpoints.
77-
func RegisterImageUpdates(api huma.API, imageUpdateSvc *services.ImageUpdateService, imageSvc *services.ImageService) {
79+
func RegisterImageUpdates(api huma.API, imageUpdateSvc *services.ImageUpdateService, imageSvc *services.ImageService, appCtx ActivityAppContext) {
7880
h := &ImageUpdateHandler{
7981
imageUpdateService: imageUpdateSvc,
8082
imageService: imageSvc,
83+
appCtx: appCtx.contextInternal(),
8184
}
8285

8386
huma.Register(api, huma.Operation{
@@ -156,7 +159,8 @@ func (h *ImageUpdateHandler) CheckImageUpdate(ctx context.Context, input *CheckI
156159
return nil, huma.Error400BadRequest((&common.ImageRefRequiredError{}).Error())
157160
}
158161

159-
result, err := h.imageUpdateService.CheckImageUpdate(ctx, input.ImageRef)
162+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
163+
result, err := h.imageUpdateService.CheckImageUpdate(runtimeCtx, input.ImageRef)
160164
if err != nil {
161165
return nil, huma.Error500InternalServerError((&common.ImageUpdateCheckError{Err: err}).Error())
162166
}
@@ -174,7 +178,8 @@ func (h *ImageUpdateHandler) CheckImageUpdateByID(ctx context.Context, input *Ch
174178
return nil, huma.Error400BadRequest((&common.ImageIDRequiredError{}).Error())
175179
}
176180

177-
result, err := h.imageUpdateService.CheckImageUpdateByID(ctx, input.ImageID)
181+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
182+
result, err := h.imageUpdateService.CheckImageUpdateByID(runtimeCtx, input.ImageID)
178183
if err != nil {
179184
return nil, huma.Error500InternalServerError((&common.ImageUpdateCheckError{Err: err}).Error())
180185
}
@@ -198,7 +203,8 @@ func (h *ImageUpdateHandler) CheckMultipleImages(ctx context.Context, input *Che
198203
}, nil
199204
}
200205

201-
results, err := h.imageUpdateService.CheckMultipleImages(ctx, input.Body.ImageRefs, input.Body.Credentials)
206+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
207+
results, err := h.imageUpdateService.CheckMultipleImages(runtimeCtx, input.Body.ImageRefs, input.Body.Credentials)
202208
if err != nil {
203209
return nil, huma.Error500InternalServerError((&common.BatchImageUpdateCheckError{Err: err}).Error())
204210
}
@@ -212,7 +218,8 @@ func (h *ImageUpdateHandler) CheckMultipleImages(ctx context.Context, input *Che
212218
}
213219

214220
func (h *ImageUpdateHandler) CheckAllImages(ctx context.Context, input *CheckAllImagesInput) (*CheckAllImagesOutput, error) {
215-
results, err := h.imageUpdateService.CheckAllImages(ctx, 0, input.Body.Credentials)
221+
runtimeCtx := utils.ActivityRuntimeContext(ctx, h.appCtx)
222+
results, err := h.imageUpdateService.CheckAllImages(runtimeCtx, 0, input.Body.Credentials)
216223
if err != nil {
217224
return nil, huma.Error500InternalServerError((&common.AllImageUpdateCheckError{Err: err}).Error())
218225
}

0 commit comments

Comments
 (0)