code.gitea.io/gitea@v1.21.7/services/actions/schedule_tasks.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package actions
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"time"
    10  
    11  	actions_model "code.gitea.io/gitea/models/actions"
    12  	"code.gitea.io/gitea/models/db"
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	"code.gitea.io/gitea/models/unit"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/timeutil"
    17  	webhook_module "code.gitea.io/gitea/modules/webhook"
    18  
    19  	"github.com/nektos/act/pkg/jobparser"
    20  )
    21  
    22  // StartScheduleTasks start the task
    23  func StartScheduleTasks(ctx context.Context) error {
    24  	return startTasks(ctx)
    25  }
    26  
    27  // startTasks retrieves specifications in pages, creates a schedule task for each specification,
    28  // and updates the specification's next run time and previous run time.
    29  // The function returns an error if there's an issue with finding or updating the specifications.
    30  func startTasks(ctx context.Context) error {
    31  	// Set the page size
    32  	pageSize := 50
    33  
    34  	// Retrieve specs in pages until all specs have been retrieved
    35  	now := time.Now()
    36  	for page := 1; ; page++ {
    37  		// Retrieve the specs for the current page
    38  		specs, _, err := actions_model.FindSpecs(ctx, actions_model.FindSpecOptions{
    39  			ListOptions: db.ListOptions{
    40  				Page:     page,
    41  				PageSize: pageSize,
    42  			},
    43  			Next: now.Unix(),
    44  		})
    45  		if err != nil {
    46  			return fmt.Errorf("find specs: %w", err)
    47  		}
    48  
    49  		if err := specs.LoadRepos(); err != nil {
    50  			return fmt.Errorf("LoadRepos: %w", err)
    51  		}
    52  
    53  		// Loop through each spec and create a schedule task for it
    54  		for _, row := range specs {
    55  			// cancel running jobs if the event is push
    56  			if row.Schedule.Event == webhook_module.HookEventPush {
    57  				// cancel running jobs of the same workflow
    58  				if err := actions_model.CancelRunningJobs(
    59  					ctx,
    60  					row.RepoID,
    61  					row.Schedule.Ref,
    62  					row.Schedule.WorkflowID,
    63  					webhook_module.HookEventSchedule,
    64  				); err != nil {
    65  					log.Error("CancelRunningJobs: %v", err)
    66  				}
    67  			}
    68  
    69  			cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
    70  			if err != nil {
    71  				if repo_model.IsErrUnitTypeNotExist(err) {
    72  					// Skip the actions unit of this repo is disabled.
    73  					continue
    74  				}
    75  				return fmt.Errorf("GetUnit: %w", err)
    76  			}
    77  			if cfg.ActionsConfig().IsWorkflowDisabled(row.Schedule.WorkflowID) {
    78  				continue
    79  			}
    80  
    81  			if err := CreateScheduleTask(ctx, row.Schedule); err != nil {
    82  				log.Error("CreateScheduleTask: %v", err)
    83  				return err
    84  			}
    85  
    86  			// Parse the spec
    87  			schedule, err := row.Parse()
    88  			if err != nil {
    89  				log.Error("Parse: %v", err)
    90  				return err
    91  			}
    92  
    93  			// Update the spec's next run time and previous run time
    94  			row.Prev = row.Next
    95  			row.Next = timeutil.TimeStamp(schedule.Next(now.Add(1 * time.Minute)).Unix())
    96  			if err := actions_model.UpdateScheduleSpec(ctx, row, "prev", "next"); err != nil {
    97  				log.Error("UpdateScheduleSpec: %v", err)
    98  				return err
    99  			}
   100  		}
   101  
   102  		// Stop if all specs have been retrieved
   103  		if len(specs) < pageSize {
   104  			break
   105  		}
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  // CreateScheduleTask creates a scheduled task from a cron action schedule.
   112  // It creates an action run based on the schedule, inserts it into the database, and creates commit statuses for each job.
   113  func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) error {
   114  	// Create a new action run based on the schedule
   115  	run := &actions_model.ActionRun{
   116  		Title:         cron.Title,
   117  		RepoID:        cron.RepoID,
   118  		OwnerID:       cron.OwnerID,
   119  		WorkflowID:    cron.WorkflowID,
   120  		TriggerUserID: cron.TriggerUserID,
   121  		Ref:           cron.Ref,
   122  		CommitSHA:     cron.CommitSHA,
   123  		Event:         cron.Event,
   124  		EventPayload:  cron.EventPayload,
   125  		TriggerEvent:  string(webhook_module.HookEventSchedule),
   126  		ScheduleID:    cron.ID,
   127  		Status:        actions_model.StatusWaiting,
   128  	}
   129  
   130  	// Parse the workflow specification from the cron schedule
   131  	workflows, err := jobparser.Parse(cron.Content)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	// Insert the action run and its associated jobs into the database
   137  	if err := actions_model.InsertRun(ctx, run, workflows); err != nil {
   138  		return err
   139  	}
   140  
   141  	// Return nil if no errors occurred
   142  	return nil
   143  }