Implement actions (#21937)
Close #13539. Co-authored by: @lunny @appleboy @fuxiaohei and others. Related projects: - https://gitea.com/gitea/actions-proto-def - https://gitea.com/gitea/actions-proto-go - https://gitea.com/gitea/act - https://gitea.com/gitea/act_runner ### Summary The target of this PR is to bring a basic implementation of "Actions", an internal CI/CD system of Gitea. That means even though it has been merged, the state of the feature is **EXPERIMENTAL**, and please note that: - It is disabled by default; - It shouldn't be used in a production environment currently; - It shouldn't be used in a public Gitea instance currently; - Breaking changes may be made before it's stable. **Please comment on #13539 if you have any different product design ideas**, all decisions reached there will be adopted here. But in this PR, we don't talk about **naming, feature-creep or alternatives**. ### ⚠️ Breaking `gitea-actions` will become a reserved user name. If a user with the name already exists in the database, it is recommended to rename it. ### Some important reviews - What is `DEFAULT_ACTIONS_URL` in `app.ini` for? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1055954954 - Why the api for runners is not under the normal `/api/v1` prefix? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1061173592 - Why DBFS? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1061301178 - Why ignore events triggered by `gitea-actions` bot? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1063254103 - Why there's no permission control for actions? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1090229868 ### What it looks like <details> #### Manage runners <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205870657-c72f590e-2e08-4cd4-be7f-2e0abb299bbf.png"> #### List runs <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205872794-50fde990-2b45-48c1-a178-908e4ec5b627.png"> #### View logs <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205872501-9b7b9000-9542-4991-8f55-18ccdada77c3.png"> </details> ### How to try it <details> #### 1. Start Gitea Clone this branch and [install from source](https://docs.gitea.io/en-us/install-from-source). Add additional configurations in `app.ini` to enable Actions: ```ini [actions] ENABLED = true ``` Start it. If all is well, you'll see the management page of runners: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205877365-8e30a780-9b10-4154-b3e8-ee6c3cb35a59.png"> #### 2. Start runner Clone the [act_runner](https://gitea.com/gitea/act_runner), and follow the [README](https://gitea.com/gitea/act_runner/src/branch/main/README.md) to start it. If all is well, you'll see a new runner has been added: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205878000-216f5937-e696-470d-b66c-8473987d91c3.png"> #### 3. Enable actions for a repo Create a new repo or open an existing one, check the `Actions` checkbox in settings and submit. <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205879705-53e09208-73c0-4b3e-a123-2dcf9aba4b9c.png"> <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205879383-23f3d08f-1a85-41dd-a8b3-54e2ee6453e8.png"> If all is well, you'll see a new tab "Actions": <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205881648-a8072d8c-5803-4d76-b8a8-9b2fb49516c1.png"> #### 4. Upload workflow files Upload some workflow files to `.gitea/workflows/xxx.yaml`, you can follow the [quickstart](https://docs.github.com/en/actions/quickstart) of GitHub Actions. Yes, Gitea Actions is compatible with GitHub Actions in most cases, you can use the same demo: ```yaml name: GitHub Actions Demo run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 on: [push] jobs: Explore-GitHub-Actions: runs-on: ubuntu-latest steps: - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - name: Check out repository code uses: actions/checkout@v3 - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - run: echo "🖥️ The workflow is now ready to test your code on the runner." - name: List files in the repository run: | ls ${{ github.workspace }} - run: echo "🍏 This job's status is ${{ job.status }}." ``` If all is well, you'll see a new run in `Actions` tab: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205884473-79a874bc-171b-4aaf-acd5-0241a45c3b53.png"> #### 5. Check the logs of jobs Click a run and you'll see the logs: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205884800-994b0374-67f7-48ff-be9a-4c53f3141547.png"> #### 6. Go on You can try more examples in [the documents](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) of GitHub Actions, then you might find a lot of bugs. Come on, PRs are welcome. </details> See also: [Feature Preview: Gitea Actions](https://blog.gitea.io/2022/12/feature-preview-gitea-actions/) --------- Co-authored-by: a1012112796 <1012112796@qq.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: ChristopherHX <christopher.homberger@web.de> Co-authored-by: John Olheiser <john.olheiser@gmail.com>
This commit is contained in:
parent
b5b3e0714e
commit
4011821c94
117 changed files with 7545 additions and 128 deletions
163
modules/actions/log.go
Normal file
163
modules/actions/log.go
Normal file
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/dbfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxLineSize = 64 * 1024
|
||||
DBFSPrefix = "actions_log/"
|
||||
|
||||
timeFormat = "2006-01-02T15:04:05.0000000Z07:00"
|
||||
defaultBufSize = MaxLineSize
|
||||
)
|
||||
|
||||
func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) {
|
||||
name := DBFSPrefix + filename
|
||||
f, err := dbfs.OpenFile(ctx, name, os.O_WRONLY|os.O_CREATE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.Seek(offset, io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("dbfs Seek %q: %w", name, err)
|
||||
}
|
||||
|
||||
writer := bufio.NewWriterSize(f, defaultBufSize)
|
||||
|
||||
ns := make([]int, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
n, err := writer.WriteString(FormatLog(row.Time.AsTime(), row.Content) + "\n")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns = append(ns, n)
|
||||
}
|
||||
|
||||
if err := writer.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limit int64) ([]*runnerv1.LogRow, error) {
|
||||
f, err := openLogs(ctx, inStorage, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := f.Seek(offset, io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("file seek: %w", err)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
maxLineSize := len(timeFormat) + MaxLineSize + 1
|
||||
scanner.Buffer(make([]byte, maxLineSize), maxLineSize)
|
||||
|
||||
var rows []*runnerv1.LogRow
|
||||
for scanner.Scan() && (int64(len(rows)) < limit || limit < 0) {
|
||||
t, c, err := ParseLog(scanner.Text())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse log %q: %w", scanner.Text(), err)
|
||||
}
|
||||
rows = append(rows, &runnerv1.LogRow{
|
||||
Time: timestamppb.New(t),
|
||||
Content: c,
|
||||
})
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("scan: %w", err)
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func TransferLogs(ctx context.Context, filename string) (func(), error) {
|
||||
name := DBFSPrefix + filename
|
||||
remove := func() {
|
||||
if err := dbfs.Remove(ctx, name); err != nil {
|
||||
log.Warn("dbfs remove %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
f, err := dbfs.Open(ctx, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dbfs open %q: %w", name, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := storage.Actions.Save(filename, f, -1); err != nil {
|
||||
return nil, fmt.Errorf("storage save %q: %w", filename, err)
|
||||
}
|
||||
return remove, nil
|
||||
}
|
||||
|
||||
func RemoveLogs(ctx context.Context, inStorage bool, filename string) error {
|
||||
if !inStorage {
|
||||
name := DBFSPrefix + filename
|
||||
err := dbfs.Remove(ctx, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dbfs remove %q: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err := storage.Actions.Delete(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage delete %q: %w", filename, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func openLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) {
|
||||
if !inStorage {
|
||||
name := DBFSPrefix + filename
|
||||
f, err := dbfs.Open(ctx, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dbfs open %q: %w", name, err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
f, err := storage.Actions.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage open %q: %w", filename, err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func FormatLog(timestamp time.Time, content string) string {
|
||||
// Content shouldn't contain new line, it will break log indexes, other control chars are safe.
|
||||
content = strings.ReplaceAll(content, "\n", `\n`)
|
||||
if len(content) > MaxLineSize {
|
||||
content = content[:MaxLineSize]
|
||||
}
|
||||
return fmt.Sprintf("%s %s", timestamp.UTC().Format(timeFormat), content)
|
||||
}
|
||||
|
||||
func ParseLog(in string) (time.Time, string, error) {
|
||||
index := strings.IndexRune(in, ' ')
|
||||
if index < 0 {
|
||||
return time.Time{}, "", fmt.Errorf("invalid log: %q", in)
|
||||
}
|
||||
timestamp, err := time.Parse(timeFormat, in[:index])
|
||||
if err != nil {
|
||||
return time.Time{}, "", err
|
||||
}
|
||||
return timestamp, in[index+1:], nil
|
||||
}
|
101
modules/actions/task_state.go
Normal file
101
modules/actions/task_state.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
)
|
||||
|
||||
const (
|
||||
preStepName = "Set up job"
|
||||
postStepName = "Complete job"
|
||||
)
|
||||
|
||||
// FullSteps returns steps with "Set up job" and "Complete job"
|
||||
func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
|
||||
if len(task.Steps) == 0 {
|
||||
return fullStepsOfEmptySteps(task)
|
||||
}
|
||||
|
||||
firstStep := task.Steps[0]
|
||||
var logIndex int64
|
||||
|
||||
preStep := &actions_model.ActionTaskStep{
|
||||
Name: preStepName,
|
||||
LogLength: task.LogLength,
|
||||
Started: task.Started,
|
||||
Status: actions_model.StatusRunning,
|
||||
}
|
||||
|
||||
if firstStep.Status.HasRun() || firstStep.Status.IsRunning() {
|
||||
preStep.LogLength = firstStep.LogIndex
|
||||
preStep.Stopped = firstStep.Started
|
||||
preStep.Status = actions_model.StatusSuccess
|
||||
} else if task.Status.IsDone() {
|
||||
preStep.Stopped = task.Stopped
|
||||
preStep.Status = actions_model.StatusFailure
|
||||
}
|
||||
logIndex += preStep.LogLength
|
||||
|
||||
var lastHasRunStep *actions_model.ActionTaskStep
|
||||
for _, step := range task.Steps {
|
||||
if step.Status.HasRun() {
|
||||
lastHasRunStep = step
|
||||
}
|
||||
logIndex += step.LogLength
|
||||
}
|
||||
if lastHasRunStep == nil {
|
||||
lastHasRunStep = preStep
|
||||
}
|
||||
|
||||
postStep := &actions_model.ActionTaskStep{
|
||||
Name: postStepName,
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
if task.Status.IsDone() {
|
||||
postStep.LogIndex = logIndex
|
||||
postStep.LogLength = task.LogLength - postStep.LogIndex
|
||||
postStep.Status = task.Status
|
||||
postStep.Started = lastHasRunStep.Stopped
|
||||
postStep.Stopped = task.Stopped
|
||||
}
|
||||
ret := make([]*actions_model.ActionTaskStep, 0, len(task.Steps)+2)
|
||||
ret = append(ret, preStep)
|
||||
ret = append(ret, task.Steps...)
|
||||
ret = append(ret, postStep)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func fullStepsOfEmptySteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
|
||||
preStep := &actions_model.ActionTaskStep{
|
||||
Name: preStepName,
|
||||
LogLength: task.LogLength,
|
||||
Started: task.Started,
|
||||
Stopped: task.Stopped,
|
||||
Status: actions_model.StatusRunning,
|
||||
}
|
||||
|
||||
postStep := &actions_model.ActionTaskStep{
|
||||
Name: postStepName,
|
||||
LogIndex: task.LogLength,
|
||||
Started: task.Stopped,
|
||||
Stopped: task.Stopped,
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
|
||||
if task.Status.IsDone() {
|
||||
preStep.Status = task.Status
|
||||
if preStep.Status.IsSuccess() {
|
||||
postStep.Status = actions_model.StatusSuccess
|
||||
} else {
|
||||
postStep.Status = actions_model.StatusCancelled
|
||||
}
|
||||
}
|
||||
|
||||
return []*actions_model.ActionTaskStep{
|
||||
preStep,
|
||||
postStep,
|
||||
}
|
||||
}
|
112
modules/actions/task_state_test.go
Normal file
112
modules/actions/task_state_test.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFullSteps(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
task *actions_model.ActionTask
|
||||
want []*actions_model.ActionTaskStep
|
||||
}{
|
||||
{
|
||||
name: "regular",
|
||||
task: &actions_model.ActionTask{
|
||||
Steps: []*actions_model.ActionTaskStep{
|
||||
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090},
|
||||
},
|
||||
Status: actions_model.StatusSuccess,
|
||||
Started: 10000,
|
||||
Stopped: 10100,
|
||||
LogLength: 100,
|
||||
},
|
||||
want: []*actions_model.ActionTaskStep{
|
||||
{Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010},
|
||||
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090},
|
||||
{Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 10100},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "failed step",
|
||||
task: &actions_model.ActionTask{
|
||||
Steps: []*actions_model.ActionTaskStep{
|
||||
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 20, Started: 10010, Stopped: 10020},
|
||||
{Status: actions_model.StatusFailure, LogIndex: 30, LogLength: 60, Started: 10020, Stopped: 10090},
|
||||
{Status: actions_model.StatusCancelled, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||
},
|
||||
Status: actions_model.StatusFailure,
|
||||
Started: 10000,
|
||||
Stopped: 10100,
|
||||
LogLength: 100,
|
||||
},
|
||||
want: []*actions_model.ActionTaskStep{
|
||||
{Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010},
|
||||
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 20, Started: 10010, Stopped: 10020},
|
||||
{Status: actions_model.StatusFailure, LogIndex: 30, LogLength: 60, Started: 10020, Stopped: 10090},
|
||||
{Status: actions_model.StatusCancelled, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||
{Name: postStepName, Status: actions_model.StatusFailure, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 10100},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first step is running",
|
||||
task: &actions_model.ActionTask{
|
||||
Steps: []*actions_model.ActionTaskStep{
|
||||
{Status: actions_model.StatusRunning, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 0},
|
||||
},
|
||||
Status: actions_model.StatusRunning,
|
||||
Started: 10000,
|
||||
Stopped: 10100,
|
||||
LogLength: 100,
|
||||
},
|
||||
want: []*actions_model.ActionTaskStep{
|
||||
{Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010},
|
||||
{Status: actions_model.StatusRunning, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 0},
|
||||
{Name: postStepName, Status: actions_model.StatusWaiting, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first step has canceled",
|
||||
task: &actions_model.ActionTask{
|
||||
Steps: []*actions_model.ActionTaskStep{
|
||||
{Status: actions_model.StatusCancelled, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||
},
|
||||
Status: actions_model.StatusFailure,
|
||||
Started: 10000,
|
||||
Stopped: 10100,
|
||||
LogLength: 100,
|
||||
},
|
||||
want: []*actions_model.ActionTaskStep{
|
||||
{Name: preStepName, Status: actions_model.StatusFailure, LogIndex: 0, LogLength: 100, Started: 10000, Stopped: 10100},
|
||||
{Status: actions_model.StatusCancelled, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||
{Name: postStepName, Status: actions_model.StatusFailure, LogIndex: 100, LogLength: 0, Started: 10100, Stopped: 10100},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty steps",
|
||||
task: &actions_model.ActionTask{
|
||||
Steps: []*actions_model.ActionTaskStep{},
|
||||
Status: actions_model.StatusSuccess,
|
||||
Started: 10000,
|
||||
Stopped: 10100,
|
||||
LogLength: 100,
|
||||
},
|
||||
want: []*actions_model.ActionTaskStep{
|
||||
{Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 100, Started: 10000, Stopped: 10100},
|
||||
{Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 100, LogLength: 0, Started: 10100, Stopped: 10100},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, FullSteps(tt.task), "FullSteps(%v)", tt.task)
|
||||
})
|
||||
}
|
||||
}
|
75
modules/actions/workflows.go
Normal file
75
modules/actions/workflows.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
func ListWorkflows(commit *git.Commit) (git.Entries, error) {
|
||||
tree, err := commit.SubTree(".gitea/workflows")
|
||||
if _, ok := err.(git.ErrNotExist); ok {
|
||||
tree, err = commit.SubTree(".github/workflows")
|
||||
}
|
||||
if _, ok := err.(git.ErrNotExist); ok {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entries, err := tree.ListEntriesRecursiveFast()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make(git.Entries, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if strings.HasSuffix(entry.Name(), ".yml") || strings.HasSuffix(entry.Name(), ".yaml") {
|
||||
ret = append(ret, entry)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func DetectWorkflows(commit *git.Commit, event webhook_module.HookEventType) (map[string][]byte, error) {
|
||||
entries, err := ListWorkflows(commit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workflows := make(map[string][]byte, len(entries))
|
||||
for _, entry := range entries {
|
||||
f, err := entry.Blob().DataAsync()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := io.ReadAll(f)
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workflow, err := model.ReadWorkflow(bytes.NewReader(content))
|
||||
if err != nil {
|
||||
log.Warn("ignore invalid workflow %q: %v", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
for _, e := range workflow.On() {
|
||||
if e == event.Event() {
|
||||
workflows[entry.Name()] = content
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return workflows, nil
|
||||
}
|
|
@ -805,6 +805,7 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
|
|||
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
|
||||
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
||||
ctx.Data["DisableStars"] = setting.Repository.DisableStars
|
||||
ctx.Data["EnableActions"] = setting.Actions.Enabled
|
||||
|
||||
ctx.Data["ManifestData"] = setting.ManifestData
|
||||
|
||||
|
@ -812,6 +813,7 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
|
|||
ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled()
|
||||
ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
|
||||
ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
|
||||
ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
|
||||
|
||||
ctx.Data["locale"] = locale
|
||||
ctx.Data["AllLangs"] = translation.AllLangs()
|
||||
|
|
|
@ -1043,6 +1043,7 @@ func UnitTypes() func(ctx *Context) {
|
|||
ctx.Data["UnitTypeExternalTracker"] = unit_model.TypeExternalTracker
|
||||
ctx.Data["UnitTypeProjects"] = unit_model.TypeProjects
|
||||
ctx.Data["UnitTypePackages"] = unit_model.TypePackages
|
||||
ctx.Data["UnitTypeActions"] = unit_model.TypeActions
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/notification/action"
|
||||
"code.gitea.io/gitea/modules/notification/base"
|
||||
"code.gitea.io/gitea/modules/notification/indexer"
|
||||
|
@ -106,6 +107,13 @@ func NotifyAutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *
|
|||
|
||||
// NotifyNewPullRequest notifies new pull request to notifiers
|
||||
func NotifyNewPullRequest(ctx context.Context, pr *issues_model.PullRequest, mentions []*user_model.User) {
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
log.Error("%v", err)
|
||||
return
|
||||
}
|
||||
if err := pr.Issue.LoadPoster(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyNewPullRequest(ctx, pr, mentions)
|
||||
}
|
||||
|
@ -120,6 +128,10 @@ func NotifyPullRequestSynchronized(ctx context.Context, doer *user_model.User, p
|
|||
|
||||
// NotifyPullRequestReview notifies new pull request review
|
||||
func NotifyPullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
|
||||
if err := review.LoadReviewer(ctx); err != nil {
|
||||
log.Error("%v", err)
|
||||
return
|
||||
}
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyPullRequestReview(ctx, pr, review, comment, mentions)
|
||||
}
|
||||
|
@ -127,6 +139,10 @@ func NotifyPullRequestReview(ctx context.Context, pr *issues_model.PullRequest,
|
|||
|
||||
// NotifyPullRequestCodeComment notifies new pull request code comment
|
||||
func NotifyPullRequestCodeComment(ctx context.Context, pr *issues_model.PullRequest, comment *issues_model.Comment, mentions []*user_model.User) {
|
||||
if err := comment.LoadPoster(ctx); err != nil {
|
||||
log.Error("LoadPoster: %v", err)
|
||||
return
|
||||
}
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyPullRequestCodeComment(ctx, pr, comment, mentions)
|
||||
}
|
||||
|
@ -169,6 +185,10 @@ func NotifyDeleteComment(ctx context.Context, doer *user_model.User, c *issues_m
|
|||
|
||||
// NotifyNewRelease notifies new release to notifiers
|
||||
func NotifyNewRelease(ctx context.Context, rel *repo_model.Release) {
|
||||
if err := rel.LoadAttributes(ctx); err != nil {
|
||||
log.Error("LoadPublisher: %v", err)
|
||||
return
|
||||
}
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyNewRelease(ctx, rel)
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ type HookOptions struct {
|
|||
PullRequestID int64
|
||||
DeployKeyID int64 // if the pusher is a DeployKey, then UserID is the repo's org user.
|
||||
IsWiki bool
|
||||
ActionPerm int
|
||||
}
|
||||
|
||||
// SSHLogOption ssh log options
|
||||
|
|
|
@ -27,6 +27,7 @@ const (
|
|||
EnvPRID = "GITEA_PR_ID"
|
||||
EnvIsInternal = "GITEA_INTERNAL_PUSH"
|
||||
EnvAppURL = "GITEA_ROOT_URL"
|
||||
EnvActionPerm = "GITEA_ACTION_PERM"
|
||||
)
|
||||
|
||||
// InternalPushingEnvironment returns an os environment to switch off hooks on push
|
||||
|
|
29
modules/setting/actions.go
Normal file
29
modules/setting/actions.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// Actions settings
|
||||
var (
|
||||
Actions = struct {
|
||||
Storage // how the created logs should be stored
|
||||
Enabled bool
|
||||
DefaultActionsURL string `ini:"DEFAULT_ACTIONS_URL"`
|
||||
}{
|
||||
Enabled: false,
|
||||
DefaultActionsURL: "https://gitea.com",
|
||||
}
|
||||
)
|
||||
|
||||
func newActions() {
|
||||
sec := Cfg.Section("actions")
|
||||
if err := sec.MapTo(&Actions); err != nil {
|
||||
log.Fatal("Failed to map Actions settings: %v", err)
|
||||
}
|
||||
|
||||
Actions.Storage = getStorage("actions_log", "", nil)
|
||||
}
|
|
@ -1074,6 +1074,8 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
|
|||
|
||||
newPackages()
|
||||
|
||||
newActions()
|
||||
|
||||
if err = Cfg.Section("ui").MapTo(&UI); err != nil {
|
||||
log.Fatal("Failed to map UI settings: %v", err)
|
||||
} else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
|
||||
|
|
|
@ -125,6 +125,9 @@ var (
|
|||
|
||||
// Packages represents packages storage
|
||||
Packages ObjectStorage = uninitializedStorage
|
||||
|
||||
// Actions represents actions storage
|
||||
Actions ObjectStorage = uninitializedStorage
|
||||
)
|
||||
|
||||
// Init init the stoarge
|
||||
|
@ -136,6 +139,7 @@ func Init() error {
|
|||
initLFS,
|
||||
initRepoArchives,
|
||||
initPackages,
|
||||
initActions,
|
||||
} {
|
||||
if err := f(); err != nil {
|
||||
return err
|
||||
|
@ -204,3 +208,13 @@ func initPackages() (err error) {
|
|||
Packages, err = NewStorage(setting.Packages.Storage.Type, &setting.Packages.Storage)
|
||||
return err
|
||||
}
|
||||
|
||||
func initActions() (err error) {
|
||||
if !setting.Actions.Enabled {
|
||||
Actions = discardStorage("Actions isn't enabled")
|
||||
return nil
|
||||
}
|
||||
log.Info("Initialising Actions storage with type: %s", setting.Actions.Storage.Type)
|
||||
Actions, err = NewStorage(setting.Actions.Storage.Type, &setting.Actions.Storage)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ const (
|
|||
CommitStatusFailure CommitStatusState = "failure"
|
||||
// CommitStatusWarning is for when the CommitStatus is Warning
|
||||
CommitStatusWarning CommitStatusState = "warning"
|
||||
// CommitStatusRunning is for when the CommitStatus is Running
|
||||
CommitStatusRunning CommitStatusState = "running"
|
||||
)
|
||||
|
||||
// NoBetterThan returns true if this State is no better than the given State
|
||||
|
|
|
@ -472,6 +472,9 @@ func NewFuncMap() []template.FuncMap {
|
|||
curBranch,
|
||||
)
|
||||
},
|
||||
"RefShortName": func(ref string) string {
|
||||
return git.RefName(ref).ShortName()
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue