gitea源码

runner.go 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package runner
  4. import (
  5. "context"
  6. "errors"
  7. "net/http"
  8. actions_model "code.gitea.io/gitea/models/actions"
  9. repo_model "code.gitea.io/gitea/models/repo"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/actions"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/util"
  14. actions_service "code.gitea.io/gitea/services/actions"
  15. notify_service "code.gitea.io/gitea/services/notify"
  16. runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
  17. "code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
  18. "connectrpc.com/connect"
  19. gouuid "github.com/google/uuid"
  20. "google.golang.org/grpc/codes"
  21. "google.golang.org/grpc/status"
  22. )
  23. func NewRunnerServiceHandler() (string, http.Handler) {
  24. return runnerv1connect.NewRunnerServiceHandler(
  25. &Service{},
  26. connect.WithCompressMinBytes(1024),
  27. withRunner,
  28. )
  29. }
  30. var _ runnerv1connect.RunnerServiceClient = (*Service)(nil)
  31. type Service struct{}
  32. // Register for new runner.
  33. func (s *Service) Register(
  34. ctx context.Context,
  35. req *connect.Request[runnerv1.RegisterRequest],
  36. ) (*connect.Response[runnerv1.RegisterResponse], error) {
  37. if req.Msg.Token == "" || req.Msg.Name == "" {
  38. return nil, errors.New("missing runner token, name")
  39. }
  40. runnerToken, err := actions_model.GetRunnerToken(ctx, req.Msg.Token)
  41. if err != nil {
  42. return nil, errors.New("runner registration token not found")
  43. }
  44. if !runnerToken.IsActive {
  45. return nil, errors.New("runner registration token has been invalidated, please use the latest one")
  46. }
  47. if runnerToken.OwnerID > 0 {
  48. if _, err := user_model.GetUserByID(ctx, runnerToken.OwnerID); err != nil {
  49. return nil, errors.New("owner of the token not found")
  50. }
  51. }
  52. if runnerToken.RepoID > 0 {
  53. if _, err := repo_model.GetRepositoryByID(ctx, runnerToken.RepoID); err != nil {
  54. return nil, errors.New("repository of the token not found")
  55. }
  56. }
  57. labels := req.Msg.Labels
  58. // create new runner
  59. name := util.EllipsisDisplayString(req.Msg.Name, 255)
  60. runner := &actions_model.ActionRunner{
  61. UUID: gouuid.New().String(),
  62. Name: name,
  63. OwnerID: runnerToken.OwnerID,
  64. RepoID: runnerToken.RepoID,
  65. Version: req.Msg.Version,
  66. AgentLabels: labels,
  67. Ephemeral: req.Msg.Ephemeral,
  68. }
  69. if err := runner.GenerateToken(); err != nil {
  70. return nil, errors.New("can't generate token")
  71. }
  72. // create new runner
  73. if err := actions_model.CreateRunner(ctx, runner); err != nil {
  74. return nil, errors.New("can't create new runner")
  75. }
  76. // update token status
  77. runnerToken.IsActive = true
  78. if err := actions_model.UpdateRunnerToken(ctx, runnerToken, "is_active"); err != nil {
  79. return nil, errors.New("can't update runner token status")
  80. }
  81. res := connect.NewResponse(&runnerv1.RegisterResponse{
  82. Runner: &runnerv1.Runner{
  83. Id: runner.ID,
  84. Uuid: runner.UUID,
  85. Token: runner.Token,
  86. Name: runner.Name,
  87. Version: runner.Version,
  88. Labels: runner.AgentLabels,
  89. Ephemeral: runner.Ephemeral,
  90. },
  91. })
  92. return res, nil
  93. }
  94. func (s *Service) Declare(
  95. ctx context.Context,
  96. req *connect.Request[runnerv1.DeclareRequest],
  97. ) (*connect.Response[runnerv1.DeclareResponse], error) {
  98. runner := GetRunner(ctx)
  99. runner.AgentLabels = req.Msg.Labels
  100. runner.Version = req.Msg.Version
  101. if err := actions_model.UpdateRunner(ctx, runner, "agent_labels", "version"); err != nil {
  102. return nil, status.Errorf(codes.Internal, "update runner: %v", err)
  103. }
  104. return connect.NewResponse(&runnerv1.DeclareResponse{
  105. Runner: &runnerv1.Runner{
  106. Id: runner.ID,
  107. Uuid: runner.UUID,
  108. Token: runner.Token,
  109. Name: runner.Name,
  110. Version: runner.Version,
  111. Labels: runner.AgentLabels,
  112. },
  113. }), nil
  114. }
  115. // FetchTask assigns a task to the runner
  116. func (s *Service) FetchTask(
  117. ctx context.Context,
  118. req *connect.Request[runnerv1.FetchTaskRequest],
  119. ) (*connect.Response[runnerv1.FetchTaskResponse], error) {
  120. runner := GetRunner(ctx)
  121. var task *runnerv1.Task
  122. tasksVersion := req.Msg.TasksVersion // task version from runner
  123. latestVersion, err := actions_model.GetTasksVersionByScope(ctx, runner.OwnerID, runner.RepoID)
  124. if err != nil {
  125. return nil, status.Errorf(codes.Internal, "query tasks version failed: %v", err)
  126. } else if latestVersion == 0 {
  127. if err := actions_model.IncreaseTaskVersion(ctx, runner.OwnerID, runner.RepoID); err != nil {
  128. return nil, status.Errorf(codes.Internal, "fail to increase task version: %v", err)
  129. }
  130. // if we don't increase the value of `latestVersion` here,
  131. // the response of FetchTask will return tasksVersion as zero.
  132. // and the runner will treat it as an old version of Gitea.
  133. latestVersion++
  134. }
  135. if tasksVersion != latestVersion {
  136. // if the task version in request is not equal to the version in db,
  137. // it means there may still be some tasks that haven't been assigned.
  138. // try to pick a task for the runner that send the request.
  139. if t, ok, err := actions_service.PickTask(ctx, runner); err != nil {
  140. log.Error("pick task failed: %v", err)
  141. return nil, status.Errorf(codes.Internal, "pick task: %v", err)
  142. } else if ok {
  143. task = t
  144. }
  145. }
  146. res := connect.NewResponse(&runnerv1.FetchTaskResponse{
  147. Task: task,
  148. TasksVersion: latestVersion,
  149. })
  150. return res, nil
  151. }
  152. // UpdateTask updates the task status.
  153. func (s *Service) UpdateTask(
  154. ctx context.Context,
  155. req *connect.Request[runnerv1.UpdateTaskRequest],
  156. ) (*connect.Response[runnerv1.UpdateTaskResponse], error) {
  157. runner := GetRunner(ctx)
  158. task, err := actions_model.UpdateTaskByState(ctx, runner.ID, req.Msg.State)
  159. if err != nil {
  160. return nil, status.Errorf(codes.Internal, "update task: %v", err)
  161. }
  162. for k, v := range req.Msg.Outputs {
  163. if len(k) > 255 {
  164. log.Warn("Ignore the output of task %d because the key is too long: %q", task.ID, k)
  165. continue
  166. }
  167. // The value can be a maximum of 1 MB
  168. if l := len(v); l > 1024*1024 {
  169. log.Warn("Ignore the output %q of task %d because the value is too long: %v", k, task.ID, l)
  170. continue
  171. }
  172. // There's another limitation on GitHub that the total of all outputs in a workflow run can be a maximum of 50 MB.
  173. // We don't check the total size here because it's not easy to do, and it doesn't really worth it.
  174. // See https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs
  175. if err := actions_model.InsertTaskOutputIfNotExist(ctx, task.ID, k, v); err != nil {
  176. log.Warn("Failed to insert the output %q of task %d: %v", k, task.ID, err)
  177. // It's ok not to return errors, the runner will resend the outputs.
  178. }
  179. }
  180. sentOutputs, err := actions_model.FindTaskOutputKeyByTaskID(ctx, task.ID)
  181. if err != nil {
  182. log.Warn("Failed to find the sent outputs of task %d: %v", task.ID, err)
  183. // It's not to return errors, it can be handled when the runner resends sent outputs.
  184. }
  185. if err := task.LoadJob(ctx); err != nil {
  186. return nil, status.Errorf(codes.Internal, "load job: %v", err)
  187. }
  188. if err := task.Job.LoadAttributes(ctx); err != nil {
  189. return nil, status.Errorf(codes.Internal, "load run: %v", err)
  190. }
  191. // don't create commit status for cron job
  192. if task.Job.Run.ScheduleID == 0 {
  193. actions_service.CreateCommitStatus(ctx, task.Job)
  194. }
  195. if task.Status.IsDone() {
  196. notify_service.WorkflowJobStatusUpdate(ctx, task.Job.Run.Repo, task.Job.Run.TriggerUser, task.Job, task)
  197. }
  198. if req.Msg.State.Result != runnerv1.Result_RESULT_UNSPECIFIED {
  199. if err := actions_service.EmitJobsIfReady(task.Job.RunID); err != nil {
  200. log.Error("Emit ready jobs of run %d: %v", task.Job.RunID, err)
  201. }
  202. }
  203. return connect.NewResponse(&runnerv1.UpdateTaskResponse{
  204. State: &runnerv1.TaskState{
  205. Id: req.Msg.State.Id,
  206. Result: task.Status.AsResult(),
  207. },
  208. SentOutputs: sentOutputs,
  209. }), nil
  210. }
  211. // UpdateLog uploads log of the task.
  212. func (s *Service) UpdateLog(
  213. ctx context.Context,
  214. req *connect.Request[runnerv1.UpdateLogRequest],
  215. ) (*connect.Response[runnerv1.UpdateLogResponse], error) {
  216. runner := GetRunner(ctx)
  217. res := connect.NewResponse(&runnerv1.UpdateLogResponse{})
  218. task, err := actions_model.GetTaskByID(ctx, req.Msg.TaskId)
  219. if err != nil {
  220. return nil, status.Errorf(codes.Internal, "get task: %v", err)
  221. } else if runner.ID != task.RunnerID {
  222. return nil, status.Errorf(codes.Internal, "invalid runner for task")
  223. }
  224. ack := task.LogLength
  225. if len(req.Msg.Rows) == 0 || req.Msg.Index > ack || int64(len(req.Msg.Rows))+req.Msg.Index <= ack {
  226. res.Msg.AckIndex = ack
  227. return res, nil
  228. }
  229. if task.LogInStorage {
  230. return nil, status.Errorf(codes.AlreadyExists, "log file has been archived")
  231. }
  232. rows := req.Msg.Rows[ack-req.Msg.Index:]
  233. ns, err := actions.WriteLogs(ctx, task.LogFilename, task.LogSize, rows)
  234. if err != nil {
  235. return nil, status.Errorf(codes.Internal, "write logs: %v", err)
  236. }
  237. task.LogLength += int64(len(rows))
  238. for _, n := range ns {
  239. task.LogIndexes = append(task.LogIndexes, task.LogSize)
  240. task.LogSize += int64(n)
  241. }
  242. res.Msg.AckIndex = task.LogLength
  243. var remove func()
  244. if req.Msg.NoMore {
  245. task.LogInStorage = true
  246. remove, err = actions.TransferLogs(ctx, task.LogFilename)
  247. if err != nil {
  248. return nil, status.Errorf(codes.Internal, "transfer logs: %v", err)
  249. }
  250. }
  251. if err := actions_model.UpdateTask(ctx, task, "log_indexes", "log_length", "log_size", "log_in_storage"); err != nil {
  252. return nil, status.Errorf(codes.Internal, "update task: %v", err)
  253. }
  254. if remove != nil {
  255. remove()
  256. }
  257. return res, nil
  258. }