@@ -1859,6 +1859,9 @@ func (ws *workflowService) RunScheduledWorkflows(ctx context.Context) error {
18591859 }
18601860 if err := ws .dispatchScheduledWorkflow (ctx , scheduled ); err != nil {
18611861 log .CtxErrorf (ctx , "Failed to dispatch scheduled workflow %s: %s" , scheduled .ScheduleID , err )
1862+ if err := ws .unclaimScheduledWorkflow (ctx , scheduled .ScheduleID ); err != nil {
1863+ log .CtxWarningf (ctx , "Failed to unclaim scheduled workflow %s: %s" , scheduled .ScheduleID , err )
1864+ }
18621865 continue
18631866 }
18641867 }
@@ -1877,7 +1880,7 @@ func (ws *workflowService) claimScheduledWorkflow(ctx context.Context) (*tables.
18771880 WHERE next_run_usec <= ?
18781881 AND (lease_expires_usec = 0 OR lease_expires_usec <= ?)
18791882 ORDER BY next_run_usec ASC
1880- LIMIT 1` + dbh .SelectForUpdateModifier (), nowUsec , nowUsec ).Take (scheduled )
1883+ LIMIT 1 ` + dbh .SelectForUpdateModifier (), nowUsec , nowUsec ).Take (scheduled )
18811884 if err != nil {
18821885 if db .IsRecordNotFound (err ) {
18831886 scheduled = nil
@@ -1925,30 +1928,68 @@ func (ws *workflowService) unclaimScheduledWorkflow(ctx context.Context, schedul
19251928 return nil
19261929}
19271930
1931+ func (ws * workflowService ) getWorkflowForScheduledDispatch (ctx context.Context , groupID , repoURL string ) (* tables.Workflow , error ) {
1932+ gitRepository := & tables.GitRepository {}
1933+ err := ws .env .GetDBHandle ().NewQuery (ctx , "workflow_service_get_for_scheduled_dispatch" ).Raw (`
1934+ SELECT * FROM "GitRepositories"
1935+ WHERE group_id = ?
1936+ AND repo_url = ?
1937+ ` , groupID , repoURL ).Take (gitRepository )
1938+ if err != nil {
1939+ return nil , status .WrapErrorf (err , "fetch repo %q" , repoURL )
1940+ }
1941+ parsedURL , err := gitutil .ParseGitHubRepoURL (repoURL )
1942+ if err != nil {
1943+ return nil , status .WrapErrorf (err , "invalid repo URL %q" , repoURL )
1944+ }
1945+ app , err := ws .env .GetGitHubAppService ().GetGitHubAppForOwner (ctx , parsedURL .Owner )
1946+ if err != nil {
1947+ return nil , status .WrapErrorf (err , "get GitHub app for owner %q" , parsedURL .Owner )
1948+ }
1949+ // The cron scheduler does not use an authenticated context, so we use this method.
1950+ accessToken , err := app .GetInstallationTokenForStatusReportingOnly (ctx , parsedURL .Owner )
1951+ if err != nil {
1952+ return nil , status .WrapErrorf (err , "get installation token for owner %q" , parsedURL .Owner )
1953+ }
1954+ return ws .gitRepositoryWorkflow (gitRepository , accessToken .GetToken ()).Workflow , nil
1955+ }
1956+
19281957func (ws * workflowService ) dispatchScheduledWorkflow (ctx context.Context , scheduled * tables.ScheduledRun ) error {
1929- wfID := ws .GetLegacyWorkflowIDForGitRepository (scheduled .GroupID , scheduled .RepoURL )
1930- rsp , err := ws .ExecuteWorkflow (ctx , & wfpb.ExecuteWorkflowRequest {
1931- WorkflowId : wfID ,
1932- PushedRepoUrl : scheduled .RepoURL ,
1933- PushedBranch : scheduled .Branch ,
1934- Async : true ,
1935- ActionNames : []string {scheduled .ActionName },
1936- })
1958+ wf , err := ws .getWorkflowForScheduledDispatch (ctx , scheduled .GroupID , scheduled .RepoURL )
19371959 if err != nil {
1938- if err := ws .unclaimScheduledWorkflow (ctx , scheduled .ScheduleID ); err != nil {
1939- log .CtxWarningf (ctx , "Failed to unclaim scheduled workflow %s: %s" , scheduled .ScheduleID , err )
1940- }
19411960 return err
19421961 }
1943- for _ , actionStatus := range rsp .GetActionStatuses () {
1944- if actionErr := gstatus .FromProto (actionStatus .GetStatus ()).Err (); actionErr != nil {
1945- if err := ws .unclaimScheduledWorkflow (ctx , scheduled .ScheduleID ); err != nil {
1946- log .CtxWarningf (ctx , "Failed to unclaim scheduled workflow %s: %s" , scheduled .ScheduleID , err )
1947- }
1948- return status .WrapErrorf (actionErr , "failed to start scheduled workflow action %q" , scheduled .ActionName )
1949- }
1962+ defaultBranch , err := ws .getRepoDefaultBranch (ctx , scheduled .RepoURL , wf .AccessToken )
1963+ if err != nil {
1964+ return err
1965+ }
1966+ wd := & interfaces.WebhookData {
1967+ EventName : webhook_data .EventName .ScheduledDispatch ,
1968+ PushedRepoURL : scheduled .RepoURL ,
1969+ PushedBranch : defaultBranch ,
1970+ TargetRepoURL : scheduled .RepoURL ,
1971+ TargetBranch : defaultBranch ,
1972+ }
1973+ apiKey , err := ws .apiKeyForWorkflow (ctx , wf )
1974+ if err != nil {
1975+ return err
1976+ }
1977+ actions , err := ws .getActions (ctx , wf , wd , []string {scheduled .ActionName })
1978+ if err != nil {
1979+ return err
19501980 }
1951- nextRunUsec , err := ws .calculateNextRunTimeUsec (scheduled .CronExpr , scheduled .NextRunUsec )
1981+ if len (actions ) != 1 {
1982+ return status .InvalidArgumentErrorf ("multiple actions named %q found" , scheduled .ActionName )
1983+ }
1984+ action := actions [0 ]
1985+ invocationUUID , err := guuid .NewRandom ()
1986+ if err != nil {
1987+ return err
1988+ }
1989+ if _ , err := ws .executeWorkflowAction (ctx , apiKey , wf , wd , true /*isTrusted*/ , action , invocationUUID .String (), nil /*extraCIRunnerArgs*/ , nil /*env*/ , true /*shouldRetry*/ ); err != nil {
1990+ return status .WrapErrorf (err , "failed to start scheduled workflow action %q" , scheduled .ActionName )
1991+ }
1992+ nextRunUsec , err := ws .calculateNextRunTimeUsec (scheduled .CronExpr )
19521993 if err != nil {
19531994 alert .CtxUnexpectedEvent (ctx , "Failed to calculate next run time for scheduled workflow %s: %s" , scheduled .ScheduleID , err )
19541995 return err
@@ -1960,12 +2001,27 @@ func (ws *workflowService) dispatchScheduledWorkflow(ctx context.Context, schedu
19602001 return nil
19612002}
19622003
1963- func (ws * workflowService ) calculateNextRunTimeUsec (cronExpr string , lastRunTimeUsec int64 ) (int64 , error ) {
2004+ func (ws * workflowService ) getRepoDefaultBranch (ctx context.Context , repoURL string , accessToken string ) (string , error ) {
2005+ parsedURL , err := gitutil .ParseGitHubRepoURL (repoURL )
2006+ if err != nil {
2007+ return "" , status .InvalidArgumentErrorf ("invalid repo URL %q: %s" , repoURL , err )
2008+ }
2009+ app , err := ws .env .GetGitHubAppService ().GetGitHubAppForOwner (ctx , parsedURL .Owner )
2010+ if err != nil {
2011+ return "" , status .WrapErrorf (err , "get GitHub app for owner %q" , parsedURL .Owner )
2012+ }
2013+ return app .GetDefaultBranch (ctx , repoURL , accessToken )
2014+ }
2015+
2016+ // calculateNextRunTimeUsec uses the given cron expression to return the next
2017+ // scheduled time, using the current time as the minimum.
2018+ func (ws * workflowService ) calculateNextRunTimeUsec (cronExpr string ) (int64 , error ) {
19642019 sched , err := cronParser .Parse (cronExpr )
19652020 if err != nil {
19662021 return 0 , err
19672022 }
1968- return sched .Next (time .UnixMicro (lastRunTimeUsec )).UnixMicro (), nil
2023+ now := ws .env .GetClock ().Now ()
2024+ return sched .Next (now ).UnixMicro (), nil
19692025}
19702026
19712027func (ws * workflowService ) advanceWorkflowSchedule (ctx context.Context , scheduleID string , nextRunUsec int64 ) error {
0 commit comments