internal/control/org.go
216 lines · 6578 bytes
1package control
2
3import (
4 "errors"
5 "fmt"
6 "io"
7
8 "gitbay.org/gitbay/internal/policy"
9 "gitbay.org/gitbay/internal/protocol"
10 "gitbay.org/gitbay/internal/store"
11)
12
13func init() {
14 register(Command{Path: []string{"org", "create"},
15 Summary: "create an organization (you become its first admin): org create <name>", Run: runOrgCreate})
16 register(Command{Path: []string{"org", "list"},
17 Summary: "list organizations you belong to", Run: runOrgList})
18 register(Command{Path: []string{"org", "show"},
19 Summary: "show an organization and its members: org show <name>", Run: runOrgShow})
20 register(Command{Path: []string{"org", "delete"},
21 Summary: "delete an empty organization: org delete <name> --yes", Run: runOrgDelete})
22 register(Command{Path: []string{"org", "members", "add"},
23 Summary: "add or update a member: org members add <org> <user> [--role member|admin]", Run: runOrgMembersAdd})
24 register(Command{Path: []string{"org", "members", "remove"},
25 Summary: "remove a member: org members remove <org> <user>", Run: runOrgMembersRemove})
26 register(Command{Path: []string{"org", "members", "list"},
27 Summary: "list members: org members list <org>", Run: runOrgMembersList})
28}
29
30// orgAdmin loads an org and requires the caller to be one of its admins.
31func orgAdmin(c *Ctx, name string) (store.Org, int) {
32 org, err := c.Store.OrgByName(name)
33 if errors.Is(err, store.ErrNotFound) {
34 return org, c.fail(protocol.ExitNotFound, "no organization %q", name)
35 }
36 if err != nil {
37 return org, c.fail(protocol.ExitFailure, "%v", err)
38 }
39 role, err := c.Store.OrgRole(org.ID, c.User.ID)
40 if err != nil {
41 return org, c.fail(protocol.ExitFailure, "%v", err)
42 }
43 if role != "admin" {
44 return org, c.fail(protocol.ExitDenied, "only admins of %s can do that", name)
45 }
46 return org, -1
47}
48
49func runOrgCreate(c *Ctx, args []string) int {
50 if len(args) != 1 {
51 return c.fail(protocol.ExitUsage, "usage: org create <name>")
52 }
53 if err := policy.ValidateOwnerName(args[0]); err != nil {
54 return c.fail(protocol.ExitUsage, "%v", err)
55 }
56 if _, err := c.Store.CreateOrg(args[0], c.User.ID); err != nil {
57 return c.fail(protocol.ExitFailure, "%v", err)
58 }
59 return c.emit(map[string]string{"org": args[0], "role": "admin"}, func(w io.Writer) {
60 fmt.Fprintf(w, "created organization %s; you are its admin\n", args[0])
61 })
62}
63
64func runOrgList(c *Ctx, args []string) int {
65 orgs, err := c.Store.ListOrgsForUser(c.User.ID)
66 if err != nil {
67 return c.fail(protocol.ExitFailure, "%v", err)
68 }
69 type out struct {
70 Org string `json:"org"`
71 Role string `json:"role"`
72 }
73 var ds []out
74 for _, o := range orgs {
75 ds = append(ds, out{o.Username, o.Role})
76 }
77 return c.emit(ds, func(w io.Writer) {
78 for _, d := range ds {
79 fmt.Fprintf(w, "%s\t%s\n", d.Org, d.Role)
80 }
81 })
82}
83
84func runOrgShow(c *Ctx, args []string) int {
85 if len(args) != 1 {
86 return c.fail(protocol.ExitUsage, "usage: org show <name>")
87 }
88 org, err := c.Store.OrgByName(args[0])
89 if errors.Is(err, store.ErrNotFound) {
90 return c.fail(protocol.ExitNotFound, "no organization %q", args[0])
91 }
92 if err != nil {
93 return c.fail(protocol.ExitFailure, "%v", err)
94 }
95 members, err := c.Store.OrgMembers(org.ID)
96 if err != nil {
97 return c.fail(protocol.ExitFailure, "%v", err)
98 }
99 type memberOut struct {
100 User string `json:"user"`
101 Role string `json:"role"`
102 }
103 var ms []memberOut
104 for _, m := range members {
105 ms = append(ms, memberOut{m.Username, m.Role})
106 }
107 d := struct {
108 Org string `json:"org"`
109 Members []memberOut `json:"members"`
110 }{org.Name, ms}
111 return c.emit(d, func(w io.Writer) {
112 fmt.Fprintf(w, "%s\n", d.Org)
113 for _, m := range ms {
114 fmt.Fprintf(w, " %s\t%s\n", m.User, m.Role)
115 }
116 })
117}
118
119func runOrgDelete(c *Ctx, args []string) int {
120 var name string
121 yes := false
122 for _, a := range args {
123 if a == "--yes" {
124 yes = true
125 } else if name == "" {
126 name = a
127 } else {
128 return c.fail(protocol.ExitUsage, "usage: org delete <name> --yes")
129 }
130 }
131 if name == "" {
132 return c.fail(protocol.ExitUsage, "usage: org delete <name> --yes")
133 }
134 org, code := orgAdmin(c, name)
135 if code >= 0 {
136 return code
137 }
138 if !yes {
139 return c.fail(protocol.ExitUsage, "org delete is permanent; re-run with --yes")
140 }
141 if err := c.Store.DeleteOrg(org.ID); err != nil {
142 return c.fail(protocol.ExitFailure, "%v", err)
143 }
144 return c.emit(map[string]string{"deleted": name}, func(w io.Writer) {
145 fmt.Fprintf(w, "deleted organization %s\n", name)
146 })
147}
148
149func runOrgMembersAdd(c *Ctx, args []string) int {
150 role := "member"
151 var rest []string
152 for i := 0; i < len(args); i++ {
153 if args[i] == "--role" {
154 if i+1 >= len(args) {
155 return c.fail(protocol.ExitUsage, "--role requires member|admin")
156 }
157 role = args[i+1]
158 i++
159 continue
160 }
161 rest = append(rest, args[i])
162 }
163 if len(rest) != 2 || (role != "member" && role != "admin") {
164 return c.fail(protocol.ExitUsage, "usage: org members add <org> <user> [--role member|admin]")
165 }
166 org, code := orgAdmin(c, rest[0])
167 if code >= 0 {
168 return code
169 }
170 target, err := c.Store.UserByUsername(rest[1])
171 if errors.Is(err, store.ErrNotFound) {
172 return c.fail(protocol.ExitNotFound, "no such user %q", rest[1])
173 }
174 if err != nil {
175 return c.fail(protocol.ExitFailure, "%v", err)
176 }
177 if err := c.Store.SetOrgMember(org.ID, target.ID, role); err != nil {
178 return c.fail(protocol.ExitUsage, "%v", err)
179 }
180 return c.emit(map[string]string{"org": org.Name, "user": target.Username, "role": role}, func(w io.Writer) {
181 fmt.Fprintf(w, "%s is now a %s of %s\n", target.Username, role, org.Name)
182 })
183}
184
185func runOrgMembersRemove(c *Ctx, args []string) int {
186 if len(args) != 2 {
187 return c.fail(protocol.ExitUsage, "usage: org members remove <org> <user>")
188 }
189 org, code := orgAdmin(c, args[0])
190 if code >= 0 {
191 return code
192 }
193 target, err := c.Store.UserByUsername(args[1])
194 if errors.Is(err, store.ErrNotFound) {
195 return c.fail(protocol.ExitNotFound, "no such user %q", args[1])
196 }
197 if err != nil {
198 return c.fail(protocol.ExitFailure, "%v", err)
199 }
200 if err := c.Store.RemoveOrgMember(org.ID, target.ID); err != nil {
201 if errors.Is(err, store.ErrNotFound) {
202 return c.fail(protocol.ExitNotFound, "%s is not a member of %s", target.Username, org.Name)
203 }
204 return c.fail(protocol.ExitUsage, "%v", err)
205 }
206 return c.emit(map[string]string{"org": org.Name, "removed": target.Username}, func(w io.Writer) {
207 fmt.Fprintf(w, "removed %s from %s\n", target.Username, org.Name)
208 })
209}
210
211func runOrgMembersList(c *Ctx, args []string) int {
212 if len(args) != 1 {
213 return c.fail(protocol.ExitUsage, "usage: org members list <org>")
214 }
215 return runOrgShow(c, args)
216}