feat: add actions workflow enable and disable

This commit is contained in:
Romain Bertrand 2026-01-18 11:50:02 +01:00
parent c2ee338f1c
commit 4c6de3ad2e
4 changed files with 159 additions and 39 deletions

View file

@ -2,8 +2,10 @@ package cmd
import (
"fmt"
"net/http"
"os"
"strconv"
"strings"
"text/tabwriter"
"time"
@ -163,6 +165,22 @@ var workflowRunCmd = &cobra.Command{
RunE: runWorkflowRun,
}
var workflowEnableCmd = &cobra.Command{
Use: "enable <workflow>",
Short: "Enable a workflow",
Long: "Enable a workflow so it can be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to enable workflows.",
Args: cobra.ExactArgs(1),
RunE: runWorkflowEnable,
}
var workflowDisableCmd = &cobra.Command{
Use: "disable <workflow>",
Short: "Disable a workflow",
Long: "Disable a workflow so it cannot be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to disable workflows.",
Args: cobra.ExactArgs(1),
RunE: runWorkflowDisable,
}
// Secret commands
var actionsSecretCmd = &cobra.Command{
Use: "secret",
@ -255,6 +273,8 @@ func init() {
workflowCmd.AddCommand(workflowListCmd)
workflowCmd.AddCommand(workflowViewCmd)
workflowCmd.AddCommand(workflowRunCmd)
workflowCmd.AddCommand(workflowEnableCmd)
workflowCmd.AddCommand(workflowDisableCmd)
// Add secret commands
actionsCmd.AddCommand(actionsSecretCmd)
@ -292,6 +312,8 @@ func init() {
addRepoFlags(workflowViewCmd)
workflowViewCmd.Flags().Bool("json", false, "Output workflow as JSON")
addRepoFlags(workflowRunCmd)
addRepoFlags(workflowEnableCmd)
addRepoFlags(workflowDisableCmd)
workflowRunCmd.Flags().StringP("ref", "r", "", "Branch or tag name to run the workflow on (defaults to repository's default branch)")
workflowRunCmd.Flags().StringSliceP("field", "f", nil, "Add a string parameter in key=value format (can be used multiple times)")
workflowRunCmd.Flags().StringSliceP("raw-field", "F", nil, "Add a string parameter in key=value format, reading from file if value starts with @ (can be used multiple times)")
@ -874,37 +896,9 @@ func runWorkflowView(cmd *cobra.Command, args []string) error {
}
workflowIdentifier := args[0]
// Find the workflow by listing from both .gitea/workflows and .forgejo/workflows
var workflow *Workflow
for _, dir := range []string{".gitea/workflows", ".forgejo/workflows"} {
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, name, dir)
var contents []ContentsResponse
if err := client.GetJSON(endpoint, &contents); err != nil {
// Directory might not exist, continue
continue
}
for _, content := range contents {
if content.Type == "file" && (content.Name == workflowIdentifier || content.Path == workflowIdentifier) {
workflow = &Workflow{
Name: content.Name,
Path: content.Path,
State: "active",
}
break
}
}
if workflow != nil {
break
}
}
if workflow == nil {
return fmt.Errorf("workflow '%s' not found", workflowIdentifier)
workflow, err := findWorkflow(client, owner, name, workflowIdentifier)
if err != nil {
return err
}
jsonOutput, _ := cmd.Flags().GetBool("json")
@ -1024,6 +1018,96 @@ func runWorkflowRun(cmd *cobra.Command, args []string) error {
return nil
}
func runWorkflowEnable(cmd *cobra.Command, args []string) error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost())
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
repo, _ := cmd.Flags().GetString("repo")
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
workflowIdentifier := args[0]
workflow, err := findWorkflow(client, owner, name, workflowIdentifier)
if err != nil {
return err
}
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/enable", owner, name, workflow.Name)
// Try PUT first (correct method per GitHub/Gitea API spec)
status, err := client.DoJSON(http.MethodPut, endpoint, nil, nil)
if err != nil && (status == http.StatusNotFound || status == http.StatusMethodNotAllowed) {
// Fall back to POST for older versions
status, err = client.DoJSON(http.MethodPost, endpoint, nil, nil)
}
if err != nil {
if status == http.StatusNotFound && strings.Contains(err.Error(), "404") {
return fmt.Errorf("failed to enable workflow: this feature requires Forgejo 15.0+ or Gitea 1.24+\n" +
"Your instance does not support the workflow enable/disable API endpoints yet.\n" +
"You can enable workflows via the web UI instead.")
}
return fmt.Errorf("failed to enable workflow: %w", err)
}
fmt.Printf("✓ Workflow '%s' enabled\n", workflow.Name)
return nil
}
func runWorkflowDisable(cmd *cobra.Command, args []string) error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost())
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
repo, _ := cmd.Flags().GetString("repo")
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
workflowIdentifier := args[0]
workflow, err := findWorkflow(client, owner, name, workflowIdentifier)
if err != nil {
return err
}
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/disable", owner, name, workflow.Name)
// Try PUT first (correct method per GitHub/Gitea API spec)
status, err := client.DoJSON(http.MethodPut, endpoint, nil, nil)
if err != nil && (status == http.StatusNotFound || status == http.StatusMethodNotAllowed) {
// Fall back to POST for older versions
status, err = client.DoJSON(http.MethodPost, endpoint, nil, nil)
}
if err != nil {
if status == http.StatusNotFound && strings.Contains(err.Error(), "404") {
return fmt.Errorf("failed to disable workflow: this feature requires Forgejo 15.0+ or Gitea 1.24+\n" +
"Your instance does not support the workflow enable/disable API endpoints yet.\n" +
"You can disable workflows via the web UI instead.")
}
return fmt.Errorf("failed to disable workflow: %w", err)
}
fmt.Printf("✓ Workflow '%s' disabled\n", workflow.Name)
return nil
}
func splitKeyValue(s string) []string {
idx := -1
for i, c := range s {
@ -1038,6 +1122,29 @@ func splitKeyValue(s string) []string {
return []string{s[:idx], s[idx+1:]}
}
func findWorkflow(client *api.Client, owner, name, workflowIdentifier string) (*Workflow, error) {
for _, dir := range []string{".gitea/workflows", ".forgejo/workflows"} {
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, name, dir)
var contents []ContentsResponse
if err := client.GetJSON(endpoint, &contents); err != nil {
continue
}
for _, content := range contents {
if content.Type == "file" && (content.Name == workflowIdentifier || content.Path == workflowIdentifier) {
return &Workflow{
Name: content.Name,
Path: content.Path,
State: "active",
}, nil
}
}
}
return nil, fmt.Errorf("workflow '%s' not found", workflowIdentifier)
}
// Secret command implementations
func runActionsSecretList(cmd *cobra.Command, args []string) error {