@@ -16,6 +16,7 @@ import (
1616 "github.com/hashicorp/go-multierror"
1717 "github.com/treeverse/lakefs/pkg/actions/lua/hook"
1818 "github.com/treeverse/lakefs/pkg/auth"
19+ "github.com/treeverse/lakefs/pkg/catalog"
1920 "github.com/treeverse/lakefs/pkg/graveler"
2021 "github.com/treeverse/lakefs/pkg/kv"
2122 "github.com/treeverse/lakefs/pkg/logging"
@@ -210,6 +211,7 @@ type Service interface {
210211 GetTaskResult (ctx context.Context , repositoryID string , runID string , hookRunID string ) (* TaskResult , error )
211212 ListRunResults (ctx context.Context , repositoryID string , branchID , commitID string , after string ) (RunResultIterator , error )
212213 ListRunTaskResults (ctx context.Context , repositoryID string , runID string , after string ) (TaskResultIterator , error )
214+ TriggerAction (ctx context.Context , repository * catalog.Repository , ref string , actionName string ) (string , error )
213215 graveler.HooksHandler
214216}
215217
@@ -303,7 +305,23 @@ func (s *StoreService) loadMatchedActions(ctx context.Context, record graveler.H
303305 if err != nil {
304306 return nil , err
305307 }
306- return MatchedActions (actions , spec )
308+
309+ // First filter by event type and branch
310+ matchedActions , err := MatchedActions (actions , spec )
311+ if err != nil {
312+ return nil , err
313+ }
314+
315+ // If ActionName is specified in the record, filter by it
316+ if record .ActionName != "" {
317+ for _ , action := range matchedActions {
318+ if action .Name == record .ActionName {
319+ return []* Action {action }, nil
320+ }
321+ }
322+ return nil , ErrNotFound
323+ }
324+ return matchedActions , nil
307325}
308326
309327func (s * StoreService ) allocateTasks (runID string , actions []* Action ) ([][]* Task , error ) {
@@ -606,6 +624,53 @@ func (s *StoreService) PostCherryPickHook(ctx context.Context, record graveler.H
606624 return nil
607625}
608626
627+ func (s * StoreService ) TriggerAction (ctx context.Context , repository * catalog.Repository , ref string , actionName string ) (string , error ) {
628+ if ! s .cfg .Enabled {
629+ logging .FromContext (ctx ).WithField ("repository" , repository .Name ).Debug ("Hooks are disabled, skipping manual trigger" )
630+ // Return NoRunID when actions are disabled - the caller should check the error
631+ return "" , ErrActionsDisabled
632+ }
633+
634+ // Construct graveler.RepositoryRecord from catalog.Repository
635+ repoRecord := & graveler.RepositoryRecord {
636+ RepositoryID : graveler .RepositoryID (repository .Name ),
637+ Repository : & graveler.Repository {
638+ StorageID : graveler .StorageID (repository .StorageID ),
639+ StorageNamespace : graveler .StorageNamespace (repository .StorageNamespace ),
640+ CreationDate : repository .CreationDate ,
641+ DefaultBranchID : graveler .BranchID (repository .DefaultBranch ),
642+ State : graveler .RepositoryState_ACTIVE ,
643+ InstanceUID : "" , // Not available from catalog API
644+ ReadOnly : repository .ReadOnly ,
645+ },
646+ }
647+
648+ runID := s .NewRunID ()
649+
650+ // Create hook record for manual trigger with all required fields
651+ record := graveler.HookRecord {
652+ RunID : runID ,
653+ EventType : graveler .EventTypeManualTrigger ,
654+ Repository : repoRecord ,
655+ SourceRef : graveler .Ref (ref ),
656+ BranchID : graveler .BranchID (ref ), // For manual triggers, we use the ref as branch
657+ ActionName : actionName ,
658+ }
659+
660+ // verify that the action exists before scheduling the run
661+ spec := MatchSpec {
662+ EventType : record .EventType ,
663+ BranchID : record .BranchID ,
664+ }
665+ _ , err := s .loadMatchedActions (ctx , record , spec )
666+ if err != nil {
667+ return "" , ErrNotFound
668+ }
669+ // Run the action asynchronously
670+ s .asyncRun (ctx , record )
671+ return runID , nil
672+ }
673+
609674func (s * StoreService ) NewRunID () string {
610675 return s .idGen .NewRunID ()
611676}
0 commit comments