mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Support changing labels of Actions runner without re-registration (#24806)
close #24540 related: - Protocol: https://gitea.com/gitea/actions-proto-def/pulls/9 - Runner side: https://gitea.com/gitea/act_runner/pulls/201 changes: - Add column of `labels` to table `action_runner`, and combine the value of `agent_labels` and `custom_labels` column to `labels` column. - Store `labels` when registering `act_runner`. - Update `labels` when `act_runner` starting and calling `Declare`. - Users cannot modify the `custom labels` in edit page any more. other changes: - Store `version` when registering `act_runner`. - If runner is latest version, parse version from `Declare`. But older version runner still parse version from request header.
This commit is contained in:
		
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @@ -3,7 +3,7 @@ module code.gitea.io/gitea | ||||
| go 1.20 | ||||
|  | ||||
| require ( | ||||
| 	code.gitea.io/actions-proto-go v0.2.1 | ||||
| 	code.gitea.io/actions-proto-go v0.3.0 | ||||
| 	code.gitea.io/gitea-vet v0.2.2 | ||||
| 	code.gitea.io/sdk/gitea v0.15.1 | ||||
| 	codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							| @@ -40,8 +40,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl | ||||
| cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= | ||||
| cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= | ||||
| cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= | ||||
| code.gitea.io/actions-proto-go v0.2.1 h1:ToMN/8thz2q10TuCq8dL2d8mI+/pWpJcHCvG+TELwa0= | ||||
| code.gitea.io/actions-proto-go v0.2.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= | ||||
| code.gitea.io/actions-proto-go v0.3.0 h1:9Tvg8+TaaCXPKi6EnWl9vVgs2VZsj1Cs5afnsHa4AmM= | ||||
| code.gitea.io/actions-proto-go v0.3.0/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= | ||||
| code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= | ||||
| code.gitea.io/gitea-vet v0.2.2 h1:TEOV/Glf38iGmKzKP0EB++Z5OSL4zGg3RrAvlwaMuvk= | ||||
| code.gitea.io/gitea-vet v0.2.2/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= | ||||
|   | ||||
| @@ -43,10 +43,8 @@ type ActionRunner struct { | ||||
| 	LastOnline timeutil.TimeStamp `xorm:"index"` | ||||
| 	LastActive timeutil.TimeStamp `xorm:"index"` | ||||
|  | ||||
| 	// Store OS and Artch. | ||||
| 	AgentLabels []string | ||||
| 	// Store custom labes use defined. | ||||
| 	CustomLabels []string | ||||
| 	// Store labels defined in state file (default: .runner file) of `act_runner` | ||||
| 	AgentLabels []string `xorm:"TEXT"` | ||||
|  | ||||
| 	Created timeutil.TimeStamp `xorm:"created"` | ||||
| 	Updated timeutil.TimeStamp `xorm:"updated"` | ||||
| @@ -104,11 +102,6 @@ func (r *ActionRunner) IsOnline() bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // AllLabels returns agent and custom labels | ||||
| func (r *ActionRunner) AllLabels() []string { | ||||
| 	return append(r.AgentLabels, r.CustomLabels...) | ||||
| } | ||||
|  | ||||
| // Editable checks if the runner is editable by the user | ||||
| func (r *ActionRunner) Editable(ownerID, repoID int64) bool { | ||||
| 	if ownerID == 0 && repoID == 0 { | ||||
|   | ||||
| @@ -241,11 +241,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask | ||||
|  | ||||
| 	// TODO: a more efficient way to filter labels | ||||
| 	var job *ActionRunJob | ||||
| 	labels := runner.AgentLabels | ||||
| 	labels = append(labels, runner.CustomLabels...) | ||||
| 	log.Trace("runner labels: %v", labels) | ||||
| 	log.Trace("runner labels: %v", runner.AgentLabels) | ||||
| 	for _, v := range jobs { | ||||
| 		if isSubset(labels, v.RunsOn) { | ||||
| 		if isSubset(runner.AgentLabels, v.RunsOn) { | ||||
| 			job = v | ||||
| 			break | ||||
| 		} | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/migrations/v1_18" | ||||
| 	"code.gitea.io/gitea/models/migrations/v1_19" | ||||
| 	"code.gitea.io/gitea/models/migrations/v1_20" | ||||
| 	"code.gitea.io/gitea/models/migrations/v1_21" | ||||
| 	"code.gitea.io/gitea/models/migrations/v1_6" | ||||
| 	"code.gitea.io/gitea/models/migrations/v1_7" | ||||
| 	"code.gitea.io/gitea/models/migrations/v1_8" | ||||
| @@ -497,6 +498,11 @@ var migrations = []Migration{ | ||||
| 	NewMigration("Add PinOrder Column", v1_20.AddPinOrderToIssue), | ||||
| 	// v259 -> 260 | ||||
| 	NewMigration("Convert scoped access tokens", v1_20.ConvertScopedAccessTokens), | ||||
|  | ||||
| 	// Gitea 1.21.0 ends at 260 | ||||
|  | ||||
| 	// v260 -> v261 | ||||
| 	NewMigration("Add label column to action_run table, and combine labels", v1_21.DropCustomLabelsColumnToActRunner), | ||||
| } | ||||
|  | ||||
| // GetCurrentDBVersion returns the current db version | ||||
|   | ||||
							
								
								
									
										14
									
								
								models/migrations/v1_21/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								models/migrations/v1_21/main_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // Copyright 2023 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package v1_21 //nolint | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/migrations/base" | ||||
| ) | ||||
|  | ||||
| func TestMain(m *testing.M) { | ||||
| 	base.MainTest(m) | ||||
| } | ||||
							
								
								
									
										26
									
								
								models/migrations/v1_21/v260.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								models/migrations/v1_21/v260.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // Copyright 2023 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package v1_21 //nolint | ||||
|  | ||||
| import ( | ||||
| 	"code.gitea.io/gitea/models/migrations/base" | ||||
|  | ||||
| 	"xorm.io/xorm" | ||||
| ) | ||||
|  | ||||
| func DropCustomLabelsColumnToActRunner(x *xorm.Engine) error { | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
|  | ||||
| 	if err := sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// drop "custom_labels" cols | ||||
| 	if err := base.DropTableColumns(sess, "action_runner", "custom_labels"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return sess.Commit() | ||||
| } | ||||
| @@ -3426,11 +3426,9 @@ runners.owner_type = Type | ||||
| runners.description = Description | ||||
| runners.labels = Labels | ||||
| runners.last_online = Last Online Time | ||||
| runners.agent_labels = Agent Labels | ||||
| runners.custom_labels = Custom Labels | ||||
| runners.custom_labels_helper = Custom labels are labels that are added manually by an administrator. A comma separates labels, whitespace at the start and end of each label is ignored. | ||||
| runners.runner_title = Runner | ||||
| runners.task_list = Recent tasks on this runner | ||||
| runners.task_list.no_tasks = There is no task yet. | ||||
| runners.task_list.run = Run | ||||
| runners.task_list.status = Status | ||||
| runners.task_list.repository = Repository | ||||
|   | ||||
| @@ -21,11 +21,10 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	uuidHeaderKey    = "x-runner-uuid" | ||||
| 	tokenHeaderKey   = "x-runner-token" | ||||
| 	uuidHeaderKey  = "x-runner-uuid" | ||||
| 	tokenHeaderKey = "x-runner-token" | ||||
| 	// Deprecated: will be removed after Gitea 1.20 released. | ||||
| 	versionHeaderKey = "x-runner-version" | ||||
|  | ||||
| 	versionUnknown = "Unknown" | ||||
| ) | ||||
|  | ||||
| var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unaryFunc connect.UnaryFunc) connect.UnaryFunc { | ||||
| @@ -36,11 +35,9 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar | ||||
| 		} | ||||
| 		uuid := request.Header().Get(uuidHeaderKey) | ||||
| 		token := request.Header().Get(tokenHeaderKey) | ||||
| 		// TODO: version will be removed from request header after Gitea 1.20 released. | ||||
| 		// And Gitea will not try to read version from reuqest header | ||||
| 		version := request.Header().Get(versionHeaderKey) | ||||
| 		if util.IsEmptyString(version) { | ||||
| 			version = versionUnknown | ||||
| 		} | ||||
| 		version, _ = util.SplitStringAtByteN(version, 64) | ||||
|  | ||||
| 		runner, err := actions_model.GetRunnerByUUID(ctx, uuid) | ||||
| 		if err != nil { | ||||
| @@ -54,7 +51,11 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar | ||||
| 		} | ||||
|  | ||||
| 		cols := []string{"last_online"} | ||||
| 		if runner.Version != version { | ||||
|  | ||||
| 		// TODO: version will be removed from request header after Gitea 1.20 released. | ||||
| 		// And Gitea will not try to read version from reuqest header | ||||
| 		version, _ = util.SplitStringAtByteN(version, 64) | ||||
| 		if !util.IsEmptyString(version) && runner.Version != version { | ||||
| 			runner.Version = version | ||||
| 			cols = append(cols, "version") | ||||
| 		} | ||||
|   | ||||
| @@ -54,15 +54,23 @@ func (s *Service) Register( | ||||
| 		return nil, errors.New("runner token has already been activated") | ||||
| 	} | ||||
|  | ||||
| 	labels := req.Msg.Labels | ||||
| 	// TODO: agent_labels should be removed from pb after Gitea 1.20 released. | ||||
| 	// Old version runner's agent_labels slice is not empty and labels slice is empty. | ||||
| 	// And due to compatibility with older versions, it is temporarily marked as Deprecated in pb, so use `//nolint` here. | ||||
| 	if len(req.Msg.AgentLabels) > 0 && len(req.Msg.Labels) == 0 { //nolint:staticcheck | ||||
| 		labels = req.Msg.AgentLabels //nolint:staticcheck | ||||
| 	} | ||||
|  | ||||
| 	// create new runner | ||||
| 	name, _ := util.SplitStringAtByteN(req.Msg.Name, 255) | ||||
| 	runner := &actions_model.ActionRunner{ | ||||
| 		UUID:         gouuid.New().String(), | ||||
| 		Name:         name, | ||||
| 		OwnerID:      runnerToken.OwnerID, | ||||
| 		RepoID:       runnerToken.RepoID, | ||||
| 		AgentLabels:  req.Msg.AgentLabels, | ||||
| 		CustomLabels: req.Msg.CustomLabels, | ||||
| 		UUID:        gouuid.New().String(), | ||||
| 		Name:        name, | ||||
| 		OwnerID:     runnerToken.OwnerID, | ||||
| 		RepoID:      runnerToken.RepoID, | ||||
| 		Version:     req.Msg.Version, | ||||
| 		AgentLabels: labels, | ||||
| 	} | ||||
| 	if err := runner.GenerateToken(); err != nil { | ||||
| 		return nil, errors.New("can't generate token") | ||||
| @@ -81,18 +89,41 @@ func (s *Service) Register( | ||||
|  | ||||
| 	res := connect.NewResponse(&runnerv1.RegisterResponse{ | ||||
| 		Runner: &runnerv1.Runner{ | ||||
| 			Id:           runner.ID, | ||||
| 			Uuid:         runner.UUID, | ||||
| 			Token:        runner.Token, | ||||
| 			Name:         runner.Name, | ||||
| 			AgentLabels:  runner.AgentLabels, | ||||
| 			CustomLabels: runner.CustomLabels, | ||||
| 			Id:      runner.ID, | ||||
| 			Uuid:    runner.UUID, | ||||
| 			Token:   runner.Token, | ||||
| 			Name:    runner.Name, | ||||
| 			Version: runner.Version, | ||||
| 			Labels:  runner.AgentLabels, | ||||
| 		}, | ||||
| 	}) | ||||
|  | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| func (s *Service) Declare( | ||||
| 	ctx context.Context, | ||||
| 	req *connect.Request[runnerv1.DeclareRequest], | ||||
| ) (*connect.Response[runnerv1.DeclareResponse], error) { | ||||
| 	runner := GetRunner(ctx) | ||||
| 	runner.AgentLabels = req.Msg.Labels | ||||
| 	runner.Version = req.Msg.Version | ||||
| 	if err := actions_model.UpdateRunner(ctx, runner, "agent_labels", "version"); err != nil { | ||||
| 		return nil, status.Errorf(codes.Internal, "update runner: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return connect.NewResponse(&runnerv1.DeclareResponse{ | ||||
| 		Runner: &runnerv1.Runner{ | ||||
| 			Id:      runner.ID, | ||||
| 			Uuid:    runner.UUID, | ||||
| 			Token:   runner.Token, | ||||
| 			Name:    runner.Name, | ||||
| 			Version: runner.Version, | ||||
| 			Labels:  runner.AgentLabels, | ||||
| 		}, | ||||
| 	}), nil | ||||
| } | ||||
|  | ||||
| // FetchTask assigns a task to the runner | ||||
| func (s *Service) FetchTask( | ||||
| 	ctx context.Context, | ||||
|   | ||||
| @@ -84,7 +84,6 @@ func List(ctx *context.Context) { | ||||
| 		allRunnerLabels := make(container.Set[string]) | ||||
| 		for _, r := range runners { | ||||
| 			allRunnerLabels.AddMultiple(r.AgentLabels...) | ||||
| 			allRunnerLabels.AddMultiple(r.CustomLabels...) | ||||
| 		} | ||||
|  | ||||
| 		workflows = make([]Workflow, 0, len(entries)) | ||||
|   | ||||
| @@ -6,7 +6,6 @@ package actions | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	actions_model "code.gitea.io/gitea/models/actions" | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| @@ -126,9 +125,8 @@ func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64 | ||||
|  | ||||
| 	form := web.GetForm(ctx).(*forms.EditRunnerForm) | ||||
| 	runner.Description = form.Description | ||||
| 	runner.CustomLabels = splitLabels(form.CustomLabels) | ||||
|  | ||||
| 	err = actions_model.UpdateRunner(ctx, runner, "description", "custom_labels") | ||||
| 	err = actions_model.UpdateRunner(ctx, runner, "description") | ||||
| 	if err != nil { | ||||
| 		log.Warn("RunnerDetailsEditPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL) | ||||
| 		ctx.Flash.Warning(ctx.Tr("actions.runners.update_runner_failed")) | ||||
| @@ -176,11 +174,3 @@ func RunnerDeletePost(ctx *context.Context, runnerID int64, | ||||
| 		"redirect": successRedirectTo, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func splitLabels(s string) []string { | ||||
| 	labels := strings.Split(s, ",") | ||||
| 	for i, v := range labels { | ||||
| 		labels[i] = strings.TrimSpace(v) | ||||
| 	} | ||||
| 	return labels | ||||
| } | ||||
|   | ||||
| @@ -14,8 +14,7 @@ import ( | ||||
|  | ||||
| // EditRunnerForm form for admin to create runner | ||||
| type EditRunnerForm struct { | ||||
| 	Description  string | ||||
| 	CustomLabels string // comma-separated | ||||
| 	Description string | ||||
| } | ||||
|  | ||||
| // Validate validates form fields | ||||
|   | ||||
| @@ -13,10 +13,10 @@ | ||||
| 				</div> | ||||
| 				<div class="field gt-dib gt-mr-4"> | ||||
| 					<label>{{.locale.Tr "actions.runners.last_online"}}</label> | ||||
| 					<span>{{if .LastOnline}}{{TimeSinceUnix .LastOnline $.locale}}{{else}}{{$.locale.Tr "never"}}{{end}}</span> | ||||
| 					<span>{{if .Runner.LastOnline}}{{TimeSinceUnix .Runner.LastOnline $.locale}}{{else}}{{$.locale.Tr "never"}}{{end}}</span> | ||||
| 				</div> | ||||
| 				<div class="field gt-dib gt-mr-4"> | ||||
| 					<label>{{.locale.Tr "actions.runners.agent_labels"}}</label> | ||||
| 					<label>{{.locale.Tr "actions.runners.labels"}}</label> | ||||
| 					<span> | ||||
| 						{{range .Runner.AgentLabels}} | ||||
| 						<span>{{.}}</span> | ||||
| @@ -35,11 +35,6 @@ | ||||
| 				<label for="description">{{.locale.Tr "actions.runners.description"}}</label> | ||||
| 				<input id="description" name="description" value="{{.Runner.Description}}"> | ||||
| 			</div> | ||||
| 			<div class="field" data-tooltip-content="Labels are comma-separated. Whitespace at the beginning, end, and around the commas are ignored."> | ||||
| 				<label for="custom_labels">{{.locale.Tr "actions.runners.custom_labels"}}</label> | ||||
| 				<input id="custom_labels" name="custom_labels" value="{{StringUtils.Join .Runner.CustomLabels `,`}}"> | ||||
| 				<p class="help">{{.locale.Tr "actions.runners.custom_labels_helper"}}</p> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="ui divider"></div> | ||||
|  | ||||
| @@ -81,7 +76,7 @@ | ||||
| 				{{end}} | ||||
| 				{{if not .Tasks}} | ||||
| 				<tr> | ||||
| 					<td colspan="5">{{.locale.Tr "runners.task_list.no_tasks"}}</td> | ||||
| 					<td colspan="5">{{.locale.Tr "actions.runners.task_list.no_tasks"}}</td> | ||||
| 				</tr> | ||||
| 				{{end}} | ||||
| 			</tbody> | ||||
|   | ||||
| @@ -67,7 +67,7 @@ | ||||
| 						<td>{{if .Version}}{{.Version}}{{else}}{{$.locale.Tr "unknown"}}{{end}}</td> | ||||
| 						<td><span data-tooltip-content="{{.BelongsToOwnerName}}">{{.BelongsToOwnerType.LocaleString $.locale}}<span></td> | ||||
| 						<td class="runner-tags"> | ||||
| 							{{range .AllLabels}}<span class="ui label">{{.}}</span>{{end}} | ||||
| 							{{range .AgentLabels}}<span class="ui label">{{.}}</span>{{end}} | ||||
| 						</td> | ||||
| 						<td>{{if .LastOnline}}{{TimeSinceUnix .LastOnline $.locale}}{{else}}{{$.locale.Tr "never"}}{{end}}</td> | ||||
| 						<td class="runner-ops"> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user