role.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. package cmd
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "os"
  7. "strings"
  8. "github.com/AlecAivazis/survey/v2"
  9. "github.com/olekukonko/tablewriter"
  10. "github.com/urfave/cli/v3"
  11. )
  12. // RoleCommand returns the role command
  13. func RoleCommand() *cli.Command {
  14. return &cli.Command{
  15. Name: "role",
  16. Usage: "Manage roles",
  17. Description: `Manage ARP roles. Roles group permissions for access control.
  18. Use this command to create, list, update, and delete roles.`,
  19. Commands: []*cli.Command{
  20. {
  21. Name: "list",
  22. Aliases: []string{"ls"},
  23. Usage: "List all roles",
  24. Flags: []cli.Flag{
  25. &cli.BoolFlag{
  26. Name: "json",
  27. Aliases: []string{"j"},
  28. Usage: "Output as JSON",
  29. },
  30. },
  31. Action: roleList,
  32. },
  33. {
  34. Name: "get",
  35. Usage: "Get a role by ID",
  36. Flags: []cli.Flag{
  37. &cli.StringFlag{
  38. Name: "id",
  39. Aliases: []string{"i"},
  40. Usage: "Role ID",
  41. Required: true,
  42. },
  43. &cli.BoolFlag{
  44. Name: "json",
  45. Aliases: []string{"j"},
  46. Usage: "Output as JSON",
  47. },
  48. },
  49. Action: roleGet,
  50. },
  51. {
  52. Name: "create",
  53. Usage: "Create a new role",
  54. Action: roleCreate,
  55. Flags: []cli.Flag{
  56. &cli.StringFlag{
  57. Name: "name",
  58. Aliases: []string{"n"},
  59. Usage: "Role name",
  60. },
  61. &cli.StringFlag{
  62. Name: "description",
  63. Aliases: []string{"d"},
  64. Usage: "Role description",
  65. },
  66. &cli.StringSliceFlag{
  67. Name: "permissions",
  68. Aliases: []string{"p"},
  69. Usage: "Permission IDs",
  70. },
  71. },
  72. },
  73. {
  74. Name: "update",
  75. Usage: "Update a role",
  76. Action: roleUpdate,
  77. Flags: []cli.Flag{
  78. &cli.StringFlag{
  79. Name: "id",
  80. Aliases: []string{"i"},
  81. Usage: "Role ID",
  82. Required: true,
  83. },
  84. &cli.StringFlag{
  85. Name: "name",
  86. Aliases: []string{"n"},
  87. Usage: "Role name",
  88. },
  89. &cli.StringFlag{
  90. Name: "description",
  91. Aliases: []string{"d"},
  92. Usage: "Role description",
  93. },
  94. &cli.StringSliceFlag{
  95. Name: "permissions",
  96. Aliases: []string{"p"},
  97. Usage: "Permission IDs",
  98. },
  99. },
  100. },
  101. {
  102. Name: "delete",
  103. Usage: "Delete a role",
  104. Action: roleDelete,
  105. Flags: []cli.Flag{
  106. &cli.StringFlag{
  107. Name: "id",
  108. Aliases: []string{"i"},
  109. Usage: "Role ID",
  110. Required: true,
  111. },
  112. &cli.BoolFlag{
  113. Name: "yes",
  114. Aliases: []string{"y"},
  115. Usage: "Skip confirmation",
  116. },
  117. },
  118. },
  119. },
  120. }
  121. }
  122. type RoleDetail struct {
  123. ID string `json:"id"`
  124. Name string `json:"name"`
  125. Description string `json:"description"`
  126. Permissions []Permission `json:"permissions"`
  127. }
  128. type Permission struct {
  129. ID string `json:"id"`
  130. Code string `json:"code"`
  131. Description string `json:"description"`
  132. }
  133. func roleList(ctx context.Context, cmd *cli.Command) error {
  134. c, cfg, err := GetClient(ctx, cmd)
  135. if err != nil {
  136. return err
  137. }
  138. if err := RequireAuth(cfg); err != nil {
  139. return err
  140. }
  141. query := "query Roles { roles { id name description permissions { id code } } }"
  142. resp, err := c.Query(query, nil)
  143. if err != nil {
  144. return err
  145. }
  146. var result struct {
  147. Roles []RoleDetail `json:"roles"`
  148. }
  149. if err := json.Unmarshal(resp.Data, &result); err != nil {
  150. return err
  151. }
  152. if cmd.Bool("json") {
  153. enc := json.NewEncoder(os.Stdout)
  154. enc.SetIndent("", " ")
  155. return enc.Encode(result.Roles)
  156. }
  157. if len(result.Roles) == 0 {
  158. fmt.Println("No roles found.")
  159. return nil
  160. }
  161. table := tablewriter.NewWriter(os.Stdout)
  162. table.Header([]string{"ID", "Name", "Description", "Permissions"})
  163. for _, r := range result.Roles {
  164. perms := make([]string, len(r.Permissions))
  165. for i, p := range r.Permissions {
  166. perms[i] = p.Code
  167. }
  168. table.Append([]string{r.ID, r.Name, r.Description, strings.Join(perms, ", ")})
  169. }
  170. table.Render()
  171. return nil
  172. }
  173. func roleGet(ctx context.Context, cmd *cli.Command) error {
  174. c, cfg, err := GetClient(ctx, cmd)
  175. if err != nil {
  176. return err
  177. }
  178. if err := RequireAuth(cfg); err != nil {
  179. return err
  180. }
  181. id := cmd.String("id")
  182. query := "query Role($id: ID!) { role(id: $id) { id name description permissions { id code description } } }"
  183. resp, err := c.Query(query, map[string]interface{}{"id": id})
  184. if err != nil {
  185. return err
  186. }
  187. var result struct {
  188. Role *RoleDetail `json:"role"`
  189. }
  190. if err := json.Unmarshal(resp.Data, &result); err != nil {
  191. return err
  192. }
  193. if result.Role == nil {
  194. return fmt.Errorf("role not found")
  195. }
  196. if cmd.Bool("json") {
  197. enc := json.NewEncoder(os.Stdout)
  198. enc.SetIndent("", " ")
  199. return enc.Encode(result.Role)
  200. }
  201. r := result.Role
  202. fmt.Printf("ID: %s\n", r.ID)
  203. fmt.Printf("Name: %s\n", r.Name)
  204. fmt.Printf("Description: %s\n", r.Description)
  205. fmt.Printf("Permissions:\n")
  206. for _, p := range r.Permissions {
  207. fmt.Printf(" - %s (%s): %s\n", p.Code, p.ID, p.Description)
  208. }
  209. return nil
  210. }
  211. func roleCreate(ctx context.Context, cmd *cli.Command) error {
  212. c, cfg, err := GetClient(ctx, cmd)
  213. if err != nil {
  214. return err
  215. }
  216. if err := RequireAuth(cfg); err != nil {
  217. return err
  218. }
  219. name := cmd.String("name")
  220. description := cmd.String("description")
  221. permissions := cmd.StringSlice("permissions")
  222. if name == "" {
  223. prompt := &survey.Input{Message: "Role name:"}
  224. if err := survey.AskOne(prompt, &name, survey.WithValidator(survey.Required)); err != nil {
  225. return err
  226. }
  227. }
  228. if description == "" {
  229. prompt := &survey.Input{Message: "Description:"}
  230. if err := survey.AskOne(prompt, &description, survey.WithValidator(survey.Required)); err != nil {
  231. return err
  232. }
  233. }
  234. if len(permissions) == 0 {
  235. var permissionsStr string
  236. prompt := &survey.Input{Message: "Permission IDs (comma-separated):"}
  237. if err := survey.AskOne(prompt, &permissionsStr, survey.WithValidator(survey.Required)); err != nil {
  238. return err
  239. }
  240. for _, p := range strings.Split(permissionsStr, ",") {
  241. permissions = append(permissions, strings.TrimSpace(p))
  242. }
  243. }
  244. mutation := `mutation CreateRole($input: NewRole!) { createRole(input: $input) { id name description permissions { id code } } }`
  245. input := map[string]interface{}{
  246. "name": name,
  247. "description": description,
  248. "permissions": permissions,
  249. }
  250. resp, err := c.Mutation(mutation, map[string]interface{}{"input": input})
  251. if err != nil {
  252. return err
  253. }
  254. var result struct {
  255. CreateRole *RoleDetail `json:"createRole"`
  256. }
  257. if err := json.Unmarshal(resp.Data, &result); err != nil {
  258. return err
  259. }
  260. if result.CreateRole == nil {
  261. return fmt.Errorf("failed to create role")
  262. }
  263. fmt.Printf("Role created successfully!\n")
  264. fmt.Printf("ID: %s\n", result.CreateRole.ID)
  265. fmt.Printf("Name: %s\n", result.CreateRole.Name)
  266. return nil
  267. }
  268. func roleUpdate(ctx context.Context, cmd *cli.Command) error {
  269. c, cfg, err := GetClient(ctx, cmd)
  270. if err != nil {
  271. return err
  272. }
  273. if err := RequireAuth(cfg); err != nil {
  274. return err
  275. }
  276. id := cmd.String("id")
  277. name := cmd.String("name")
  278. description := cmd.String("description")
  279. permissions := cmd.StringSlice("permissions")
  280. if name == "" && description == "" && len(permissions) == 0 {
  281. fmt.Println("No updates provided. Use flags to specify what to update.")
  282. return nil
  283. }
  284. input := make(map[string]interface{})
  285. if name != "" {
  286. input["name"] = name
  287. }
  288. if description != "" {
  289. input["description"] = description
  290. }
  291. if len(permissions) > 0 {
  292. input["permissions"] = permissions
  293. }
  294. mutation := `mutation UpdateRole($id: ID!, $input: UpdateRoleInput!) { updateRole(id: $id, input: $input) { id name description permissions { id code } } }`
  295. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input})
  296. if err != nil {
  297. return err
  298. }
  299. var result struct {
  300. UpdateRole *RoleDetail `json:"updateRole"`
  301. }
  302. if err := json.Unmarshal(resp.Data, &result); err != nil {
  303. return err
  304. }
  305. if result.UpdateRole == nil {
  306. return fmt.Errorf("role not found")
  307. }
  308. fmt.Printf("Role updated successfully!\n")
  309. fmt.Printf("ID: %s\n", result.UpdateRole.ID)
  310. fmt.Printf("Name: %s\n", result.UpdateRole.Name)
  311. return nil
  312. }
  313. func roleDelete(ctx context.Context, cmd *cli.Command) error {
  314. c, cfg, err := GetClient(ctx, cmd)
  315. if err != nil {
  316. return err
  317. }
  318. if err := RequireAuth(cfg); err != nil {
  319. return err
  320. }
  321. id := cmd.String("id")
  322. skipConfirm := cmd.Bool("yes")
  323. if !skipConfirm {
  324. confirm := false
  325. prompt := &survey.Confirm{
  326. Message: fmt.Sprintf("Are you sure you want to delete role %s?", id),
  327. Default: false,
  328. }
  329. if err := survey.AskOne(prompt, &confirm); err != nil {
  330. return err
  331. }
  332. if !confirm {
  333. fmt.Println("Deletion cancelled.")
  334. return nil
  335. }
  336. }
  337. mutation := `mutation DeleteRole($id: ID!) { deleteRole(id: $id) }`
  338. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
  339. if err != nil {
  340. return err
  341. }
  342. var result struct {
  343. DeleteRole bool `json:"deleteRole"`
  344. }
  345. if err := json.Unmarshal(resp.Data, &result); err != nil {
  346. return err
  347. }
  348. if result.DeleteRole {
  349. fmt.Printf("Role %s deleted successfully.\n", id)
  350. } else {
  351. fmt.Printf("Failed to delete role %s.\n", id)
  352. }
  353. return nil
  354. }