code.gitea.io/gitea@v1.21.7/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/context"
    19  	"code.gitea.io/gitea/modules/git"
    20  	"code.gitea.io/gitea/modules/setting"
    21  	"code.gitea.io/gitea/modules/util"
    22  	"code.gitea.io/gitea/routers/web/repo"
    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  		opts := actions_model.FindRunnerOptions{
    80  			RepoID:        ctx.Repo.Repository.ID,
    81  			IsOnline:      util.OptionalBoolTrue,
    82  			WithAvailable: true,
    83  		}
    84  		runners, err := actions_model.FindRunners(ctx, opts)
    85  		if err != nil {
    86  			ctx.ServerError("FindRunners", err)
    87  			return
    88  		}
    89  		allRunnerLabels := make(container.Set[string])
    90  		for _, r := range runners {
    91  			allRunnerLabels.AddMultiple(r.AgentLabels...)
    92  		}
    93  
    94  		workflows = make([]Workflow, 0, len(entries))
    95  		for _, entry := range entries {
    96  			workflow := Workflow{Entry: *entry}
    97  			content, err := actions.GetContentFromEntry(entry)
    98  			if err != nil {
    99  				ctx.ServerError("GetContentFromEntry", err)
   100  				return
   101  			}
   102  			wf, err := model.ReadWorkflow(bytes.NewReader(content))
   103  			if err != nil {
   104  				workflow.ErrMsg = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", err.Error())
   105  				workflows = append(workflows, workflow)
   106  				continue
   107  			}
   108  			// Check whether have matching runner
   109  			for _, j := range wf.Jobs {
   110  				runsOnList := j.RunsOn()
   111  				for _, ro := range runsOnList {
   112  					if strings.Contains(ro, "${{") {
   113  						// Skip if it contains expressions.
   114  						// The expressions could be very complex and could not be evaluated here,
   115  						// so just skip it, it's OK since it's just a tooltip message.
   116  						continue
   117  					}
   118  					if !allRunnerLabels.Contains(ro) {
   119  						workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_online_runner_helper", ro)
   120  						break
   121  					}
   122  				}
   123  				if workflow.ErrMsg != "" {
   124  					break
   125  				}
   126  			}
   127  			workflows = append(workflows, workflow)
   128  		}
   129  	}
   130  	ctx.Data["workflows"] = workflows
   131  	ctx.Data["RepoLink"] = ctx.Repo.Repository.Link()
   132  
   133  	page := ctx.FormInt("page")
   134  	if page <= 0 {
   135  		page = 1
   136  	}
   137  
   138  	workflow := ctx.FormString("workflow")
   139  	actorID := ctx.FormInt64("actor")
   140  	status := ctx.FormInt("status")
   141  	ctx.Data["CurWorkflow"] = workflow
   142  
   143  	actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
   144  	ctx.Data["ActionsConfig"] = actionsConfig
   145  
   146  	if len(workflow) > 0 && ctx.Repo.IsAdmin() {
   147  		ctx.Data["AllowDisableOrEnableWorkflow"] = true
   148  		ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflow)
   149  	}
   150  
   151  	// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
   152  	// they will be 0 by default, which indicates get all status or actors
   153  	ctx.Data["CurActor"] = actorID
   154  	ctx.Data["CurStatus"] = status
   155  	if actorID > 0 || status > int(actions_model.StatusUnknown) {
   156  		ctx.Data["IsFiltered"] = true
   157  	}
   158  
   159  	opts := actions_model.FindRunOptions{
   160  		ListOptions: db.ListOptions{
   161  			Page:     page,
   162  			PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
   163  		},
   164  		RepoID:        ctx.Repo.Repository.ID,
   165  		WorkflowID:    workflow,
   166  		TriggerUserID: actorID,
   167  	}
   168  
   169  	// if status is not StatusUnknown, it means user has selected a status filter
   170  	if actions_model.Status(status) != actions_model.StatusUnknown {
   171  		opts.Status = []actions_model.Status{actions_model.Status(status)}
   172  	}
   173  
   174  	runs, total, err := actions_model.FindRuns(ctx, opts)
   175  	if err != nil {
   176  		ctx.ServerError("FindAndCount", err)
   177  		return
   178  	}
   179  
   180  	for _, run := range runs {
   181  		run.Repo = ctx.Repo.Repository
   182  	}
   183  
   184  	if err := runs.LoadTriggerUser(ctx); err != nil {
   185  		ctx.ServerError("LoadTriggerUser", err)
   186  		return
   187  	}
   188  
   189  	ctx.Data["Runs"] = runs
   190  
   191  	actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
   192  	if err != nil {
   193  		ctx.ServerError("GetActors", err)
   194  		return
   195  	}
   196  	ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors)
   197  
   198  	ctx.Data["StatusInfoList"] = actions_model.GetStatusInfoList(ctx)
   199  
   200  	pager := context.NewPagination(int(total), opts.PageSize, opts.Page, 5)
   201  	pager.SetDefaultParams(ctx)
   202  	pager.AddParamString("workflow", workflow)
   203  	pager.AddParamString("actor", fmt.Sprint(actorID))
   204  	pager.AddParamString("status", fmt.Sprint(status))
   205  	ctx.Data["Page"] = pager
   206  	ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
   207  
   208  	ctx.HTML(http.StatusOK, tplListActions)
   209  }