1381 lines
38 KiB
Go
1381 lines
38 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
"time"
|
|
|
|
"code.gitea.io/sdk/gitea"
|
|
"github.com/spf13/cobra"
|
|
|
|
"codeberg.org/romaintb/fgj/internal/api"
|
|
"codeberg.org/romaintb/fgj/internal/config"
|
|
)
|
|
|
|
// ActionRun represents a workflow run
|
|
type ActionRun struct {
|
|
ID int64 `json:"id"`
|
|
Title string `json:"title"`
|
|
WorkflowID string `json:"workflow_id"`
|
|
IndexInRepo int64 `json:"index_in_repo"`
|
|
Event string `json:"event"`
|
|
Status string `json:"status"`
|
|
CommitSHA string `json:"commit_sha"`
|
|
PrettyRef string `json:"prettyref"`
|
|
Created string `json:"created"`
|
|
Updated string `json:"updated"`
|
|
Started string `json:"started"`
|
|
}
|
|
|
|
// ActionRunList represents a list of workflow runs
|
|
type ActionRunList struct {
|
|
TotalCount int `json:"total_count"`
|
|
WorkflowRuns []ActionRun `json:"workflow_runs"`
|
|
}
|
|
|
|
// ActionTask represents a job/task within a workflow run
|
|
type ActionTask struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
HeadBranch string `json:"head_branch"`
|
|
HeadSHA string `json:"head_sha"`
|
|
RunNumber int64 `json:"run_number"`
|
|
Event string `json:"event"`
|
|
DisplayTitle string `json:"display_title"`
|
|
Status string `json:"status"`
|
|
WorkflowID string `json:"workflow_id"`
|
|
URL string `json:"url"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
RunStartedAt string `json:"run_started_at"`
|
|
}
|
|
|
|
// ActionTaskList represents a list of tasks/jobs
|
|
type ActionTaskList struct {
|
|
WorkflowRuns []ActionTask `json:"workflow_runs"`
|
|
TotalCount int `json:"total_count"`
|
|
}
|
|
|
|
// Workflow represents a workflow definition
|
|
type Workflow struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
State string `json:"state"`
|
|
}
|
|
|
|
// WorkflowList represents a list of workflows
|
|
type WorkflowList struct {
|
|
Workflows []Workflow `json:"workflows"`
|
|
TotalCount int `json:"total_count"`
|
|
}
|
|
|
|
// ContentsResponse represents a file/directory in the repository
|
|
type ContentsResponse struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
Type string `json:"type"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
var actionsCmd = &cobra.Command{
|
|
Use: "actions",
|
|
Aliases: []string{"action"},
|
|
Short: "Manage Forgejo Actions",
|
|
Long: "View and manage workflows, runs, secrets, and variables for Forgejo Actions in your repositories.",
|
|
}
|
|
|
|
// Run commands (compatible with gh run)
|
|
var runCmd = &cobra.Command{
|
|
Use: "run",
|
|
Short: "View and manage workflow runs",
|
|
Long: "List, view, and manage workflow runs.",
|
|
}
|
|
|
|
var runListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List recent workflow runs",
|
|
Long: "List recent workflow runs for a repository.",
|
|
RunE: runRunList,
|
|
}
|
|
|
|
var runViewCmd = &cobra.Command{
|
|
Use: "view <run-id>",
|
|
Short: "View a workflow run",
|
|
Long: "View details about a specific workflow run.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runRunView,
|
|
}
|
|
|
|
var runWatchCmd = &cobra.Command{
|
|
Use: "watch <run-id>",
|
|
Short: "Watch a workflow run",
|
|
Long: "Poll a workflow run until it completes.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runRunWatch,
|
|
}
|
|
|
|
var runRerunCmd = &cobra.Command{
|
|
Use: "rerun <run-id>",
|
|
Short: "Rerun a workflow run",
|
|
Long: "Trigger a rerun for a specific workflow run.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runRunRerun,
|
|
}
|
|
|
|
var runCancelCmd = &cobra.Command{
|
|
Use: "cancel <run-id>",
|
|
Short: "Cancel a workflow run",
|
|
Long: "Cancel a running workflow run.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runRunCancel,
|
|
}
|
|
|
|
// Workflow commands
|
|
var workflowCmd = &cobra.Command{
|
|
Use: "workflow",
|
|
Short: "Manage workflows",
|
|
Long: "List, view, and run workflows.",
|
|
}
|
|
|
|
var workflowListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List workflows",
|
|
Long: "List all workflows in a repository.",
|
|
RunE: runWorkflowList,
|
|
}
|
|
|
|
var workflowViewCmd = &cobra.Command{
|
|
Use: "view <workflow>",
|
|
Short: "View a workflow",
|
|
Long: "View details about a specific workflow. You can specify the workflow by name or filename.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runWorkflowView,
|
|
}
|
|
|
|
var workflowRunCmd = &cobra.Command{
|
|
Use: "run <workflow>",
|
|
Short: "Run a workflow",
|
|
Long: "Trigger a workflow_dispatch event for a workflow. The workflow must support the workflow_dispatch trigger.",
|
|
Args: cobra.ExactArgs(1),
|
|
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",
|
|
Short: "Manage repository secrets",
|
|
Long: "List, create, and delete secrets for Forgejo Actions.",
|
|
}
|
|
|
|
var actionsSecretListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List repository secrets",
|
|
Long: "List all secrets for a repository.",
|
|
RunE: runActionsSecretList,
|
|
}
|
|
|
|
var actionsSecretCreateCmd = &cobra.Command{
|
|
Use: "create <name>",
|
|
Short: "Create or update a repository secret",
|
|
Long: "Create or update a secret for Forgejo Actions. The secret value will be read from stdin.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runActionsSecretCreate,
|
|
}
|
|
|
|
var actionsSecretDeleteCmd = &cobra.Command{
|
|
Use: "delete <name>",
|
|
Short: "Delete a repository secret",
|
|
Long: "Delete a secret from Forgejo Actions.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runActionsSecretDelete,
|
|
}
|
|
|
|
// Variable commands
|
|
var actionsVariableCmd = &cobra.Command{
|
|
Use: "variable",
|
|
Short: "Manage repository variables",
|
|
Long: "List, get, create, update, and delete variables for Forgejo Actions.",
|
|
}
|
|
|
|
var actionsVariableListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List repository variables",
|
|
Long: "List all variables for a repository.",
|
|
RunE: runActionsVariableList,
|
|
}
|
|
|
|
var actionsVariableGetCmd = &cobra.Command{
|
|
Use: "get <name>",
|
|
Short: "Get a repository variable",
|
|
Long: "Get the value of a specific repository variable.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runActionsVariableGet,
|
|
}
|
|
|
|
var actionsVariableCreateCmd = &cobra.Command{
|
|
Use: "create <name> <value>",
|
|
Short: "Create a repository variable",
|
|
Long: "Create a new variable for Forgejo Actions.",
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: runActionsVariableCreate,
|
|
}
|
|
|
|
var actionsVariableUpdateCmd = &cobra.Command{
|
|
Use: "update <name> <value>",
|
|
Short: "Update a repository variable",
|
|
Long: "Update an existing variable for Forgejo Actions.",
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: runActionsVariableUpdate,
|
|
}
|
|
|
|
var actionsVariableDeleteCmd = &cobra.Command{
|
|
Use: "delete <name>",
|
|
Short: "Delete a repository variable",
|
|
Long: "Delete a variable from Forgejo Actions.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runActionsVariableDelete,
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(actionsCmd)
|
|
|
|
// Add run commands (gh run compatible)
|
|
actionsCmd.AddCommand(runCmd)
|
|
runCmd.AddCommand(runListCmd)
|
|
runCmd.AddCommand(runViewCmd)
|
|
runCmd.AddCommand(runWatchCmd)
|
|
runCmd.AddCommand(runRerunCmd)
|
|
runCmd.AddCommand(runCancelCmd)
|
|
|
|
// Add workflow commands (gh workflow compatible)
|
|
actionsCmd.AddCommand(workflowCmd)
|
|
workflowCmd.AddCommand(workflowListCmd)
|
|
workflowCmd.AddCommand(workflowViewCmd)
|
|
workflowCmd.AddCommand(workflowRunCmd)
|
|
workflowCmd.AddCommand(workflowEnableCmd)
|
|
workflowCmd.AddCommand(workflowDisableCmd)
|
|
|
|
// Add secret commands
|
|
actionsCmd.AddCommand(actionsSecretCmd)
|
|
actionsSecretCmd.AddCommand(actionsSecretListCmd)
|
|
actionsSecretCmd.AddCommand(actionsSecretCreateCmd)
|
|
actionsSecretCmd.AddCommand(actionsSecretDeleteCmd)
|
|
|
|
// Add variable commands
|
|
actionsCmd.AddCommand(actionsVariableCmd)
|
|
actionsVariableCmd.AddCommand(actionsVariableListCmd)
|
|
actionsVariableCmd.AddCommand(actionsVariableGetCmd)
|
|
actionsVariableCmd.AddCommand(actionsVariableCreateCmd)
|
|
actionsVariableCmd.AddCommand(actionsVariableUpdateCmd)
|
|
actionsVariableCmd.AddCommand(actionsVariableDeleteCmd)
|
|
|
|
// Add flags for run commands
|
|
addRepoFlags(runListCmd)
|
|
runListCmd.Flags().IntP("limit", "L", 20, "Maximum number of runs to list")
|
|
runListCmd.Flags().Bool("json", false, "Output workflow runs as JSON")
|
|
addRepoFlags(runViewCmd)
|
|
runViewCmd.Flags().BoolP("verbose", "v", false, "Show job steps")
|
|
runViewCmd.Flags().BoolP("log", "", false, "View full log for either a run or specific job")
|
|
runViewCmd.Flags().StringP("job", "j", "", "View a specific job ID from a run")
|
|
runViewCmd.Flags().BoolP("log-failed", "", false, "View the log for any failed steps in a run or specific job")
|
|
runViewCmd.Flags().Bool("json", false, "Output workflow run as JSON")
|
|
addRepoFlags(runWatchCmd)
|
|
runWatchCmd.Flags().DurationP("interval", "i", 5*time.Second, "Polling interval")
|
|
addRepoFlags(runRerunCmd)
|
|
addRepoFlags(runCancelCmd)
|
|
|
|
// Add flags for workflow commands
|
|
addRepoFlags(workflowListCmd)
|
|
workflowListCmd.Flags().IntP("limit", "L", 20, "Maximum number of workflows to list")
|
|
workflowListCmd.Flags().Bool("json", false, "Output workflows as JSON")
|
|
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)")
|
|
|
|
// Add flags for secret commands
|
|
addRepoFlags(actionsSecretListCmd)
|
|
addRepoFlags(actionsSecretCreateCmd)
|
|
addRepoFlags(actionsSecretDeleteCmd)
|
|
|
|
// Add flags for variable commands
|
|
addRepoFlags(actionsVariableListCmd)
|
|
addRepoFlags(actionsVariableGetCmd)
|
|
addRepoFlags(actionsVariableCreateCmd)
|
|
addRepoFlags(actionsVariableUpdateCmd)
|
|
addRepoFlags(actionsVariableDeleteCmd)
|
|
}
|
|
|
|
func addRepoFlags(cmd *cobra.Command) {
|
|
cmd.Flags().StringP("repo", "R", "", "Repository in owner/name format (auto-detected from git if not specified)")
|
|
}
|
|
|
|
// Run command implementations
|
|
|
|
func runRunList(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
|
|
}
|
|
|
|
limit, _ := cmd.Flags().GetInt("limit")
|
|
|
|
// Call the API endpoint directly since SDK doesn't have it yet
|
|
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs?limit=%d", owner, name, limit)
|
|
|
|
var runList ActionRunList
|
|
if err := client.GetJSON(endpoint, &runList); err != nil {
|
|
return fmt.Errorf("failed to list runs: %w", err)
|
|
}
|
|
|
|
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
|
return writeJSON(runList.WorkflowRuns)
|
|
}
|
|
|
|
if len(runList.WorkflowRuns) == 0 {
|
|
fmt.Println("No workflow runs found")
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
if _, err := fmt.Fprintln(w, "STATUS\tTITLE\tWORKFLOW\tEVENT\tID\tCREATED"); err != nil {
|
|
return fmt.Errorf("failed to write header: %w", err)
|
|
}
|
|
|
|
for _, run := range runList.WorkflowRuns {
|
|
createdTime, err := time.Parse(time.RFC3339, run.Created)
|
|
if err != nil {
|
|
createdTime = time.Now()
|
|
}
|
|
|
|
timeStr := formatTimeSince(createdTime)
|
|
|
|
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%s\n",
|
|
formatStatus(run.Status), run.Title, run.WorkflowID, run.Event, run.ID, timeStr); err != nil {
|
|
return fmt.Errorf("failed to write run: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := w.Flush(); err != nil {
|
|
return fmt.Errorf("failed to flush output: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runRunView(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
|
|
}
|
|
|
|
runID, err := strconv.ParseInt(args[0], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid run ID: %w", err)
|
|
}
|
|
|
|
verbose, _ := cmd.Flags().GetBool("verbose")
|
|
showLog, _ := cmd.Flags().GetBool("log")
|
|
jobIDStr, _ := cmd.Flags().GetString("job")
|
|
showLogFailed, _ := cmd.Flags().GetBool("log-failed")
|
|
jsonOutput, _ := cmd.Flags().GetBool("json")
|
|
|
|
var jobID int64
|
|
if jobIDStr != "" {
|
|
var err error
|
|
jobID, err = strconv.ParseInt(jobIDStr, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid job ID: %w", err)
|
|
}
|
|
}
|
|
|
|
if jsonOutput && (showLog || showLogFailed) {
|
|
return fmt.Errorf("--json cannot be used with --log or --log-failed")
|
|
}
|
|
|
|
// Call the API endpoint directly since SDK doesn't have it yet
|
|
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d", owner, name, runID)
|
|
|
|
var run ActionRun
|
|
if err := client.GetJSON(endpoint, &run); err != nil {
|
|
return fmt.Errorf("failed to get run: %w", err)
|
|
}
|
|
|
|
needsJobs := verbose || showLog || showLogFailed || jobID > 0
|
|
|
|
if jsonOutput {
|
|
var runTasks []ActionTask
|
|
if needsJobs {
|
|
tasksEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/tasks", owner, name)
|
|
var taskList ActionTaskList
|
|
if err := client.GetJSON(tasksEndpoint, &taskList); err != nil {
|
|
return fmt.Errorf("failed to get tasks: %w", err)
|
|
}
|
|
|
|
for _, task := range taskList.WorkflowRuns {
|
|
if task.RunNumber == run.IndexInRepo {
|
|
runTasks = append(runTasks, task)
|
|
}
|
|
}
|
|
|
|
if jobID > 0 {
|
|
var filtered []ActionTask
|
|
for _, task := range runTasks {
|
|
if task.ID == jobID {
|
|
filtered = append(filtered, task)
|
|
break
|
|
}
|
|
}
|
|
if len(filtered) == 0 {
|
|
return fmt.Errorf("job %d not found in this run", jobID)
|
|
}
|
|
runTasks = filtered
|
|
}
|
|
}
|
|
|
|
payload := struct {
|
|
Run ActionRun `json:"run"`
|
|
Tasks []ActionTask `json:"tasks,omitempty"`
|
|
}{
|
|
Run: run,
|
|
Tasks: runTasks,
|
|
}
|
|
return writeJSON(payload)
|
|
}
|
|
|
|
// Display run information
|
|
fmt.Printf("Title: %s\n", run.Title)
|
|
fmt.Printf("Workflow: %s\n", run.WorkflowID)
|
|
fmt.Printf("Run: #%d\n", run.IndexInRepo)
|
|
fmt.Printf("Status: %s\n", formatStatus(run.Status))
|
|
fmt.Printf("Event: %s\n", run.Event)
|
|
fmt.Printf("Ref: %s\n", run.PrettyRef)
|
|
|
|
commit := run.CommitSHA
|
|
if len(commit) > 8 {
|
|
commit = commit[:8]
|
|
}
|
|
fmt.Printf("Commit: %s\n", commit)
|
|
|
|
if createdTime, err := time.Parse(time.RFC3339, run.Created); err == nil {
|
|
fmt.Printf("Created: %s\n", createdTime.Format("2006-01-02 15:04:05"))
|
|
}
|
|
if run.Started != "" {
|
|
if startedTime, err := time.Parse(time.RFC3339, run.Started); err == nil {
|
|
fmt.Printf("Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
|
|
}
|
|
}
|
|
if updatedTime, err := time.Parse(time.RFC3339, run.Updated); err == nil {
|
|
fmt.Printf("Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
|
|
}
|
|
|
|
// Fetch jobs if needed for verbose, log, or job-specific views
|
|
if !needsJobs {
|
|
return nil
|
|
}
|
|
|
|
tasksEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/tasks", owner, name)
|
|
var taskList ActionTaskList
|
|
if err := client.GetJSON(tasksEndpoint, &taskList); err != nil {
|
|
return fmt.Errorf("failed to get tasks: %w", err)
|
|
}
|
|
|
|
// Filter tasks for this run number
|
|
var runTasks []ActionTask
|
|
for _, task := range taskList.WorkflowRuns {
|
|
if task.RunNumber == run.IndexInRepo {
|
|
runTasks = append(runTasks, task)
|
|
}
|
|
}
|
|
|
|
if len(runTasks) == 0 {
|
|
fmt.Println("\nNo jobs found for this run")
|
|
return nil
|
|
}
|
|
|
|
// If --job is specified, filter to that job
|
|
if jobID > 0 {
|
|
var found bool
|
|
for _, task := range runTasks {
|
|
if task.ID == jobID {
|
|
runTasks = []ActionTask{task}
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("job %d not found in this run", jobID)
|
|
}
|
|
}
|
|
|
|
// Case 1: --verbose (show job steps/details without logs)
|
|
if verbose && !showLog && !showLogFailed {
|
|
fmt.Println("\nJobs:")
|
|
for _, task := range runTasks {
|
|
fmt.Printf("\n %s - %s (ID: %d)\n", formatStatus(task.Status), task.Name, task.ID)
|
|
if startedTime, err := time.Parse(time.RFC3339, task.RunStartedAt); err == nil {
|
|
fmt.Printf(" Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
|
|
}
|
|
if updatedTime, err := time.Parse(time.RFC3339, task.UpdatedAt); err == nil {
|
|
fmt.Printf(" Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Case 2: --log or --log-failed (show logs)
|
|
if showLog || showLogFailed {
|
|
for _, task := range runTasks {
|
|
if err := showJobLog(client, owner, name, run.IndexInRepo, task, showLogFailed); err != nil {
|
|
fmt.Printf("\nError fetching log for job %s: %v\n", task.Name, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Case 3: --job without --log or --verbose (show job details only)
|
|
if jobID > 0 {
|
|
task := runTasks[0]
|
|
fmt.Println("\nJob Details:")
|
|
fmt.Printf(" Name: %s\n", task.Name)
|
|
fmt.Printf(" ID: %d\n", task.ID)
|
|
fmt.Printf(" Status: %s\n", formatStatus(task.Status))
|
|
if startedTime, err := time.Parse(time.RFC3339, task.RunStartedAt); err == nil {
|
|
fmt.Printf(" Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
|
|
}
|
|
if updatedTime, err := time.Parse(time.RFC3339, task.UpdatedAt); err == nil {
|
|
fmt.Printf(" Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runRunWatch(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
|
|
}
|
|
|
|
runID, err := strconv.ParseInt(args[0], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid run ID: %w", err)
|
|
}
|
|
|
|
interval, _ := cmd.Flags().GetDuration("interval")
|
|
if interval <= 0 {
|
|
return fmt.Errorf("interval must be greater than 0")
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d", owner, name, runID)
|
|
|
|
var lastStatus string
|
|
for {
|
|
var run ActionRun
|
|
if err := client.GetJSON(endpoint, &run); err != nil {
|
|
return fmt.Errorf("failed to get run: %w", err)
|
|
}
|
|
|
|
if run.Status != lastStatus {
|
|
fmt.Printf("Status: %s\n", formatStatus(run.Status))
|
|
lastStatus = run.Status
|
|
}
|
|
|
|
if isRunComplete(run.Status) {
|
|
fmt.Printf("Run #%d completed with status %s\n", run.IndexInRepo, formatStatus(run.Status))
|
|
return nil
|
|
}
|
|
|
|
time.Sleep(interval)
|
|
}
|
|
}
|
|
|
|
func runRunRerun(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
|
|
}
|
|
|
|
runID, err := strconv.ParseInt(args[0], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid run ID: %w", err)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d/rerun", owner, name, runID)
|
|
if err := client.PostJSON(endpoint, nil, nil); err != nil {
|
|
return fmt.Errorf("failed to rerun workflow: %w", err)
|
|
}
|
|
|
|
fmt.Printf("✓ Rerun requested for run %d\n", runID)
|
|
return nil
|
|
}
|
|
|
|
func runRunCancel(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
|
|
}
|
|
|
|
runID, err := strconv.ParseInt(args[0], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid run ID: %w", err)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d/cancel", owner, name, runID)
|
|
if err := client.PostJSON(endpoint, nil, nil); err != nil {
|
|
return fmt.Errorf("failed to cancel workflow run: %w", err)
|
|
}
|
|
|
|
fmt.Printf("✓ Cancel requested for run %d\n", runID)
|
|
return nil
|
|
}
|
|
|
|
func showJobLog(client *api.Client, owner, name string, runNumber int64, task ActionTask, logFailed bool) error {
|
|
// Fetch log from /repos/{owner}/{repo}/actions/runs/{run_number}/jobs/{job_id}/logs
|
|
logURL := fmt.Sprintf("https://%s/%s/%s/actions/runs/%d/jobs/%d/logs",
|
|
client.Hostname(), owner, name, runNumber, task.ID)
|
|
|
|
fmt.Printf("\n========================================\n")
|
|
fmt.Printf("Job: %s (ID: %d)\n", task.Name, task.ID)
|
|
fmt.Printf("Status: %s\n", formatStatus(task.Status))
|
|
fmt.Printf("========================================\n\n")
|
|
|
|
// Use GetRawLog helper
|
|
logContent, err := client.GetRawLog(logURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If --log-failed, filter to only show failed steps
|
|
// For now, just show all logs (filtering failed steps would require parsing the log format)
|
|
if logFailed {
|
|
// TODO: Implement filtering for failed steps only
|
|
// This would require parsing the log format and identifying failed step markers
|
|
fmt.Println("Note: --log-failed filtering not yet implemented, showing all logs")
|
|
}
|
|
|
|
fmt.Print(logContent)
|
|
fmt.Println()
|
|
|
|
return nil
|
|
}
|
|
|
|
func formatStatus(status string) string {
|
|
switch status {
|
|
case "success":
|
|
return "✓ success"
|
|
case "failure":
|
|
return "✗ failure"
|
|
case "cancelled":
|
|
return "- cancelled"
|
|
case "skipped":
|
|
return "○ skipped"
|
|
case "in_progress", "running":
|
|
return "● in progress"
|
|
case "queued", "waiting":
|
|
return "○ queued"
|
|
default:
|
|
return status
|
|
}
|
|
}
|
|
|
|
func isRunComplete(status string) bool {
|
|
switch status {
|
|
case "success", "failure", "cancelled", "skipped":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func formatTimeSince(t time.Time) string {
|
|
duration := time.Since(t)
|
|
|
|
if duration < time.Minute {
|
|
return "just now"
|
|
} else if duration < time.Hour {
|
|
mins := int(duration.Minutes())
|
|
if mins == 1 {
|
|
return "1 minute ago"
|
|
}
|
|
return fmt.Sprintf("%d minutes ago", mins)
|
|
} else if duration < 24*time.Hour {
|
|
hours := int(duration.Hours())
|
|
if hours == 1 {
|
|
return "1 hour ago"
|
|
}
|
|
return fmt.Sprintf("%d hours ago", hours)
|
|
}
|
|
|
|
days := int(duration.Hours() / 24)
|
|
if days == 1 {
|
|
return "1 day ago"
|
|
}
|
|
return fmt.Sprintf("%d days ago", days)
|
|
}
|
|
|
|
// Workflow command implementations
|
|
|
|
func runWorkflowList(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
|
|
}
|
|
|
|
limit, _ := cmd.Flags().GetInt("limit")
|
|
|
|
// List workflows from both .gitea/workflows and .forgejo/workflows
|
|
var allWorkflows []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" && (len(content.Name) > 4 && (content.Name[len(content.Name)-4:] == ".yml" || content.Name[len(content.Name)-5:] == ".yaml")) {
|
|
workflow := Workflow{
|
|
Name: content.Name,
|
|
Path: content.Path,
|
|
State: "active",
|
|
}
|
|
allWorkflows = append(allWorkflows, workflow)
|
|
|
|
if len(allWorkflows) >= limit {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(allWorkflows) >= limit {
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(allWorkflows) == 0 {
|
|
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
|
return writeJSON(allWorkflows)
|
|
}
|
|
fmt.Println("No workflows found")
|
|
return nil
|
|
}
|
|
|
|
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
|
return writeJSON(allWorkflows)
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
if _, err := fmt.Fprintln(w, "NAME\tSTATE\tPATH"); err != nil {
|
|
return fmt.Errorf("failed to write header: %w", err)
|
|
}
|
|
|
|
for _, workflow := range allWorkflows {
|
|
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n",
|
|
workflow.Name, workflow.State, workflow.Path); err != nil {
|
|
return fmt.Errorf("failed to write workflow: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := w.Flush(); err != nil {
|
|
return fmt.Errorf("failed to flush output: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runWorkflowView(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
|
|
}
|
|
|
|
jsonOutput, _ := cmd.Flags().GetBool("json")
|
|
|
|
var latestRun *ActionRun
|
|
|
|
// Get the latest run for this workflow
|
|
runsEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs?workflow_id=%s&limit=1", owner, name, workflow.Path)
|
|
var runList ActionRunList
|
|
if err := client.GetJSON(runsEndpoint, &runList); err == nil && len(runList.WorkflowRuns) > 0 {
|
|
latestRun = &runList.WorkflowRuns[0]
|
|
}
|
|
|
|
if jsonOutput {
|
|
payload := struct {
|
|
Workflow *Workflow `json:"workflow"`
|
|
LatestRun *ActionRun `json:"latest_run,omitempty"`
|
|
}{
|
|
Workflow: workflow,
|
|
LatestRun: latestRun,
|
|
}
|
|
return writeJSON(payload)
|
|
}
|
|
|
|
// Display workflow information
|
|
fmt.Printf("Name: %s\n", workflow.Name)
|
|
fmt.Printf("Path: %s\n", workflow.Path)
|
|
fmt.Printf("State: %s\n", workflow.State)
|
|
|
|
if latestRun != nil {
|
|
fmt.Printf("\nLatest run:\n")
|
|
fmt.Printf(" Status: %s\n", formatStatus(latestRun.Status))
|
|
fmt.Printf(" Event: %s\n", latestRun.Event)
|
|
fmt.Printf(" Ref: %s\n", latestRun.PrettyRef)
|
|
if createdTime, err := time.Parse(time.RFC3339, latestRun.Created); err == nil {
|
|
fmt.Printf(" Created: %s\n", formatTimeSince(createdTime))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runWorkflowRun(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]
|
|
ref, _ := cmd.Flags().GetString("ref")
|
|
fields, _ := cmd.Flags().GetStringSlice("field")
|
|
rawFields, _ := cmd.Flags().GetStringSlice("raw-field")
|
|
|
|
// If no ref is specified, get the repository's default branch
|
|
if ref == "" {
|
|
repoInfo, _, err := client.GetRepo(owner, name)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get repository info: %w", err)
|
|
}
|
|
ref = repoInfo.DefaultBranch
|
|
}
|
|
|
|
// Build the inputs map
|
|
inputs := make(map[string]string)
|
|
|
|
// Process -f/--field flags
|
|
for _, field := range fields {
|
|
parts := splitKeyValue(field)
|
|
if len(parts) == 2 {
|
|
inputs[parts[0]] = parts[1]
|
|
}
|
|
}
|
|
|
|
// Process -F/--raw-field flags (same as field for now, file reading can be added later)
|
|
for _, field := range rawFields {
|
|
parts := splitKeyValue(field)
|
|
if len(parts) == 2 {
|
|
inputs[parts[0]] = parts[1]
|
|
}
|
|
}
|
|
|
|
// Prepare the dispatch request
|
|
dispatchReq := map[string]any{
|
|
"ref": ref,
|
|
}
|
|
if len(inputs) > 0 {
|
|
dispatchReq["inputs"] = inputs
|
|
}
|
|
|
|
// Trigger the workflow
|
|
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/dispatches", owner, name, workflowIdentifier)
|
|
|
|
if err := client.PostJSON(endpoint, dispatchReq, nil); err != nil {
|
|
return fmt.Errorf("failed to trigger workflow: %w", err)
|
|
}
|
|
|
|
fmt.Printf("✓ Workflow '%s' triggered successfully\n", workflowIdentifier)
|
|
fmt.Printf(" Branch/Tag: %s\n", ref)
|
|
if len(inputs) > 0 {
|
|
fmt.Println(" Inputs:")
|
|
for key, value := range inputs {
|
|
fmt.Printf(" %s: %s\n", key, value)
|
|
}
|
|
}
|
|
|
|
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+. " +
|
|
"Your instance does not support the workflow enable/disable API endpoints yet. " +
|
|
"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+. " +
|
|
"Your instance does not support the workflow enable/disable API endpoints yet. " +
|
|
"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 {
|
|
if c == '=' {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx == -1 {
|
|
return []string{s}
|
|
}
|
|
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 {
|
|
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
|
|
}
|
|
|
|
secrets, _, err := client.ListRepoActionSecret(owner, name, gitea.ListRepoActionSecretOption{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list secrets: %w", err)
|
|
}
|
|
|
|
if len(secrets) == 0 {
|
|
fmt.Println("No secrets found")
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
if _, err := fmt.Fprintln(w, "NAME\tCREATED"); err != nil {
|
|
return fmt.Errorf("failed to write header: %w", err)
|
|
}
|
|
for _, secret := range secrets {
|
|
if _, err := fmt.Fprintf(w, "%s\t%s\n", secret.Name, secret.Created.Format("2006-01-02 15:04:05")); err != nil {
|
|
return fmt.Errorf("failed to write secret: %w", err)
|
|
}
|
|
}
|
|
if err := w.Flush(); err != nil {
|
|
return fmt.Errorf("failed to flush output: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runActionsSecretCreate(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
|
|
}
|
|
|
|
secretName := args[0]
|
|
|
|
// Read secret value from stdin
|
|
fmt.Print("Enter secret value: ")
|
|
var secretValue string
|
|
_, err = fmt.Scanln(&secretValue)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read secret value: %w", err)
|
|
}
|
|
|
|
opt := gitea.CreateSecretOption{
|
|
Name: secretName,
|
|
Data: secretValue,
|
|
}
|
|
|
|
_, err = client.CreateRepoActionSecret(owner, name, opt)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create secret: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Secret '%s' created successfully\n", secretName)
|
|
return nil
|
|
}
|
|
|
|
func runActionsSecretDelete(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
|
|
}
|
|
|
|
secretName := args[0]
|
|
|
|
_, err = client.DeleteRepoActionSecret(owner, name, secretName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete secret: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Secret '%s' deleted successfully\n", secretName)
|
|
return nil
|
|
}
|
|
|
|
// Variable command implementations
|
|
|
|
func runActionsVariableList(cmd *cobra.Command, args []string) error {
|
|
// Note: The SDK doesn't have a ListRepoActionVariable method yet
|
|
return fmt.Errorf("listing variables is not yet supported in the SDK")
|
|
}
|
|
|
|
func runActionsVariableGet(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
|
|
}
|
|
|
|
variableName := args[0]
|
|
|
|
variable, _, err := client.GetRepoActionVariable(owner, name, variableName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get variable: %w", err)
|
|
}
|
|
|
|
fmt.Printf("%s=%s\n", variable.Name, variable.Value)
|
|
return nil
|
|
}
|
|
|
|
func runActionsVariableCreate(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
|
|
}
|
|
|
|
variableName := args[0]
|
|
variableValue := args[1]
|
|
|
|
_, err = client.CreateRepoActionVariable(owner, name, variableName, variableValue)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create variable: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Variable '%s' created successfully\n", variableName)
|
|
return nil
|
|
}
|
|
|
|
func runActionsVariableUpdate(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
|
|
}
|
|
|
|
variableName := args[0]
|
|
variableValue := args[1]
|
|
|
|
_, err = client.UpdateRepoActionVariable(owner, name, variableName, variableValue)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update variable: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Variable '%s' updated successfully\n", variableName)
|
|
return nil
|
|
}
|
|
|
|
func runActionsVariableDelete(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
|
|
}
|
|
|
|
variableName := args[0]
|
|
|
|
_, err = client.DeleteRepoActionVariable(owner, name, variableName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete variable: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Variable '%s' deleted successfully\n", variableName)
|
|
return nil
|
|
}
|