|
4 | 4 | package repo
|
5 | 5 |
|
6 | 6 | import (
|
| 7 | + "code.gitea.io/gitea/models/perm" |
| 8 | + access_model "code.gitea.io/gitea/models/perm/access" |
| 9 | + "code.gitea.io/gitea/models/unit" |
| 10 | + "code.gitea.io/gitea/modules/actions" |
| 11 | + "code.gitea.io/gitea/modules/git" |
7 | 12 | "errors"
|
| 13 | + "github.com/nektos/act/pkg/jobparser" |
| 14 | + "github.com/nektos/act/pkg/model" |
8 | 15 | "net/http"
|
| 16 | + "strconv" |
| 17 | + "strings" |
9 | 18 |
|
10 | 19 | actions_model "code.gitea.io/gitea/models/actions"
|
11 | 20 | "code.gitea.io/gitea/models/db"
|
@@ -581,3 +590,271 @@ func ListActionTasks(ctx *context.APIContext) {
|
581 | 590 |
|
582 | 591 | ctx.JSON(http.StatusOK, &res)
|
583 | 592 | }
|
| 593 | + |
| 594 | +// ActionWorkflow implements actions_service.WorkflowAPI |
| 595 | +type ActionWorkflow struct{} |
| 596 | + |
| 597 | +// NewActionWorkflow creates a new ActionWorkflow service |
| 598 | +func NewActionWorkflow() actions_service.WorkflowAPI { |
| 599 | + return ActionWorkflow{} |
| 600 | +} |
| 601 | + |
| 602 | +func (a ActionWorkflow) ListRepositoryWorkflows(ctx *context.APIContext) { |
| 603 | + // swagger:operation GET /repos/{owner}/{repo}/actions/workflows repository ListRepositoryWorkflows |
| 604 | + // --- |
| 605 | + // summary: List repository workflows |
| 606 | + // produces: |
| 607 | + // - application/json |
| 608 | + // parameters: |
| 609 | + // - name: owner |
| 610 | + // in: path |
| 611 | + // description: owner of the repo |
| 612 | + // type: string |
| 613 | + // required: true |
| 614 | + // - name: repo |
| 615 | + // in: path |
| 616 | + // description: name of the repo |
| 617 | + // type: string |
| 618 | + // required: true |
| 619 | + // responses: |
| 620 | + // "200": |
| 621 | + // "$ref": "#/responses/ActionWorkflowList" |
| 622 | + // "400": |
| 623 | + // "$ref": "#/responses/error" |
| 624 | + // "403": |
| 625 | + // "$ref": "#/responses/forbidden" |
| 626 | + // "404": |
| 627 | + // "$ref": "#/responses/notFound" |
| 628 | + // "409": |
| 629 | + // "$ref": "#/responses/conflict" |
| 630 | + // "422": |
| 631 | + // "$ref": "#/responses/validationError" |
| 632 | + panic("implement me") |
| 633 | +} |
| 634 | + |
| 635 | +func (a ActionWorkflow) GetWorkflow(ctx *context.APIContext) { |
| 636 | + // swagger:operation GET /repos/{owner}/{repo}/actions/workflows/{workflow_id} repository GetWorkflow |
| 637 | + // --- |
| 638 | + // summary: Get a workflow |
| 639 | + // produces: |
| 640 | + // - application/json |
| 641 | + // parameters: |
| 642 | + // - name: owner |
| 643 | + // in: path |
| 644 | + // description: owner of the repo |
| 645 | + // type: string |
| 646 | + // required: true |
| 647 | + // - name: repo |
| 648 | + // in: path |
| 649 | + // description: name of the repo |
| 650 | + // type: string |
| 651 | + // required: true |
| 652 | + // - name: workflow_id |
| 653 | + // in: path |
| 654 | + // description: id of the workflow |
| 655 | + // type: string |
| 656 | + // required: true |
| 657 | + // responses: |
| 658 | + // "200": |
| 659 | + // "$ref": "#/responses/ActionWorkflow" |
| 660 | + // "400": |
| 661 | + // "$ref": "#/responses/error" |
| 662 | + // "403": |
| 663 | + // "$ref": "#/responses/forbidden" |
| 664 | + // "404": |
| 665 | + // "$ref": "#/responses/notFound" |
| 666 | + // "409": |
| 667 | + // "$ref": "#/responses/conflict" |
| 668 | + // "422": |
| 669 | + // "$ref": "#/responses/validationError" |
| 670 | + panic("implement me") |
| 671 | +} |
| 672 | + |
| 673 | +func (a ActionWorkflow) DispatchWorkflow(ctx *context.APIContext) { |
| 674 | + // swagger:operation POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches repository DispatchWorkflow |
| 675 | + // --- |
| 676 | + // summary: Create a workflow dispatch event |
| 677 | + // produces: |
| 678 | + // - application/json |
| 679 | + // parameters: |
| 680 | + // - name: owner |
| 681 | + // in: path |
| 682 | + // description: owner of the repo |
| 683 | + // type: string |
| 684 | + // required: true |
| 685 | + // - name: repo |
| 686 | + // in: path |
| 687 | + // description: name of the repo |
| 688 | + // type: string |
| 689 | + // required: true |
| 690 | + // - name: workflow_id |
| 691 | + // in: path |
| 692 | + // description: id of the workflow |
| 693 | + // type: string |
| 694 | + // required: true |
| 695 | + // - name: body |
| 696 | + // in: body |
| 697 | + // schema: |
| 698 | + // "$ref": "#/definitions/CreateActionWorkflowDispatch" |
| 699 | + // responses: |
| 700 | + // "204": |
| 701 | + // description: No Content |
| 702 | + // "400": |
| 703 | + // "$ref": "#/responses/error" |
| 704 | + // "403": |
| 705 | + // "$ref": "#/responses/forbidden" |
| 706 | + // "404": |
| 707 | + // "$ref": "#/responses/notFound" |
| 708 | + // "409": |
| 709 | + // "$ref": "#/responses/conflict" |
| 710 | + // "422": |
| 711 | + // "$ref": "#/responses/validationError" |
| 712 | + |
| 713 | + opt := web.GetForm(ctx).(*api.CreateActionWorkflowDispatch) |
| 714 | + |
| 715 | + workflowID := ctx.PathParam("workflow_id") |
| 716 | + if len(workflowID) == 0 { |
| 717 | + ctx.Error(http.StatusUnprocessableEntity, "MissingWorkflowParameter", util.NewInvalidArgumentErrorf("workflow_id is required parameter")) |
| 718 | + return |
| 719 | + } |
| 720 | + |
| 721 | + ref := opt.Ref |
| 722 | + if len(ref) == 0 { |
| 723 | + ctx.Error(http.StatusUnprocessableEntity, "MissingWorkflowParameter", util.NewInvalidArgumentErrorf("req is requried parameter")) |
| 724 | + return |
| 725 | + } |
| 726 | + |
| 727 | + // can not rerun job when workflow is disabled |
| 728 | + cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions) |
| 729 | + cfg := cfgUnit.ActionsConfig() |
| 730 | + if cfg.IsWorkflowDisabled(workflowID) { |
| 731 | + ctx.Error(http.StatusInternalServerError, "WorkflowDisabled", ctx.Tr("actions.workflow.disabled")) |
| 732 | + return |
| 733 | + } |
| 734 | + |
| 735 | + // get target commit of run from specified ref |
| 736 | + refName := git.RefName(ref) |
| 737 | + var runTargetCommit *git.Commit |
| 738 | + var err error |
| 739 | + if refName.IsTag() { |
| 740 | + runTargetCommit, err = ctx.Repo.GitRepo.GetTagCommit(refName.TagName()) |
| 741 | + } else if refName.IsBranch() { |
| 742 | + // [E] PANIC: runtime error: invalid memory address or nil pointer dereference |
| 743 | + runTargetCommit, err = ctx.Repo.GitRepo.GetBranchCommit(refName.BranchName()) |
| 744 | + } else { |
| 745 | + ctx.Error(http.StatusInternalServerError, "WorkflowRefNameError", ctx.Tr("form.git_ref_name_error", ref)) |
| 746 | + return |
| 747 | + } |
| 748 | + if err != nil { |
| 749 | + ctx.Error(http.StatusNotFound, "WorkflowRefNotFound", ctx.Tr("form.target_ref_not_exist", ref)) |
| 750 | + return |
| 751 | + } |
| 752 | + |
| 753 | + // get workflow entry from default branch commit |
| 754 | + defaultBranchCommit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) |
| 755 | + if err != nil { |
| 756 | + ctx.Error(http.StatusInternalServerError, "WorkflowDefaultBranchError", err.Error()) |
| 757 | + return |
| 758 | + } |
| 759 | + entries, err := actions.ListWorkflows(defaultBranchCommit) |
| 760 | + if err != nil { |
| 761 | + ctx.Error(http.StatusInternalServerError, "WorkflowListError", err.Error()) |
| 762 | + } |
| 763 | + |
| 764 | + // find workflow from commit |
| 765 | + var workflows []*jobparser.SingleWorkflow |
| 766 | + for _, entry := range entries { |
| 767 | + if entry.Name() == workflowID { |
| 768 | + content, err := actions.GetContentFromEntry(entry) |
| 769 | + if err != nil { |
| 770 | + ctx.Error(http.StatusInternalServerError, "WorkflowGetContentError", err.Error()) |
| 771 | + return |
| 772 | + } |
| 773 | + workflows, err = jobparser.Parse(content) |
| 774 | + if err != nil { |
| 775 | + ctx.Error(http.StatusInternalServerError, "WorkflowParseError", err.Error()) |
| 776 | + return |
| 777 | + } |
| 778 | + break |
| 779 | + } |
| 780 | + } |
| 781 | + |
| 782 | + if len(workflows) == 0 { |
| 783 | + ctx.Error(http.StatusNotFound, "WorkflowNotFound", ctx.Tr("actions.workflow.not_found", workflowID)) |
| 784 | + return |
| 785 | + } |
| 786 | + |
| 787 | + workflow := &model.Workflow{ |
| 788 | + RawOn: workflows[0].RawOn, |
| 789 | + } |
| 790 | + inputs := make(map[string]any) |
| 791 | + if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil { |
| 792 | + for name, config := range workflowDispatch.Inputs { |
| 793 | + value, exists := opt.Inputs[name] |
| 794 | + if !exists { |
| 795 | + continue |
| 796 | + } |
| 797 | + if config.Type == "boolean" { |
| 798 | + inputs[name] = strconv.FormatBool(value == "on") |
| 799 | + } else if value != "" { |
| 800 | + inputs[name] = value.(string) |
| 801 | + } else { |
| 802 | + inputs[name] = config.Default |
| 803 | + } |
| 804 | + } |
| 805 | + } |
| 806 | + |
| 807 | + workflowDispatchPayload := &api.WorkflowDispatchPayload{ |
| 808 | + Workflow: workflowID, |
| 809 | + Ref: ref, |
| 810 | + Repository: convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeNone}), |
| 811 | + Inputs: inputs, |
| 812 | + Sender: convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone), |
| 813 | + } |
| 814 | + var eventPayload []byte |
| 815 | + if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil { |
| 816 | + ctx.Error(http.StatusInternalServerError, "WorkflowDispatchJSONParseError", err.Error()) |
| 817 | + return |
| 818 | + } |
| 819 | + |
| 820 | + run := &actions_model.ActionRun{ |
| 821 | + Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0], |
| 822 | + RepoID: ctx.Repo.Repository.ID, |
| 823 | + OwnerID: ctx.Repo.Repository.Owner.ID, |
| 824 | + WorkflowID: workflowID, |
| 825 | + TriggerUserID: ctx.Doer.ID, |
| 826 | + Ref: ref, |
| 827 | + CommitSHA: runTargetCommit.ID.String(), |
| 828 | + IsForkPullRequest: false, |
| 829 | + Event: "workflow_dispatch", |
| 830 | + TriggerEvent: "workflow_dispatch", |
| 831 | + EventPayload: string(eventPayload), |
| 832 | + Status: actions_model.StatusWaiting, |
| 833 | + } |
| 834 | + |
| 835 | + // cancel running jobs of the same workflow |
| 836 | + if err := actions_model.CancelPreviousJobs( |
| 837 | + ctx, |
| 838 | + run.RepoID, |
| 839 | + run.Ref, |
| 840 | + run.WorkflowID, |
| 841 | + run.Event, |
| 842 | + ); err != nil { |
| 843 | + ctx.Error(http.StatusInternalServerError, "WorkflowCancelPreviousJobsError", err.Error()) |
| 844 | + return |
| 845 | + } |
| 846 | + |
| 847 | + if err := actions_model.InsertRun(ctx, run, workflows); err != nil { |
| 848 | + ctx.Error(http.StatusInternalServerError, "WorkflowInsertRunError", err.Error()) |
| 849 | + return |
| 850 | + } |
| 851 | + |
| 852 | + alljobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID}) |
| 853 | + if err != nil { |
| 854 | + ctx.Error(http.StatusInternalServerError, "WorkflowFindRunJobError", err.Error()) |
| 855 | + return |
| 856 | + } |
| 857 | + actions_service.CreateCommitStatus(ctx, alljobs...) |
| 858 | + |
| 859 | + ctx.Status(http.StatusNoContent) |
| 860 | +} |
0 commit comments