mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 09:07:18 -04:00 
			
		
		
		
	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>
		
			
				
	
	
		
			266 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2017 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package repo
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 
 | |
| 	"code.gitea.io/gitea/models/db"
 | |
| 	"code.gitea.io/gitea/models/unit"
 | |
| 	"code.gitea.io/gitea/modules/json"
 | |
| 	"code.gitea.io/gitea/modules/setting"
 | |
| 	"code.gitea.io/gitea/modules/timeutil"
 | |
| 	"code.gitea.io/gitea/modules/util"
 | |
| 
 | |
| 	"xorm.io/xorm"
 | |
| 	"xorm.io/xorm/convert"
 | |
| )
 | |
| 
 | |
| // ErrUnitTypeNotExist represents a "UnitTypeNotExist" kind of error.
 | |
| type ErrUnitTypeNotExist struct {
 | |
| 	UT unit.Type
 | |
| }
 | |
| 
 | |
| // IsErrUnitTypeNotExist checks if an error is a ErrUnitNotExist.
 | |
| func IsErrUnitTypeNotExist(err error) bool {
 | |
| 	_, ok := err.(ErrUnitTypeNotExist)
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| func (err ErrUnitTypeNotExist) Error() string {
 | |
| 	return fmt.Sprintf("Unit type does not exist: %s", err.UT.String())
 | |
| }
 | |
| 
 | |
| func (err ErrUnitTypeNotExist) Unwrap() error {
 | |
| 	return util.ErrNotExist
 | |
| }
 | |
| 
 | |
| // RepoUnit describes all units of a repository
 | |
| type RepoUnit struct { //revive:disable-line:exported
 | |
| 	ID          int64
 | |
| 	RepoID      int64              `xorm:"INDEX(s)"`
 | |
| 	Type        unit.Type          `xorm:"INDEX(s)"`
 | |
| 	Config      convert.Conversion `xorm:"TEXT"`
 | |
| 	CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	db.RegisterModel(new(RepoUnit))
 | |
| }
 | |
| 
 | |
| // UnitConfig describes common unit config
 | |
| type UnitConfig struct{}
 | |
| 
 | |
| // FromDB fills up a UnitConfig from serialized format.
 | |
| func (cfg *UnitConfig) FromDB(bs []byte) error {
 | |
| 	return json.UnmarshalHandleDoubleEncode(bs, &cfg)
 | |
| }
 | |
| 
 | |
| // ToDB exports a UnitConfig to a serialized format.
 | |
| func (cfg *UnitConfig) ToDB() ([]byte, error) {
 | |
| 	return json.Marshal(cfg)
 | |
| }
 | |
| 
 | |
| // ExternalWikiConfig describes external wiki config
 | |
| type ExternalWikiConfig struct {
 | |
| 	ExternalWikiURL string
 | |
| }
 | |
| 
 | |
| // FromDB fills up a ExternalWikiConfig from serialized format.
 | |
| func (cfg *ExternalWikiConfig) FromDB(bs []byte) error {
 | |
| 	return json.UnmarshalHandleDoubleEncode(bs, &cfg)
 | |
| }
 | |
| 
 | |
| // ToDB exports a ExternalWikiConfig to a serialized format.
 | |
| func (cfg *ExternalWikiConfig) ToDB() ([]byte, error) {
 | |
| 	return json.Marshal(cfg)
 | |
| }
 | |
| 
 | |
| // ExternalTrackerConfig describes external tracker config
 | |
| type ExternalTrackerConfig struct {
 | |
| 	ExternalTrackerURL           string
 | |
| 	ExternalTrackerFormat        string
 | |
| 	ExternalTrackerStyle         string
 | |
| 	ExternalTrackerRegexpPattern string
 | |
| }
 | |
| 
 | |
| // FromDB fills up a ExternalTrackerConfig from serialized format.
 | |
| func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error {
 | |
| 	return json.UnmarshalHandleDoubleEncode(bs, &cfg)
 | |
| }
 | |
| 
 | |
| // ToDB exports a ExternalTrackerConfig to a serialized format.
 | |
| func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) {
 | |
| 	return json.Marshal(cfg)
 | |
| }
 | |
| 
 | |
| // IssuesConfig describes issues config
 | |
| type IssuesConfig struct {
 | |
| 	EnableTimetracker                bool
 | |
| 	AllowOnlyContributorsToTrackTime bool
 | |
| 	EnableDependencies               bool
 | |
| }
 | |
| 
 | |
| // FromDB fills up a IssuesConfig from serialized format.
 | |
| func (cfg *IssuesConfig) FromDB(bs []byte) error {
 | |
| 	return json.UnmarshalHandleDoubleEncode(bs, &cfg)
 | |
| }
 | |
| 
 | |
| // ToDB exports a IssuesConfig to a serialized format.
 | |
| func (cfg *IssuesConfig) ToDB() ([]byte, error) {
 | |
| 	return json.Marshal(cfg)
 | |
| }
 | |
| 
 | |
| // PullRequestsConfig describes pull requests config
 | |
| type PullRequestsConfig struct {
 | |
| 	IgnoreWhitespaceConflicts     bool
 | |
| 	AllowMerge                    bool
 | |
| 	AllowRebase                   bool
 | |
| 	AllowRebaseMerge              bool
 | |
| 	AllowSquash                   bool
 | |
| 	AllowManualMerge              bool
 | |
| 	AutodetectManualMerge         bool
 | |
| 	AllowRebaseUpdate             bool
 | |
| 	DefaultDeleteBranchAfterMerge bool
 | |
| 	DefaultMergeStyle             MergeStyle
 | |
| }
 | |
| 
 | |
| // FromDB fills up a PullRequestsConfig from serialized format.
 | |
| func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
 | |
| 	// AllowRebaseUpdate = true as default for existing PullRequestConfig in DB
 | |
| 	cfg.AllowRebaseUpdate = true
 | |
| 	return json.UnmarshalHandleDoubleEncode(bs, &cfg)
 | |
| }
 | |
| 
 | |
| // ToDB exports a PullRequestsConfig to a serialized format.
 | |
| func (cfg *PullRequestsConfig) ToDB() ([]byte, error) {
 | |
| 	return json.Marshal(cfg)
 | |
| }
 | |
| 
 | |
| // IsMergeStyleAllowed returns if merge style is allowed
 | |
| func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool {
 | |
| 	return mergeStyle == MergeStyleMerge && cfg.AllowMerge ||
 | |
| 		mergeStyle == MergeStyleRebase && cfg.AllowRebase ||
 | |
| 		mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge ||
 | |
| 		mergeStyle == MergeStyleSquash && cfg.AllowSquash ||
 | |
| 		mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge
 | |
| }
 | |
| 
 | |
| // GetDefaultMergeStyle returns the default merge style for this pull request
 | |
| func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {
 | |
| 	if len(cfg.DefaultMergeStyle) != 0 {
 | |
| 		return cfg.DefaultMergeStyle
 | |
| 	}
 | |
| 
 | |
| 	if setting.Repository.PullRequest.DefaultMergeStyle != "" {
 | |
| 		return MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle)
 | |
| 	}
 | |
| 
 | |
| 	return MergeStyleMerge
 | |
| }
 | |
| 
 | |
| // BeforeSet is invoked from XORM before setting the value of a field of this object.
 | |
| func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
 | |
| 	switch colName {
 | |
| 	case "type":
 | |
| 		switch unit.Type(db.Cell2Int64(val)) {
 | |
| 		case unit.TypeExternalWiki:
 | |
| 			r.Config = new(ExternalWikiConfig)
 | |
| 		case unit.TypeExternalTracker:
 | |
| 			r.Config = new(ExternalTrackerConfig)
 | |
| 		case unit.TypePullRequests:
 | |
| 			r.Config = new(PullRequestsConfig)
 | |
| 		case unit.TypeIssues:
 | |
| 			r.Config = new(IssuesConfig)
 | |
| 		case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages, unit.TypeActions:
 | |
| 			fallthrough
 | |
| 		default:
 | |
| 			r.Config = new(UnitConfig)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Unit returns Unit
 | |
| func (r *RepoUnit) Unit() unit.Unit {
 | |
| 	return unit.Units[r.Type]
 | |
| }
 | |
| 
 | |
| // CodeConfig returns config for unit.TypeCode
 | |
| func (r *RepoUnit) CodeConfig() *UnitConfig {
 | |
| 	return r.Config.(*UnitConfig)
 | |
| }
 | |
| 
 | |
| // PullRequestsConfig returns config for unit.TypePullRequests
 | |
| func (r *RepoUnit) PullRequestsConfig() *PullRequestsConfig {
 | |
| 	return r.Config.(*PullRequestsConfig)
 | |
| }
 | |
| 
 | |
| // ReleasesConfig returns config for unit.TypeReleases
 | |
| func (r *RepoUnit) ReleasesConfig() *UnitConfig {
 | |
| 	return r.Config.(*UnitConfig)
 | |
| }
 | |
| 
 | |
| // ExternalWikiConfig returns config for unit.TypeExternalWiki
 | |
| func (r *RepoUnit) ExternalWikiConfig() *ExternalWikiConfig {
 | |
| 	return r.Config.(*ExternalWikiConfig)
 | |
| }
 | |
| 
 | |
| // IssuesConfig returns config for unit.TypeIssues
 | |
| func (r *RepoUnit) IssuesConfig() *IssuesConfig {
 | |
| 	return r.Config.(*IssuesConfig)
 | |
| }
 | |
| 
 | |
| // ExternalTrackerConfig returns config for unit.TypeExternalTracker
 | |
| func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
 | |
| 	return r.Config.(*ExternalTrackerConfig)
 | |
| }
 | |
| 
 | |
| func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err error) {
 | |
| 	var tmpUnits []*RepoUnit
 | |
| 	if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, u := range tmpUnits {
 | |
| 		if !u.Type.UnitGlobalDisabled() {
 | |
| 			units = append(units, u)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return units, nil
 | |
| }
 | |
| 
 | |
| // UpdateRepoUnit updates the provided repo unit
 | |
| func UpdateRepoUnit(unit *RepoUnit) error {
 | |
| 	_, err := db.GetEngine(db.DefaultContext).ID(unit.ID).Update(unit)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // UpdateRepositoryUnits updates a repository's units
 | |
| func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) {
 | |
| 	ctx, committer, err := db.TxContext(db.DefaultContext)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer committer.Close()
 | |
| 
 | |
| 	// Delete existing settings of units before adding again
 | |
| 	for _, u := range units {
 | |
| 		deleteUnitTypes = append(deleteUnitTypes, u.Type)
 | |
| 	}
 | |
| 
 | |
| 	if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if len(units) > 0 {
 | |
| 		if err = db.Insert(ctx, units); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return committer.Commit()
 | |
| }
 |