workflow.go 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. package cmd
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "os"
  7. "strings"
  8. "gogs.dmsc.dev/arp/arp_cli/client"
  9. "gogs.dmsc.dev/arp/arp_cli/config"
  10. "github.com/AlecAivazis/survey/v2"
  11. "github.com/olekukonko/tablewriter"
  12. "github.com/urfave/cli/v3"
  13. )
  14. // WorkflowCommand returns the workflow command
  15. func WorkflowCommand() *cli.Command {
  16. return &cli.Command{
  17. Name: "workflow",
  18. Usage: "Manage workflows",
  19. Description: `Manage ARP workflows. Workflows are automated processes defined by templates.
  20. Workflow templates define the structure of automated workflows as DAGs (Directed Acyclic Graphs).
  21. Workflow instances are running executions of templates.
  22. Workflow nodes represent individual steps in a running workflow.`,
  23. Commands: []*cli.Command{
  24. // WorkflowTemplate commands
  25. {
  26. Name: "template",
  27. Usage: "Manage workflow templates",
  28. Commands: []*cli.Command{
  29. {
  30. Name: "list",
  31. Aliases: []string{"ls"},
  32. Usage: "List all workflow templates",
  33. Flags: []cli.Flag{
  34. &cli.BoolFlag{
  35. Name: "json",
  36. Aliases: []string{"j"},
  37. Usage: "Output as JSON",
  38. },
  39. },
  40. Action: workflowTemplateList,
  41. },
  42. {
  43. Name: "get",
  44. Usage: "Get a workflow template by ID",
  45. Flags: []cli.Flag{
  46. &cli.StringFlag{
  47. Name: "id",
  48. Aliases: []string{"i"},
  49. Usage: "Template ID",
  50. Required: true,
  51. },
  52. &cli.BoolFlag{
  53. Name: "json",
  54. Aliases: []string{"j"},
  55. Usage: "Output as JSON",
  56. },
  57. },
  58. Action: workflowTemplateGet,
  59. },
  60. {
  61. Name: "create",
  62. Usage: "Create a new workflow template",
  63. Action: workflowTemplateCreate,
  64. Flags: []cli.Flag{
  65. &cli.StringFlag{
  66. Name: "name",
  67. Aliases: []string{"n"},
  68. Usage: "Template name",
  69. },
  70. &cli.StringFlag{
  71. Name: "description",
  72. Aliases: []string{"d"},
  73. Usage: "Template description",
  74. },
  75. &cli.StringFlag{
  76. Name: "definition",
  77. Aliases: []string{"f"},
  78. Usage: "Workflow definition (JSON string or @filename)",
  79. },
  80. &cli.BoolFlag{
  81. Name: "active",
  82. Usage: "Set template as active",
  83. Value: true,
  84. },
  85. },
  86. },
  87. {
  88. Name: "update",
  89. Usage: "Update a workflow template",
  90. Action: workflowTemplateUpdate,
  91. Flags: []cli.Flag{
  92. &cli.StringFlag{
  93. Name: "id",
  94. Aliases: []string{"i"},
  95. Usage: "Template ID",
  96. Required: true,
  97. },
  98. &cli.StringFlag{
  99. Name: "name",
  100. Aliases: []string{"n"},
  101. Usage: "Template name",
  102. },
  103. &cli.StringFlag{
  104. Name: "description",
  105. Aliases: []string{"d"},
  106. Usage: "Template description",
  107. },
  108. &cli.StringFlag{
  109. Name: "definition",
  110. Aliases: []string{"f"},
  111. Usage: "Workflow definition (JSON string or @filename)",
  112. },
  113. &cli.BoolFlag{
  114. Name: "active",
  115. Usage: "Set template as active",
  116. },
  117. },
  118. },
  119. {
  120. Name: "delete",
  121. Usage: "Delete a workflow template",
  122. Action: workflowTemplateDelete,
  123. Flags: []cli.Flag{
  124. &cli.StringFlag{
  125. Name: "id",
  126. Aliases: []string{"i"},
  127. Usage: "Template ID",
  128. Required: true,
  129. },
  130. &cli.BoolFlag{
  131. Name: "yes",
  132. Aliases: []string{"y"},
  133. Usage: "Skip confirmation",
  134. },
  135. },
  136. },
  137. },
  138. },
  139. // WorkflowInstance commands
  140. {
  141. Name: "instance",
  142. Usage: "Manage workflow instances",
  143. Commands: []*cli.Command{
  144. {
  145. Name: "list",
  146. Aliases: []string{"ls"},
  147. Usage: "List all workflow instances",
  148. Flags: []cli.Flag{
  149. &cli.BoolFlag{
  150. Name: "json",
  151. Aliases: []string{"j"},
  152. Usage: "Output as JSON",
  153. },
  154. },
  155. Action: workflowInstanceList,
  156. },
  157. {
  158. Name: "get",
  159. Usage: "Get a workflow instance by ID",
  160. Flags: []cli.Flag{
  161. &cli.StringFlag{
  162. Name: "id",
  163. Aliases: []string{"i"},
  164. Usage: "Instance ID",
  165. Required: true,
  166. },
  167. &cli.BoolFlag{
  168. Name: "json",
  169. Aliases: []string{"j"},
  170. Usage: "Output as JSON",
  171. },
  172. },
  173. Action: workflowInstanceGet,
  174. },
  175. {
  176. Name: "start",
  177. Usage: "Start a new workflow instance from a template",
  178. Action: workflowInstanceStart,
  179. Flags: []cli.Flag{
  180. &cli.StringFlag{
  181. Name: "template",
  182. Aliases: []string{"t"},
  183. Usage: "Template ID",
  184. Required: true,
  185. },
  186. &cli.StringFlag{
  187. Name: "service",
  188. Aliases: []string{"s"},
  189. Usage: "Service ID to associate with the workflow",
  190. },
  191. &cli.StringFlag{
  192. Name: "context",
  193. Aliases: []string{"c"},
  194. Usage: "Initial workflow context (JSON string)",
  195. },
  196. },
  197. },
  198. {
  199. Name: "cancel",
  200. Usage: "Cancel a running workflow instance",
  201. Action: workflowInstanceCancel,
  202. Flags: []cli.Flag{
  203. &cli.StringFlag{
  204. Name: "id",
  205. Aliases: []string{"i"},
  206. Usage: "Instance ID",
  207. Required: true,
  208. },
  209. },
  210. },
  211. },
  212. },
  213. // WorkflowNode commands
  214. {
  215. Name: "node",
  216. Usage: "Manage workflow nodes",
  217. Commands: []*cli.Command{
  218. {
  219. Name: "list",
  220. Aliases: []string{"ls"},
  221. Usage: "List all nodes for a workflow instance",
  222. Flags: []cli.Flag{
  223. &cli.StringFlag{
  224. Name: "instance",
  225. Aliases: []string{"i"},
  226. Usage: "Instance ID",
  227. Required: true,
  228. },
  229. &cli.BoolFlag{
  230. Name: "json",
  231. Aliases: []string{"j"},
  232. Usage: "Output as JSON",
  233. },
  234. },
  235. Action: workflowNodeList,
  236. },
  237. {
  238. Name: "get",
  239. Usage: "Get a workflow node by ID",
  240. Flags: []cli.Flag{
  241. &cli.StringFlag{
  242. Name: "id",
  243. Aliases: []string{"i"},
  244. Usage: "Node ID",
  245. Required: true,
  246. },
  247. &cli.BoolFlag{
  248. Name: "json",
  249. Aliases: []string{"j"},
  250. Usage: "Output as JSON",
  251. },
  252. },
  253. Action: workflowNodeGet,
  254. },
  255. {
  256. Name: "retry",
  257. Usage: "Retry a failed workflow node",
  258. Action: workflowNodeRetry,
  259. Flags: []cli.Flag{
  260. &cli.StringFlag{
  261. Name: "id",
  262. Aliases: []string{"i"},
  263. Usage: "Node ID",
  264. Required: true,
  265. },
  266. },
  267. },
  268. },
  269. },
  270. },
  271. }
  272. }
  273. // WorkflowTemplate represents a workflow template
  274. type WorkflowTemplate struct {
  275. ID string `json:"id"`
  276. Name string `json:"name"`
  277. Description string `json:"description"`
  278. Definition string `json:"definition"`
  279. IsActive bool `json:"isActive"`
  280. CreatedBy *User `json:"createdBy"`
  281. CreatedAt string `json:"createdAt"`
  282. UpdatedAt string `json:"updatedAt"`
  283. }
  284. // WorkflowInstance represents a running workflow instance
  285. type WorkflowInstance struct {
  286. ID string `json:"id"`
  287. Template *WorkflowTemplate `json:"template"`
  288. Status string `json:"status"`
  289. Context string `json:"context"`
  290. Service *Service `json:"service"`
  291. CreatedAt string `json:"createdAt"`
  292. UpdatedAt string `json:"updatedAt"`
  293. CompletedAt *string `json:"completedAt"`
  294. }
  295. // WorkflowNode represents a node in a workflow instance
  296. type WorkflowNode struct {
  297. ID string `json:"id"`
  298. NodeKey string `json:"nodeKey"`
  299. NodeType string `json:"nodeType"`
  300. Status string `json:"status"`
  301. Task *Task `json:"task"`
  302. InputData string `json:"inputData"`
  303. OutputData string `json:"outputData"`
  304. RetryCount int `json:"retryCount"`
  305. CreatedAt string `json:"createdAt"`
  306. UpdatedAt string `json:"updatedAt"`
  307. StartedAt *string `json:"startedAt"`
  308. CompletedAt *string `json:"completedAt"`
  309. }
  310. // WorkflowTemplate CRUD operations
  311. func workflowTemplateList(ctx context.Context, cmd *cli.Command) error {
  312. cfg, err := config.Load()
  313. if err != nil {
  314. return err
  315. }
  316. if err := RequireAuth(cfg); err != nil {
  317. return err
  318. }
  319. c := client.New(cfg.ServerURL)
  320. c.SetToken(cfg.Token)
  321. query := `query WorkflowTemplates { workflowTemplates { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
  322. resp, err := c.Query(query, nil)
  323. if err != nil {
  324. return err
  325. }
  326. var result struct {
  327. WorkflowTemplates []WorkflowTemplate `json:"workflowTemplates"`
  328. }
  329. if err := json.Unmarshal(resp.Data, &result); err != nil {
  330. return err
  331. }
  332. if cmd.Bool("json") {
  333. enc := json.NewEncoder(os.Stdout)
  334. enc.SetIndent("", " ")
  335. return enc.Encode(result.WorkflowTemplates)
  336. }
  337. if len(result.WorkflowTemplates) == 0 {
  338. fmt.Println("No workflow templates found.")
  339. return nil
  340. }
  341. table := tablewriter.NewWriter(os.Stdout)
  342. table.Header([]string{"ID", "Name", "Description", "Active", "Created By", "Created At"})
  343. for _, t := range result.WorkflowTemplates {
  344. desc := t.Description
  345. if len(desc) > 30 {
  346. desc = desc[:27] + "..."
  347. }
  348. createdBy := ""
  349. if t.CreatedBy != nil {
  350. createdBy = t.CreatedBy.Email
  351. }
  352. active := "yes"
  353. if !t.IsActive {
  354. active = "no"
  355. }
  356. table.Append([]string{t.ID, t.Name, desc, active, createdBy, t.CreatedAt})
  357. }
  358. table.Render()
  359. return nil
  360. }
  361. func workflowTemplateGet(ctx context.Context, cmd *cli.Command) error {
  362. cfg, err := config.Load()
  363. if err != nil {
  364. return err
  365. }
  366. if err := RequireAuth(cfg); err != nil {
  367. return err
  368. }
  369. c := client.New(cfg.ServerURL)
  370. c.SetToken(cfg.Token)
  371. id := cmd.String("id")
  372. query := `query WorkflowTemplate($id: ID!) { workflowTemplate(id: $id) { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
  373. resp, err := c.Query(query, map[string]interface{}{"id": id})
  374. if err != nil {
  375. return err
  376. }
  377. var result struct {
  378. WorkflowTemplate *WorkflowTemplate `json:"workflowTemplate"`
  379. }
  380. if err := json.Unmarshal(resp.Data, &result); err != nil {
  381. return err
  382. }
  383. if result.WorkflowTemplate == nil {
  384. return fmt.Errorf("workflow template not found")
  385. }
  386. if cmd.Bool("json") {
  387. enc := json.NewEncoder(os.Stdout)
  388. enc.SetIndent("", " ")
  389. return enc.Encode(result.WorkflowTemplate)
  390. }
  391. t := result.WorkflowTemplate
  392. fmt.Printf("ID: %s\n", t.ID)
  393. fmt.Printf("Name: %s\n", t.Name)
  394. fmt.Printf("Description: %s\n", t.Description)
  395. fmt.Printf("Active: %v\n", t.IsActive)
  396. if t.CreatedBy != nil {
  397. fmt.Printf("Created By: %s\n", t.CreatedBy.Email)
  398. }
  399. fmt.Printf("Created At: %s\n", t.CreatedAt)
  400. fmt.Printf("Updated At: %s\n", t.UpdatedAt)
  401. fmt.Printf("\nDefinition:\n%s\n", t.Definition)
  402. return nil
  403. }
  404. func workflowTemplateCreate(ctx context.Context, cmd *cli.Command) error {
  405. cfg, err := config.Load()
  406. if err != nil {
  407. return err
  408. }
  409. if err := RequireAuth(cfg); err != nil {
  410. return err
  411. }
  412. name := cmd.String("name")
  413. description := cmd.String("description")
  414. definition := cmd.String("definition")
  415. isActive := cmd.Bool("active")
  416. if name == "" {
  417. prompt := &survey.Input{Message: "Template name:"}
  418. if err := survey.AskOne(prompt, &name, survey.WithValidator(survey.Required)); err != nil {
  419. return err
  420. }
  421. }
  422. if description == "" {
  423. prompt := &survey.Input{Message: "Description (optional):"}
  424. survey.AskOne(prompt, &description)
  425. }
  426. if definition == "" {
  427. prompt := &survey.Multiline{Message: "Workflow definition (JSON):"}
  428. if err := survey.AskOne(prompt, &definition, survey.WithValidator(survey.Required)); err != nil {
  429. return err
  430. }
  431. }
  432. // Handle file input for definition
  433. definition = ReadFileOrString(definition)
  434. c := client.New(cfg.ServerURL)
  435. c.SetToken(cfg.Token)
  436. mutation := `mutation CreateWorkflowTemplate($input: NewWorkflowTemplate!) { createWorkflowTemplate(input: $input) { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
  437. input := map[string]interface{}{
  438. "name": name,
  439. "description": description,
  440. "definition": definition,
  441. "isActive": isActive,
  442. }
  443. resp, err := c.Mutation(mutation, map[string]interface{}{"input": input})
  444. if err != nil {
  445. return err
  446. }
  447. var result struct {
  448. CreateWorkflowTemplate *WorkflowTemplate `json:"createWorkflowTemplate"`
  449. }
  450. if err := json.Unmarshal(resp.Data, &result); err != nil {
  451. return err
  452. }
  453. if result.CreateWorkflowTemplate == nil {
  454. return fmt.Errorf("failed to create workflow template")
  455. }
  456. fmt.Printf("Workflow template created successfully!\n")
  457. fmt.Printf("ID: %s\n", result.CreateWorkflowTemplate.ID)
  458. fmt.Printf("Name: %s\n", result.CreateWorkflowTemplate.Name)
  459. return nil
  460. }
  461. func workflowTemplateUpdate(ctx context.Context, cmd *cli.Command) error {
  462. cfg, err := config.Load()
  463. if err != nil {
  464. return err
  465. }
  466. if err := RequireAuth(cfg); err != nil {
  467. return err
  468. }
  469. id := cmd.String("id")
  470. name := cmd.String("name")
  471. description := cmd.String("description")
  472. definition := cmd.String("definition")
  473. // Check if active flag was explicitly set
  474. var isActive *bool
  475. if cmd.IsSet("active") {
  476. val := cmd.Bool("active")
  477. isActive = &val
  478. }
  479. if name == "" && description == "" && definition == "" && isActive == nil {
  480. fmt.Println("No updates provided. Use flags to specify what to update.")
  481. return nil
  482. }
  483. c := client.New(cfg.ServerURL)
  484. c.SetToken(cfg.Token)
  485. input := make(map[string]interface{})
  486. if name != "" {
  487. input["name"] = name
  488. }
  489. if description != "" {
  490. input["description"] = description
  491. }
  492. if definition != "" {
  493. input["definition"] = ReadFileOrString(definition)
  494. }
  495. if isActive != nil {
  496. input["isActive"] = *isActive
  497. }
  498. mutation := `mutation UpdateWorkflowTemplate($id: ID!, $input: UpdateWorkflowTemplateInput!) { updateWorkflowTemplate(id: $id, input: $input) { id name description definition isActive createdAt updatedAt } }`
  499. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input})
  500. if err != nil {
  501. return err
  502. }
  503. var result struct {
  504. UpdateWorkflowTemplate *WorkflowTemplate `json:"updateWorkflowTemplate"`
  505. }
  506. if err := json.Unmarshal(resp.Data, &result); err != nil {
  507. return err
  508. }
  509. if result.UpdateWorkflowTemplate == nil {
  510. return fmt.Errorf("workflow template not found")
  511. }
  512. fmt.Printf("Workflow template updated successfully!\n")
  513. fmt.Printf("ID: %s\n", result.UpdateWorkflowTemplate.ID)
  514. fmt.Printf("Name: %s\n", result.UpdateWorkflowTemplate.Name)
  515. return nil
  516. }
  517. func workflowTemplateDelete(ctx context.Context, cmd *cli.Command) error {
  518. cfg, err := config.Load()
  519. if err != nil {
  520. return err
  521. }
  522. if err := RequireAuth(cfg); err != nil {
  523. return err
  524. }
  525. id := cmd.String("id")
  526. skipConfirm := cmd.Bool("yes")
  527. if !skipConfirm {
  528. confirm := false
  529. prompt := &survey.Confirm{
  530. Message: fmt.Sprintf("Are you sure you want to delete workflow template %s?", id),
  531. Default: false,
  532. }
  533. if err := survey.AskOne(prompt, &confirm); err != nil {
  534. return err
  535. }
  536. if !confirm {
  537. fmt.Println("Deletion cancelled.")
  538. return nil
  539. }
  540. }
  541. c := client.New(cfg.ServerURL)
  542. c.SetToken(cfg.Token)
  543. mutation := `mutation DeleteWorkflowTemplate($id: ID!) { deleteWorkflowTemplate(id: $id) }`
  544. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
  545. if err != nil {
  546. return err
  547. }
  548. var result struct {
  549. DeleteWorkflowTemplate bool `json:"deleteWorkflowTemplate"`
  550. }
  551. if err := json.Unmarshal(resp.Data, &result); err != nil {
  552. return err
  553. }
  554. if result.DeleteWorkflowTemplate {
  555. fmt.Printf("Workflow template %s deleted successfully.\n", id)
  556. } else {
  557. fmt.Printf("Failed to delete workflow template %s.\n", id)
  558. }
  559. return nil
  560. }
  561. // WorkflowInstance operations
  562. func workflowInstanceList(ctx context.Context, cmd *cli.Command) error {
  563. cfg, err := config.Load()
  564. if err != nil {
  565. return err
  566. }
  567. if err := RequireAuth(cfg); err != nil {
  568. return err
  569. }
  570. c := client.New(cfg.ServerURL)
  571. c.SetToken(cfg.Token)
  572. query := `query WorkflowInstances { workflowInstances { id template { id name } status context service { id name } createdAt updatedAt completedAt } }`
  573. resp, err := c.Query(query, nil)
  574. if err != nil {
  575. return err
  576. }
  577. var result struct {
  578. WorkflowInstances []WorkflowInstance `json:"workflowInstances"`
  579. }
  580. if err := json.Unmarshal(resp.Data, &result); err != nil {
  581. return err
  582. }
  583. if cmd.Bool("json") {
  584. enc := json.NewEncoder(os.Stdout)
  585. enc.SetIndent("", " ")
  586. return enc.Encode(result.WorkflowInstances)
  587. }
  588. if len(result.WorkflowInstances) == 0 {
  589. fmt.Println("No workflow instances found.")
  590. return nil
  591. }
  592. table := tablewriter.NewWriter(os.Stdout)
  593. table.Header([]string{"ID", "Template", "Status", "Service", "Created At"})
  594. for _, i := range result.WorkflowInstances {
  595. templateName := ""
  596. if i.Template != nil {
  597. templateName = i.Template.Name
  598. }
  599. serviceName := ""
  600. if i.Service != nil {
  601. serviceName = i.Service.Name
  602. }
  603. table.Append([]string{i.ID, templateName, i.Status, serviceName, i.CreatedAt})
  604. }
  605. table.Render()
  606. return nil
  607. }
  608. func workflowInstanceGet(ctx context.Context, cmd *cli.Command) error {
  609. cfg, err := config.Load()
  610. if err != nil {
  611. return err
  612. }
  613. if err := RequireAuth(cfg); err != nil {
  614. return err
  615. }
  616. c := client.New(cfg.ServerURL)
  617. c.SetToken(cfg.Token)
  618. id := cmd.String("id")
  619. query := `query WorkflowInstance($id: ID!) { workflowInstance(id: $id) { id template { id name description } status context service { id name } createdAt updatedAt completedAt } }`
  620. resp, err := c.Query(query, map[string]interface{}{"id": id})
  621. if err != nil {
  622. return err
  623. }
  624. var result struct {
  625. WorkflowInstance *WorkflowInstance `json:"workflowInstance"`
  626. }
  627. if err := json.Unmarshal(resp.Data, &result); err != nil {
  628. return err
  629. }
  630. if result.WorkflowInstance == nil {
  631. return fmt.Errorf("workflow instance not found")
  632. }
  633. if cmd.Bool("json") {
  634. enc := json.NewEncoder(os.Stdout)
  635. enc.SetIndent("", " ")
  636. return enc.Encode(result.WorkflowInstance)
  637. }
  638. i := result.WorkflowInstance
  639. fmt.Printf("ID: %s\n", i.ID)
  640. if i.Template != nil {
  641. fmt.Printf("Template: %s (%s)\n", i.Template.Name, i.Template.ID)
  642. }
  643. fmt.Printf("Status: %s\n", i.Status)
  644. if i.Service != nil {
  645. fmt.Printf("Service: %s (%s)\n", i.Service.Name, i.Service.ID)
  646. }
  647. fmt.Printf("Created At: %s\n", i.CreatedAt)
  648. fmt.Printf("Updated At: %s\n", i.UpdatedAt)
  649. if i.CompletedAt != nil {
  650. fmt.Printf("Completed At: %s\n", *i.CompletedAt)
  651. }
  652. if i.Context != "" {
  653. fmt.Printf("\nContext:\n%s\n", i.Context)
  654. }
  655. return nil
  656. }
  657. func workflowInstanceStart(ctx context.Context, cmd *cli.Command) error {
  658. cfg, err := config.Load()
  659. if err != nil {
  660. return err
  661. }
  662. if err := RequireAuth(cfg); err != nil {
  663. return err
  664. }
  665. templateID := cmd.String("template")
  666. serviceID := cmd.String("service")
  667. contextJSON := cmd.String("context")
  668. c := client.New(cfg.ServerURL)
  669. c.SetToken(cfg.Token)
  670. mutation := `mutation StartWorkflow($templateId: ID!, $input: StartWorkflowInput!) { startWorkflow(templateId: $templateId, input: $input) { id template { id name } status context service { id name } createdAt } }`
  671. input := make(map[string]interface{})
  672. if serviceID != "" {
  673. input["serviceId"] = serviceID
  674. }
  675. if contextJSON != "" {
  676. input["context"] = contextJSON
  677. }
  678. resp, err := c.Mutation(mutation, map[string]interface{}{"templateId": templateID, "input": input})
  679. if err != nil {
  680. return err
  681. }
  682. var result struct {
  683. StartWorkflow *WorkflowInstance `json:"startWorkflow"`
  684. }
  685. if err := json.Unmarshal(resp.Data, &result); err != nil {
  686. return err
  687. }
  688. if result.StartWorkflow == nil {
  689. return fmt.Errorf("failed to start workflow")
  690. }
  691. fmt.Printf("Workflow started successfully!\n")
  692. fmt.Printf("Instance ID: %s\n", result.StartWorkflow.ID)
  693. if result.StartWorkflow.Template != nil {
  694. fmt.Printf("Template: %s\n", result.StartWorkflow.Template.Name)
  695. }
  696. fmt.Printf("Status: %s\n", result.StartWorkflow.Status)
  697. return nil
  698. }
  699. func workflowInstanceCancel(ctx context.Context, cmd *cli.Command) error {
  700. cfg, err := config.Load()
  701. if err != nil {
  702. return err
  703. }
  704. if err := RequireAuth(cfg); err != nil {
  705. return err
  706. }
  707. id := cmd.String("id")
  708. c := client.New(cfg.ServerURL)
  709. c.SetToken(cfg.Token)
  710. mutation := `mutation CancelWorkflow($id: ID!) { cancelWorkflow(id: $id) { id status completedAt } }`
  711. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
  712. if err != nil {
  713. return err
  714. }
  715. var result struct {
  716. CancelWorkflow *WorkflowInstance `json:"cancelWorkflow"`
  717. }
  718. if err := json.Unmarshal(resp.Data, &result); err != nil {
  719. return err
  720. }
  721. if result.CancelWorkflow == nil {
  722. return fmt.Errorf("workflow instance not found")
  723. }
  724. fmt.Printf("Workflow cancelled successfully!\n")
  725. fmt.Printf("Instance ID: %s\n", result.CancelWorkflow.ID)
  726. fmt.Printf("Status: %s\n", result.CancelWorkflow.Status)
  727. return nil
  728. }
  729. // WorkflowNode operations
  730. func workflowNodeList(ctx context.Context, cmd *cli.Command) error {
  731. cfg, err := config.Load()
  732. if err != nil {
  733. return err
  734. }
  735. if err := RequireAuth(cfg); err != nil {
  736. return err
  737. }
  738. c := client.New(cfg.ServerURL)
  739. c.SetToken(cfg.Token)
  740. instanceID := cmd.String("instance")
  741. query := `query WorkflowInstance($id: ID!) { workflowInstance(id: $id) { id status nodes: workflowNodes { id nodeKey nodeType status task { id title } retryCount createdAt startedAt completedAt } } }`
  742. resp, err := c.Query(query, map[string]interface{}{"id": instanceID})
  743. if err != nil {
  744. return err
  745. }
  746. var result struct {
  747. WorkflowInstance *struct {
  748. ID string `json:"id"`
  749. Status string `json:"status"`
  750. Nodes []WorkflowNode `json:"nodes"`
  751. } `json:"workflowInstance"`
  752. }
  753. if err := json.Unmarshal(resp.Data, &result); err != nil {
  754. return err
  755. }
  756. if result.WorkflowInstance == nil {
  757. return fmt.Errorf("workflow instance not found")
  758. }
  759. if cmd.Bool("json") {
  760. enc := json.NewEncoder(os.Stdout)
  761. enc.SetIndent("", " ")
  762. return enc.Encode(result.WorkflowInstance.Nodes)
  763. }
  764. if len(result.WorkflowInstance.Nodes) == 0 {
  765. fmt.Println("No nodes found for this workflow instance.")
  766. return nil
  767. }
  768. table := tablewriter.NewWriter(os.Stdout)
  769. table.Header([]string{"ID", "Key", "Type", "Status", "Task", "Retries"})
  770. for _, n := range result.WorkflowInstance.Nodes {
  771. taskTitle := ""
  772. if n.Task != nil {
  773. taskTitle = n.Task.Title
  774. if len(taskTitle) > 30 {
  775. taskTitle = taskTitle[:27] + "..."
  776. }
  777. }
  778. table.Append([]string{n.ID, n.NodeKey, n.NodeType, n.Status, taskTitle, fmt.Sprintf("%d", n.RetryCount)})
  779. }
  780. table.Render()
  781. return nil
  782. }
  783. func workflowNodeGet(ctx context.Context, cmd *cli.Command) error {
  784. cfg, err := config.Load()
  785. if err != nil {
  786. return err
  787. }
  788. if err := RequireAuth(cfg); err != nil {
  789. return err
  790. }
  791. c := client.New(cfg.ServerURL)
  792. c.SetToken(cfg.Token)
  793. id := cmd.String("id")
  794. query := `query WorkflowNode($id: ID!) { workflowNode(id: $id) { id nodeKey nodeType status task { id title } inputData outputData retryCount createdAt updatedAt startedAt completedAt } }`
  795. // Note: This assumes a workflowNode query exists. If not, we need to fetch via instance
  796. // For now, let's use a workaround by fetching the instance and finding the node
  797. query = `query WorkflowInstances { workflowInstances { id nodes: workflowNodes { id nodeKey nodeType status task { id title content } inputData outputData retryCount createdAt updatedAt startedAt completedAt } } }`
  798. resp, err := c.Query(query, nil)
  799. if err != nil {
  800. return err
  801. }
  802. var result struct {
  803. WorkflowInstances []struct {
  804. ID string `json:"id"`
  805. Nodes []WorkflowNode `json:"nodes"`
  806. } `json:"workflowInstances"`
  807. }
  808. if err := json.Unmarshal(resp.Data, &result); err != nil {
  809. return err
  810. }
  811. // Find the node
  812. var node *WorkflowNode
  813. for _, instance := range result.WorkflowInstances {
  814. for _, n := range instance.Nodes {
  815. if n.ID == id {
  816. node = &n
  817. break
  818. }
  819. }
  820. if node != nil {
  821. break
  822. }
  823. }
  824. if node == nil {
  825. return fmt.Errorf("workflow node not found")
  826. }
  827. if cmd.Bool("json") {
  828. enc := json.NewEncoder(os.Stdout)
  829. enc.SetIndent("", " ")
  830. return enc.Encode(node)
  831. }
  832. fmt.Printf("ID: %s\n", node.ID)
  833. fmt.Printf("Key: %s\n", node.NodeKey)
  834. fmt.Printf("Type: %s\n", node.NodeType)
  835. fmt.Printf("Status: %s\n", node.Status)
  836. fmt.Printf("Retry Count: %d\n", node.RetryCount)
  837. if node.Task != nil {
  838. fmt.Printf("Task: %s (%s)\n", node.Task.Title, node.Task.ID)
  839. }
  840. fmt.Printf("Created At: %s\n", node.CreatedAt)
  841. fmt.Printf("Updated At: %s\n", node.UpdatedAt)
  842. if node.StartedAt != nil {
  843. fmt.Printf("Started At: %s\n", *node.StartedAt)
  844. }
  845. if node.CompletedAt != nil {
  846. fmt.Printf("Completed At: %s\n", *node.CompletedAt)
  847. }
  848. if node.InputData != "" {
  849. fmt.Printf("\nInput Data:\n%s\n", node.InputData)
  850. }
  851. if node.OutputData != "" {
  852. fmt.Printf("\nOutput Data:\n%s\n", node.OutputData)
  853. }
  854. return nil
  855. }
  856. func workflowNodeRetry(ctx context.Context, cmd *cli.Command) error {
  857. cfg, err := config.Load()
  858. if err != nil {
  859. return err
  860. }
  861. if err := RequireAuth(cfg); err != nil {
  862. return err
  863. }
  864. id := cmd.String("id")
  865. c := client.New(cfg.ServerURL)
  866. c.SetToken(cfg.Token)
  867. mutation := `mutation RetryWorkflowNode($nodeId: ID!) { retryWorkflowNode(nodeId: $nodeId) { id nodeKey status retryCount } }`
  868. resp, err := c.Mutation(mutation, map[string]interface{}{"nodeId": id})
  869. if err != nil {
  870. return err
  871. }
  872. var result struct {
  873. RetryWorkflowNode *WorkflowNode `json:"retryWorkflowNode"`
  874. }
  875. if err := json.Unmarshal(resp.Data, &result); err != nil {
  876. return err
  877. }
  878. if result.RetryWorkflowNode == nil {
  879. return fmt.Errorf("workflow node not found")
  880. }
  881. fmt.Printf("Workflow node retry initiated!\n")
  882. fmt.Printf("Node ID: %s\n", result.RetryWorkflowNode.ID)
  883. fmt.Printf("Key: %s\n", result.RetryWorkflowNode.NodeKey)
  884. fmt.Printf("Status: %s\n", result.RetryWorkflowNode.Status)
  885. fmt.Printf("Retry Count: %d\n", result.RetryWorkflowNode.RetryCount)
  886. return nil
  887. }
  888. // ReadFileOrString reads file content if input starts with @, otherwise returns as-is
  889. func ReadFileOrString(input string) string {
  890. if strings.HasPrefix(input, "@") {
  891. filename := input[1:]
  892. content, err := os.ReadFile(filename)
  893. if err != nil {
  894. return input // Return original if file can't be read
  895. }
  896. return string(content)
  897. }
  898. return input
  899. }