2014-03-15 19:01:50 +08:00
|
|
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
2020-01-10 05:34:25 +08:00
|
|
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
2022-11-28 02:20:29 +08:00
|
|
|
// SPDX-License-Identifier: MIT
|
2014-03-15 19:01:50 +08:00
|
|
|
|
2016-03-12 00:56:52 +08:00
|
|
|
package context
|
2014-03-15 19:01:50 +08:00
|
|
|
|
|
|
|
import (
|
2016-11-30 05:49:06 +08:00
|
|
|
"html"
|
2014-03-23 01:44:02 +08:00
|
|
|
"html/template"
|
2014-04-16 00:27:29 +08:00
|
|
|
"io"
|
2014-03-15 19:01:50 +08:00
|
|
|
"net/http"
|
2018-03-16 05:13:34 +08:00
|
|
|
"net/url"
|
2014-03-23 04:40:09 +08:00
|
|
|
"strings"
|
2014-03-19 21:57:55 +08:00
|
|
|
"time"
|
2014-03-15 19:01:50 +08:00
|
|
|
|
2021-11-10 03:57:58 +08:00
|
|
|
"code.gitea.io/gitea/models/unit"
|
2021-11-24 17:49:20 +08:00
|
|
|
user_model "code.gitea.io/gitea/models/user"
|
2021-01-27 22:56:54 +08:00
|
|
|
mc "code.gitea.io/gitea/modules/cache"
|
2022-01-20 07:26:57 +08:00
|
|
|
"code.gitea.io/gitea/modules/git"
|
2022-07-23 14:38:03 +08:00
|
|
|
"code.gitea.io/gitea/modules/httpcache"
|
2016-11-11 00:24:48 +08:00
|
|
|
"code.gitea.io/gitea/modules/setting"
|
2021-01-26 23:36:53 +08:00
|
|
|
"code.gitea.io/gitea/modules/templates"
|
|
|
|
"code.gitea.io/gitea/modules/translation"
|
2023-06-18 15:59:09 +08:00
|
|
|
"code.gitea.io/gitea/modules/web"
|
2021-01-30 16:55:53 +08:00
|
|
|
"code.gitea.io/gitea/modules/web/middleware"
|
2023-06-18 15:59:09 +08:00
|
|
|
web_types "code.gitea.io/gitea/modules/web/types"
|
2019-08-24 00:40:30 +08:00
|
|
|
|
2021-01-26 23:36:53 +08:00
|
|
|
"gitea.com/go-chi/cache"
|
|
|
|
"gitea.com/go-chi/session"
|
2014-03-15 19:01:50 +08:00
|
|
|
)
|
|
|
|
|
2021-01-26 23:36:53 +08:00
|
|
|
// Render represents a template render
|
|
|
|
type Render interface {
|
Make HTML template functions support context (#24056)
# Background
Golang template is not friendly for large projects, and Golang template
team is quite slow, related:
* `https://github.com/golang/go/issues/54450`
Without upstream support, we can also have our solution to make HTML
template functions support context.
It helps a lot, the above Golang template issue `#54450` explains a lot:
1. It makes `{{Locale.Tr}}` could be used in any template, without
passing unclear `(dict "root" . )` anymore.
2. More and more functions need `context`, like `avatar`, etc, we do not
need to do `(dict "Context" $.Context)` anymore.
3. Many request-related functions could be shared by parent&children
templates, like "user setting" / "system setting"
See the test `TestScopedTemplateSetFuncMap`, one template set, two
`Execute` calls with different `CtxFunc`.
# The Solution
Instead of waiting for upstream, this PR re-uses the escaped HTML
template trees, use `AddParseTree` to add related templates/trees to a
new template instance, then the new template instance can have its own
FuncMap , the function calls in the template trees will always use the
new template's FuncMap.
`template.New` / `template.AddParseTree` / `adding-FuncMap` are all
quite fast, so the performance is not affected.
The details:
1. Make a new `html/template/Template` for `all` templates
2. Add template code to the `all` template
3. Freeze the `all` template, reset its exec func map, it shouldn't
execute any template.
4. When a router wants to render a template by its `name`
1. Find the `name` in `all`
2. Find all its related sub templates
3. Escape all related templates (just like what the html template
package does)
4. Add the escaped parse-trees of related templates into a new (scoped)
`text/template/Template`
5. Add context-related func map into the new (scoped) text template
6. Execute the new (scoped) text template
7. To improve performance, the escaped templates are cached to `template
sets`
# FAQ
## There is a `unsafe` call, is this PR unsafe?
This PR is safe. Golang has strict language definition, it's safe to do
so: https://pkg.go.dev/unsafe#Pointer (1) Conversion of a *T1 to Pointer
to *T2
## What if Golang template supports such feature in the future?
The public structs/interfaces/functions introduced by this PR is quite
simple, the code of `HTMLRender` is not changed too much. It's very easy
to switch to the official mechanism if there would be one.
## Does this PR change the template execution behavior?
No, see the tests (welcome to design more tests if it's necessary)
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-04-20 16:08:58 +08:00
|
|
|
TemplateLookup(tmpl string) (templates.TemplateExecutor, error)
|
2023-07-05 02:36:08 +08:00
|
|
|
HTML(w io.Writer, status int, name string, data any) error
|
2021-01-26 23:36:53 +08:00
|
|
|
}
|
|
|
|
|
2014-03-15 21:17:16 +08:00
|
|
|
// Context represents context of a request.
|
2014-03-15 19:01:50 +08:00
|
|
|
type Context struct {
|
2023-05-21 09:50:53 +08:00
|
|
|
*Base
|
2023-05-09 07:30:14 +08:00
|
|
|
|
2023-05-21 09:50:53 +08:00
|
|
|
Render Render
|
|
|
|
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
|
2014-07-26 12:24:27 +08:00
|
|
|
|
2023-05-09 07:30:14 +08:00
|
|
|
Cache cache.Cache
|
|
|
|
Csrf CSRFProtector
|
|
|
|
Flash *middleware.Flash
|
|
|
|
Session session.Store
|
|
|
|
|
2023-05-21 09:50:53 +08:00
|
|
|
Link string // current request URL (without query string)
|
|
|
|
|
|
|
|
Doer *user_model.User // current signed-in user
|
2014-11-19 00:07:16 +08:00
|
|
|
IsSigned bool
|
|
|
|
IsBasicAuth bool
|
2014-03-16 00:03:23 +08:00
|
|
|
|
2023-05-21 09:50:53 +08:00
|
|
|
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
2014-03-15 19:01:50 +08:00
|
|
|
|
2023-05-21 09:50:53 +08:00
|
|
|
Repo *Repository
|
|
|
|
Org *Organization
|
|
|
|
Package *Package
|
2022-05-05 22:13:23 +08:00
|
|
|
}
|
|
|
|
|
2023-06-18 15:59:09 +08:00
|
|
|
func init() {
|
|
|
|
web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
|
|
|
|
return req.Context().Value(WebContextKey).(*Context)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-05-08 17:36:54 +08:00
|
|
|
// TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString.
|
2021-11-17 02:18:25 +08:00
|
|
|
// This is useful if the locale message is intended to only produce HTML content.
|
|
|
|
func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
|
2023-07-05 02:36:08 +08:00
|
|
|
trArgs := make([]any, len(args))
|
2021-11-17 02:18:25 +08:00
|
|
|
for i, arg := range args {
|
|
|
|
trArgs[i] = html.EscapeString(arg)
|
|
|
|
}
|
2023-05-08 17:36:54 +08:00
|
|
|
return ctx.Locale.Tr(msg, trArgs...)
|
2021-01-24 23:23:05 +08:00
|
|
|
}
|
|
|
|
|
2023-05-23 09:29:15 +08:00
|
|
|
type webContextKeyType struct{}
|
2021-12-15 14:59:57 +08:00
|
|
|
|
2023-05-23 09:29:15 +08:00
|
|
|
var WebContextKey = webContextKeyType{}
|
2021-01-26 23:36:53 +08:00
|
|
|
|
2023-05-23 09:29:15 +08:00
|
|
|
func GetWebContext(req *http.Request) *Context {
|
|
|
|
ctx, _ := req.Context().Value(WebContextKey).(*Context)
|
2023-05-21 09:50:53 +08:00
|
|
|
return ctx
|
2021-01-26 23:36:53 +08:00
|
|
|
}
|
|
|
|
|
2023-05-21 09:50:53 +08:00
|
|
|
// ValidateContext is a special context for form validation middleware. It may be different from other contexts.
|
|
|
|
type ValidateContext struct {
|
|
|
|
*Base
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidateContext gets a context for middleware form validation
|
|
|
|
func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
|
|
|
|
if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
|
|
|
|
ctx = &ValidateContext{Base: ctxAPI.Base}
|
2023-05-23 09:29:15 +08:00
|
|
|
} else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok {
|
2023-05-21 09:50:53 +08:00
|
|
|
ctx = &ValidateContext{Base: ctxWeb.Base}
|
|
|
|
} else {
|
|
|
|
panic("invalid context, expect either APIContext or Context")
|
2023-04-14 03:45:33 +08:00
|
|
|
}
|
2023-05-21 09:50:53 +08:00
|
|
|
return ctx
|
2021-01-26 23:36:53 +08:00
|
|
|
}
|
|
|
|
|
2023-05-08 17:36:54 +08:00
|
|
|
// Contexter initializes a classic context for a request.
|
|
|
|
func Contexter() func(next http.Handler) http.Handler {
|
|
|
|
rnd := templates.HTMLRenderer()
|
|
|
|
csrfOpts := CsrfOptions{
|
2021-01-26 23:36:53 +08:00
|
|
|
Secret: setting.SecretKey,
|
|
|
|
Cookie: setting.CSRFCookieName,
|
|
|
|
SetCookie: true,
|
|
|
|
Secure: setting.SessionConfig.Secure,
|
|
|
|
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
|
|
|
|
Header: "X-Csrf-Token",
|
|
|
|
CookieDomain: setting.SessionConfig.Domain,
|
|
|
|
CookiePath: setting.SessionConfig.CookiePath,
|
2021-03-07 16:12:43 +08:00
|
|
|
SameSite: setting.SessionConfig.SameSite,
|
2021-01-26 23:36:53 +08:00
|
|
|
}
|
2022-04-08 13:21:05 +08:00
|
|
|
if !setting.IsProd {
|
|
|
|
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
|
|
|
|
}
|
2021-01-26 23:36:53 +08:00
|
|
|
return func(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
2023-05-21 09:50:53 +08:00
|
|
|
base, baseCleanUp := NewBaseContext(resp, req)
|
|
|
|
ctx := &Context{
|
|
|
|
Base: base,
|
2021-01-27 22:56:54 +08:00
|
|
|
Cache: mc.GetCache(),
|
2023-05-04 14:36:34 +08:00
|
|
|
Link: setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/"),
|
2021-01-26 23:36:53 +08:00
|
|
|
Render: rnd,
|
|
|
|
Session: session.GetSession(req),
|
2023-05-21 09:50:53 +08:00
|
|
|
Repo: &Repository{PullRequest: &PullRequest{}},
|
|
|
|
Org: &Organization{},
|
2021-01-26 23:36:53 +08:00
|
|
|
}
|
2023-05-21 09:50:53 +08:00
|
|
|
defer baseCleanUp()
|
2022-05-05 22:13:23 +08:00
|
|
|
|
2023-05-04 14:36:34 +08:00
|
|
|
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
|
|
|
ctx.Data["Context"] = &ctx
|
|
|
|
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
|
|
|
ctx.Data["Link"] = ctx.Link
|
|
|
|
ctx.Data["locale"] = ctx.Locale
|
|
|
|
|
2021-10-15 10:35:26 +08:00
|
|
|
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
|
2023-05-04 14:36:34 +08:00
|
|
|
ctx.PageData = map[string]any{}
|
2021-10-13 02:11:35 +08:00
|
|
|
ctx.Data["PageData"] = ctx.PageData
|
2021-01-26 23:36:53 +08:00
|
|
|
|
2023-05-23 09:29:15 +08:00
|
|
|
ctx.Base.AppendContextValue(WebContextKey, ctx)
|
2023-05-21 09:50:53 +08:00
|
|
|
ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
|
|
|
|
|
|
|
|
ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
|
2021-01-26 23:36:53 +08:00
|
|
|
|
2023-04-14 03:45:33 +08:00
|
|
|
// Get the last flash message from cookie
|
|
|
|
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
|
|
|
|
if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
|
|
|
|
// store last Flash message into the template data, to render it
|
|
|
|
ctx.Data["Flash"] = &middleware.Flash{
|
2023-05-21 09:50:53 +08:00
|
|
|
DataStore: ctx,
|
2021-01-26 23:36:53 +08:00
|
|
|
Values: vals,
|
|
|
|
ErrorMsg: vals.Get("error"),
|
|
|
|
SuccessMsg: vals.Get("success"),
|
|
|
|
InfoMsg: vals.Get("info"),
|
|
|
|
WarningMsg: vals.Get("warning"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-14 03:45:33 +08:00
|
|
|
// prepare an empty Flash message for current request
|
2023-05-21 09:50:53 +08:00
|
|
|
ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
|
2021-01-26 23:36:53 +08:00
|
|
|
ctx.Resp.Before(func(resp ResponseWriter) {
|
2023-04-14 03:45:33 +08:00
|
|
|
if val := ctx.Flash.Encode(); val != "" {
|
|
|
|
middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0)
|
|
|
|
} else if lastFlashCookie != "" {
|
|
|
|
middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, "", -1)
|
2021-01-26 23:36:53 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
|
|
|
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
|
|
|
|
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
|
|
|
|
ctx.ServerError("ParseMultipartForm", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2018-08-27 10:23:27 +08:00
|
|
|
|
2023-03-09 04:40:04 +08:00
|
|
|
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
|
2021-08-07 04:47:10 +08:00
|
|
|
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
2021-01-26 23:36:53 +08:00
|
|
|
|
2023-04-14 03:45:33 +08:00
|
|
|
ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
|
2021-01-26 23:36:53 +08:00
|
|
|
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
|
|
|
|
2021-05-05 05:48:31 +08:00
|
|
|
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
|
2021-01-26 23:36:53 +08:00
|
|
|
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
2021-04-16 00:53:57 +08:00
|
|
|
ctx.Data["DisableStars"] = setting.Repository.DisableStars
|
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>
2023-01-31 09:45:19 +08:00
|
|
|
ctx.Data["EnableActions"] = setting.Actions.Enabled
|
2021-01-26 23:36:53 +08:00
|
|
|
|
|
|
|
ctx.Data["ManifestData"] = setting.ManifestData
|
|
|
|
|
2021-11-10 03:57:58 +08:00
|
|
|
ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled()
|
|
|
|
ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled()
|
|
|
|
ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
|
|
|
|
ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
|
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>
2023-01-31 09:45:19 +08:00
|
|
|
ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
|
2021-05-05 05:48:31 +08:00
|
|
|
|
2021-01-26 23:36:53 +08:00
|
|
|
ctx.Data["AllLangs"] = translation.AllLangs()
|
2020-12-22 19:13:50 +08:00
|
|
|
|
2021-01-26 23:36:53 +08:00
|
|
|
next.ServeHTTP(ctx.Resp, ctx.Req)
|
|
|
|
})
|
2014-03-15 19:01:50 +08:00
|
|
|
}
|
|
|
|
}
|
2023-05-21 09:50:53 +08:00
|
|
|
|
|
|
|
// HasError returns true if error occurs in form validation.
|
|
|
|
// Attention: this function changes ctx.Data and ctx.Flash
|
|
|
|
func (ctx *Context) HasError() bool {
|
|
|
|
hasErr, ok := ctx.Data["HasError"]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
ctx.Flash.ErrorMsg = ctx.GetErrMsg()
|
|
|
|
ctx.Data["Flash"] = ctx.Flash
|
|
|
|
return hasErr.(bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetErrMsg returns error message in form validation.
|
|
|
|
func (ctx *Context) GetErrMsg() string {
|
|
|
|
msg, _ := ctx.Data["ErrorMsg"].(string)
|
|
|
|
if msg == "" {
|
|
|
|
msg = "invalid form data"
|
|
|
|
}
|
|
|
|
return msg
|
|
|
|
}
|
2023-07-26 14:04:01 +08:00
|
|
|
|
|
|
|
func (ctx *Context) JSONRedirect(redirect string) {
|
|
|
|
ctx.JSON(http.StatusOK, map[string]any{"redirect": redirect})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *Context) JSONOK() {
|
|
|
|
ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *Context) JSONError(msg string) {
|
|
|
|
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": msg})
|
|
|
|
}
|