code.gitea.io/gitea@v1.22.3/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(ctx); 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.CancelPreviousJobs(
    59  					ctx,
    60  					row.RepoID,
    61  					row.Schedule.Ref,
    62  					row.Schedule.WorkflowID,
    63  					webhook_module.HookEventSchedule,
    64  				); err != nil {
    65  					log.Error("CancelPreviousJobs: %v", err)
    66  				}
    67  			}
    68  
    69  			if row.Repo.IsArchived {
    70  				// Skip if the repo is archived
    71  				continue
    72  			}
    73  
    74  			cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
    75  			if err != nil {
    76  				if repo_model.IsErrUnitTypeNotExist(err) {
    77  					// Skip the actions unit of this repo is disabled.
    78  					continue
    79  				}
    80  				return fmt.Errorf("GetUnit: %w", err)
    81  			}
    82  			if cfg.ActionsConfig().IsWorkflowDisabled(row.Schedule.WorkflowID) {
    83  				continue
    84  			}
    85  
    86  			if err := CreateScheduleTask(ctx, row.Schedule); err != nil {
    87  				log.Error("CreateScheduleTask: %v", err)
    88  				return err
    89  			}
    90  
    91  			// Parse the spec
    92  			schedule, err := row.Parse()
    93  			if err != nil {
    94  				log.Error("Parse: %v", err)
    95  				return err
    96  			}
    97  
    98  			// Update the spec's next run time and previous run time
    99  			row.Prev = row.Next
   100  			row.Next = timeutil.TimeStamp(schedule.Next(now.Add(1 * time.Minute)).Unix())
   101  			if err := actions_model.UpdateScheduleSpec(ctx, row, "prev", "next"); err != nil {
   102  				log.Error("UpdateScheduleSpec: %v", err)
   103  				return err
   104  			}
   105  		}
   106  
   107  		// Stop if all specs have been retrieved
   108  		if len(specs) < pageSize {
   109  			break
   110  		}
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  // CreateScheduleTask creates a scheduled task from a cron action schedule.
   117  // It creates an action run based on the schedule, inserts it into the database, and creates commit statuses for each job.
   118  func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) error {
   119  	// Create a new action run based on the schedule
   120  	run := &actions_model.ActionRun{
   121  		Title:         cron.Title,
   122  		RepoID:        cron.RepoID,
   123  		OwnerID:       cron.OwnerID,
   124  		WorkflowID:    cron.WorkflowID,
   125  		TriggerUserID: cron.TriggerUserID,
   126  		Ref:           cron.Ref,
   127  		CommitSHA:     cron.CommitSHA,
   128  		Event:         cron.Event,
   129  		EventPayload:  cron.EventPayload,
   130  		TriggerEvent:  string(webhook_module.HookEventSchedule),
   131  		ScheduleID:    cron.ID,
   132  		Status:        actions_model.StatusWaiting,
   133  	}
   134  
   135  	vars, err := actions_model.GetVariablesOfRun(ctx, run)
   136  	if err != nil {
   137  		log.Error("GetVariablesOfRun: %v", err)
   138  		return err
   139  	}
   140  
   141  	// Parse the workflow specification from the cron schedule
   142  	workflows, err := jobparser.Parse(cron.Content, jobparser.WithVars(vars))
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	// Insert the action run and its associated jobs into the database
   148  	if err := actions_model.InsertRun(ctx, run, workflows); err != nil {
   149  		return err
   150  	}
   151  
   152  	// Return nil if no errors occurred
   153  	return nil
   154  }