code.gitea.io/gitea@v1.22.3/routers/web/repo/actions/actions.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package actions
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"net/http"
    10  	"strings"
    11  
    12  	actions_model "code.gitea.io/gitea/models/actions"
    13  	"code.gitea.io/gitea/models/db"
    14  	"code.gitea.io/gitea/models/unit"
    15  	"code.gitea.io/gitea/modules/actions"
    16  	"code.gitea.io/gitea/modules/base"
    17  	"code.gitea.io/gitea/modules/container"
    18  	"code.gitea.io/gitea/modules/git"
    19  	"code.gitea.io/gitea/modules/optional"
    20  	"code.gitea.io/gitea/modules/setting"
    21  	"code.gitea.io/gitea/routers/web/repo"
    22  	"code.gitea.io/gitea/services/context"
    23  	"code.gitea.io/gitea/services/convert"
    24  
    25  	"github.com/nektos/act/pkg/model"
    26  )
    27  
    28  const (
    29  	tplListActions base.TplName = "repo/actions/list"
    30  	tplViewActions base.TplName = "repo/actions/view"
    31  )
    32  
    33  type Workflow struct {
    34  	Entry  git.TreeEntry
    35  	ErrMsg string
    36  }
    37  
    38  // MustEnableActions check if actions are enabled in settings
    39  func MustEnableActions(ctx *context.Context) {
    40  	if !setting.Actions.Enabled {
    41  		ctx.NotFound("MustEnableActions", nil)
    42  		return
    43  	}
    44  
    45  	if unit.TypeActions.UnitGlobalDisabled() {
    46  		ctx.NotFound("MustEnableActions", nil)
    47  		return
    48  	}
    49  
    50  	if ctx.Repo.Repository != nil {
    51  		if !ctx.Repo.CanRead(unit.TypeActions) {
    52  			ctx.NotFound("MustEnableActions", nil)
    53  			return
    54  		}
    55  	}
    56  }
    57  
    58  func List(ctx *context.Context) {
    59  	ctx.Data["Title"] = ctx.Tr("actions.actions")
    60  	ctx.Data["PageIsActions"] = true
    61  
    62  	var workflows []Workflow
    63  	if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
    64  		ctx.ServerError("IsEmpty", err)
    65  		return
    66  	} else if !empty {
    67  		commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
    68  		if err != nil {
    69  			ctx.ServerError("GetBranchCommit", err)
    70  			return
    71  		}
    72  		entries, err := actions.ListWorkflows(commit)
    73  		if err != nil {
    74  			ctx.ServerError("ListWorkflows", err)
    75  			return
    76  		}
    77  
    78  		// Get all runner labels
    79  		runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
    80  			RepoID:        ctx.Repo.Repository.ID,
    81  			IsOnline:      optional.Some(true),
    82  			WithAvailable: true,
    83  		})
    84  		if err != nil {
    85  			ctx.ServerError("FindRunners", err)
    86  			return
    87  		}
    88  		allRunnerLabels := make(container.Set[string])
    89  		for _, r := range runners {
    90  			allRunnerLabels.AddMultiple(r.AgentLabels...)
    91  		}
    92  
    93  		workflows = make([]Workflow, 0, len(entries))
    94  		for _, entry := range entries {
    95  			workflow := Workflow{Entry: *entry}
    96  			content, err := actions.GetContentFromEntry(entry)
    97  			if err != nil {
    98  				ctx.ServerError("GetContentFromEntry", err)
    99  				return
   100  			}
   101  			wf, err := model.ReadWorkflow(bytes.NewReader(content))
   102  			if err != nil {
   103  				workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
   104  				workflows = append(workflows, workflow)
   105  				continue
   106  			}
   107  			// The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run.
   108  			hasJobWithoutNeeds := false
   109  			// Check whether have matching runner and a job without "needs"
   110  			emptyJobsNumber := 0
   111  			for _, j := range wf.Jobs {
   112  				if j == nil {
   113  					emptyJobsNumber++
   114  					continue
   115  				}
   116  				if !hasJobWithoutNeeds && len(j.Needs()) == 0 {
   117  					hasJobWithoutNeeds = true
   118  				}
   119  				runsOnList := j.RunsOn()
   120  				for _, ro := range runsOnList {
   121  					if strings.Contains(ro, "${{") {
   122  						// Skip if it contains expressions.
   123  						// The expressions could be very complex and could not be evaluated here,
   124  						// so just skip it, it's OK since it's just a tooltip message.
   125  						continue
   126  					}
   127  					if !allRunnerLabels.Contains(ro) {
   128  						workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro)
   129  						break
   130  					}
   131  				}
   132  				if workflow.ErrMsg != "" {
   133  					break
   134  				}
   135  			}
   136  			if !hasJobWithoutNeeds {
   137  				workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs")
   138  			}
   139  			if emptyJobsNumber == len(wf.Jobs) {
   140  				workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job")
   141  			}
   142  			workflows = append(workflows, workflow)
   143  		}
   144  	}
   145  	ctx.Data["workflows"] = workflows
   146  	ctx.Data["RepoLink"] = ctx.Repo.Repository.Link()
   147  
   148  	page := ctx.FormInt("page")
   149  	if page <= 0 {
   150  		page = 1
   151  	}
   152  
   153  	workflow := ctx.FormString("workflow")
   154  	actorID := ctx.FormInt64("actor")
   155  	status := ctx.FormInt("status")
   156  	ctx.Data["CurWorkflow"] = workflow
   157  
   158  	actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
   159  	ctx.Data["ActionsConfig"] = actionsConfig
   160  
   161  	if len(workflow) > 0 && ctx.Repo.IsAdmin() {
   162  		ctx.Data["AllowDisableOrEnableWorkflow"] = true
   163  		ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflow)
   164  	}
   165  
   166  	// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
   167  	// they will be 0 by default, which indicates get all status or actors
   168  	ctx.Data["CurActor"] = actorID
   169  	ctx.Data["CurStatus"] = status
   170  	if actorID > 0 || status > int(actions_model.StatusUnknown) {
   171  		ctx.Data["IsFiltered"] = true
   172  	}
   173  
   174  	opts := actions_model.FindRunOptions{
   175  		ListOptions: db.ListOptions{
   176  			Page:     page,
   177  			PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
   178  		},
   179  		RepoID:        ctx.Repo.Repository.ID,
   180  		WorkflowID:    workflow,
   181  		TriggerUserID: actorID,
   182  	}
   183  
   184  	// if status is not StatusUnknown, it means user has selected a status filter
   185  	if actions_model.Status(status) != actions_model.StatusUnknown {
   186  		opts.Status = []actions_model.Status{actions_model.Status(status)}
   187  	}
   188  
   189  	runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
   190  	if err != nil {
   191  		ctx.ServerError("FindAndCount", err)
   192  		return
   193  	}
   194  
   195  	for _, run := range runs {
   196  		run.Repo = ctx.Repo.Repository
   197  	}
   198  
   199  	if err := actions_model.RunList(runs).LoadTriggerUser(ctx); err != nil {
   200  		ctx.ServerError("LoadTriggerUser", err)
   201  		return
   202  	}
   203  
   204  	ctx.Data["Runs"] = runs
   205  
   206  	actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
   207  	if err != nil {
   208  		ctx.ServerError("GetActors", err)
   209  		return
   210  	}
   211  	ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors)
   212  
   213  	ctx.Data["StatusInfoList"] = actions_model.GetStatusInfoList(ctx)
   214  
   215  	pager := context.NewPagination(int(total), opts.PageSize, opts.Page, 5)
   216  	pager.SetDefaultParams(ctx)
   217  	pager.AddParamString("workflow", workflow)
   218  	pager.AddParamString("actor", fmt.Sprint(actorID))
   219  	pager.AddParamString("status", fmt.Sprint(status))
   220  	ctx.Data["Page"] = pager
   221  	ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
   222  
   223  	ctx.HTML(http.StatusOK, tplListActions)
   224  }