// Copyright The Forgejo Authors.// SPDX-License-Identifier: MIT package actions import ( "bytes" "context" "errors" "fmt" "strconv" actions_model "forgejo.org/models/actions" "forgejo.org/models/perm" "forgejo.org/models/perm/access" repo_model "forgejo.org/models/repo" "forgejo.org/models/user" "forgejo.org/modules/actions" "forgejo.org/modules/git" "forgejo.org/modules/json" "forgejo.org/modules/setting" "forgejo.org/modules/structs" "forgejo.org/modules/util" "forgejo.org/modules/webhook" "forgejo.org/services/convert" "github.com/nektos/act/pkg/jobparser" act_model "github.com/nektos/act/pkg/model") type InputRequiredErr struct { Name string} func (err InputRequiredErr) Error() string { return fmt.Sprintf("input required for '%s'", err.Name)} func IsInputRequiredErr(err error) bool { _, ok := err.(InputRequiredErr) return ok} type Workflow struct { WorkflowID string Ref string Commit *git.Commit GitEntry *git.TreeEntry} type InputValueGetter func(key string) string func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGetter, repo *repo_model.Repository, doer *user.User) (r *actions_model.ActionRun, j []string, err error) { content, err := actions.GetContentFromEntry(entry.GitEntry) if err != nil { return nil, nil, err } wf, err := act_model.ReadWorkflow(bytes.NewReader(content)) if err != nil { return nil, nil, err } fullWorkflowID := ".forgejo/workflows/" + entry.WorkflowID title := wf.Name if len(title) < 1 { title = fullWorkflowID } inputs := make(map[string]string) if workflowDispatch := wf.WorkflowDispatchConfig(); workflowDispatch != nil { for key, input := range workflowDispatch.Inputs { val := inputGetter(key) if len(val) == 0 { val = input.Default if len(val) == 0 { if input.Required { name := input.Description if len(name) == 0 { name = key } return nil, nil, InputRequiredErr{Name: name} } continue } } else if input.Type == "boolean" { // Since "boolean" inputs are rendered as a checkbox in html, the value inside the form is "on" val = strconv.FormatBool(val == "on") } inputs[key] = val } } if int64(len(inputs)) > setting.Actions.LimitDispatchInputs { return nil, nil, errors.New("to many inputs") } jobNames := util.KeysOfMap(wf.Jobs) payload := &structs.WorkflowDispatchPayload{ Inputs: inputs, Ref: entry.Ref, Repository: convert.ToRepo(ctx, repo, access.Permission{AccessMode: perm.AccessModeNone}), Sender: convert.ToUser(ctx, doer, nil), Workflow: fullWorkflowID, } p, err := json.Marshal(payload) if err != nil { return nil, nil, err } run := &actions_model.ActionRun{ Title: title, RepoID: repo.ID, Repo: repo, OwnerID: repo.OwnerID, WorkflowID: entry.WorkflowID, TriggerUserID: doer.ID, TriggerUser: doer, Ref: entry.Ref, CommitSHA: entry.Commit.ID.String(), Event: webhook.HookEventWorkflowDispatch, EventPayload: string(p), TriggerEvent: string(webhook.HookEventWorkflowDispatch), Status: actions_model.StatusWaiting, } vars, err := actions_model.GetVariablesOfRun(ctx, run) if err != nil { return nil, nil, err } jobs, err := jobparser.Parse(content, jobparser.WithVars(vars)) if err != nil { return nil, nil, err } return run, jobNames, actions_model.InsertRun(ctx, run, jobs)} func GetWorkflowFromCommit(gitRepo *git.Repository, ref, workflowID string) (*Workflow, error) { ref, err := gitRepo.ExpandRef(ref) if err != nil { return nil, err } commit, err := gitRepo.GetCommit(ref) if err != nil { return nil, err } entries, err := actions.ListWorkflows(commit) if err != nil { return nil, err } var workflowEntry *git.TreeEntry for _, entry := range entries { if entry.Name() == workflowID { workflowEntry = entry break } } if workflowEntry == nil { return nil, errors.New("workflow not found") } return &Workflow{ WorkflowID: workflowID, Ref: ref, Commit: commit, GitEntry: workflowEntry, }, nil}
// Copyright The Forgejo Authors.
// SPDX-License-Identifier: MIT
package actions
import (
"bytes"
"context"
"errors"
"fmt"
"strconv"
actions_model "forgejo.org/models/actions"
"forgejo.org/models/perm"
"forgejo.org/models/perm/access"
repo_model "forgejo.org/models/repo"
"forgejo.org/models/user"
"forgejo.org/modules/actions"
"forgejo.org/modules/git"
"forgejo.org/modules/json"
"forgejo.org/modules/setting"
"forgejo.org/modules/structs"
"forgejo.org/modules/util"
"forgejo.org/modules/webhook"
"forgejo.org/services/convert"
"github.com/nektos/act/pkg/jobparser"
act_model "github.com/nektos/act/pkg/model"
)
type InputRequiredErr struct {
Name string
}
func (err InputRequiredErr) Error() string {
return fmt.Sprintf("input required for '%s'", err.Name)
}
func IsInputRequiredErr(err error) bool {
_, ok := err.(InputRequiredErr)
return ok
}
type Workflow struct {
WorkflowID string
Ref string
Commit *git.Commit
GitEntry *git.TreeEntry
}
type InputValueGetter func(key string) string
func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGetter, repo *repo_model.Repository, doer *user.User) (r *actions_model.ActionRun, j []string, err error) {
content, err := actions.GetContentFromEntry(entry.GitEntry)
if err != nil {
return nil, nil, err
}
wf, err := act_model.ReadWorkflow(bytes.NewReader(content))
if err != nil {
return nil, nil, err
}
fullWorkflowID := ".forgejo/workflows/" + entry.WorkflowID
title := wf.Name
if len(title) < 1 {
title = fullWorkflowID
}
inputs := make(map[string]string)
if workflowDispatch := wf.WorkflowDispatchConfig(); workflowDispatch != nil {
for key, input := range workflowDispatch.Inputs {
val := inputGetter(key)
if len(val) == 0 {
val = input.Default
if len(val) == 0 {
if input.Required {
name := input.Description
if len(name) == 0 {
name = key
}
return nil, nil, InputRequiredErr{Name: name}
}
continue
}
} else if input.Type == "boolean" {
// Since "boolean" inputs are rendered as a checkbox in html, the value inside the form is "on"
val = strconv.FormatBool(val == "on")
}
inputs[key] = val
}
}
if int64(len(inputs)) > setting.Actions.LimitDispatchInputs {
return nil, nil, errors.New("to many inputs")
}
jobNames := util.KeysOfMap(wf.Jobs)
payload := &structs.WorkflowDispatchPayload{
Inputs: inputs,
Ref: entry.Ref,
Repository: convert.ToRepo(ctx, repo, access.Permission{AccessMode: perm.AccessModeNone}),
Sender: convert.ToUser(ctx, doer, nil),
Workflow: fullWorkflowID,
}
p, err := json.Marshal(payload)
if err != nil {
return nil, nil, err
}
run := &actions_model.ActionRun{
Title: title,
RepoID: repo.ID,
Repo: repo,
OwnerID: repo.OwnerID,
WorkflowID: entry.WorkflowID,
TriggerUserID: doer.ID,
TriggerUser: doer,
Ref: entry.Ref,
CommitSHA: entry.Commit.ID.String(),
Event: webhook.HookEventWorkflowDispatch,
EventPayload: string(p),
TriggerEvent: string(webhook.HookEventWorkflowDispatch),
Status: actions_model.StatusWaiting,
}
vars, err := actions_model.GetVariablesOfRun(ctx, run)
if err != nil {
return nil, nil, err
}
jobs, err := jobparser.Parse(content, jobparser.WithVars(vars))
if err != nil {
return nil, nil, err
}
return run, jobNames, actions_model.InsertRun(ctx, run, jobs)
}
func GetWorkflowFromCommit(gitRepo *git.Repository, ref, workflowID string) (*Workflow, error) {
ref, err := gitRepo.ExpandRef(ref)
if err != nil {
return nil, err
}
commit, err := gitRepo.GetCommit(ref)
if err != nil {
return nil, err
}
entries, err := actions.ListWorkflows(commit)
if err != nil {
return nil, err
}
var workflowEntry *git.TreeEntry
for _, entry := range entries {
if entry.Name() == workflowID {
workflowEntry = entry
break
}
}
if workflowEntry == nil {
return nil, errors.New("workflow not found")
}
return &Workflow{
WorkflowID: workflowID,
Ref: ref,
Commit: commit,
GitEntry: workflowEntry,
}, nil
}