code.oscarkilo.com/okg

Hash:
119bd420753e057e5e7a06d61352f759666947bc
Author:
Igor Naverniouk <[email protected]>
Date:
Mon Mar 9 22:00:18 2026 -0400
Message:
Add okg repo create command Calls Klee's POST /.add-repo to create a new repo. The caller (identified by API key) becomes the owner. Optional --reader flag sets the reader user/group. Usage: okg repo create NAME [--reader USER] Added 7 tests covering arg parsing, server interaction, and error cases.
diff --git a/main.go b/main.go
index 2c62b20..f22ec75 100644
--- a/main.go
+++ b/main.go
@@ -47,6 +47,7 @@ Usage:
okg pr merge NUMBER [--json]
okg pr close NUMBER [--json]
okg pr reopen NUMBER [--json]
+ okg repo create NAME [--reader USER]
okg repo list [--json]
okg auth login [--host HOST] [--user USERNAME]

diff --git a/okg_test.go b/okg_test.go
index 6f1c920..62dffa9 100644
--- a/okg_test.go
+++ b/okg_test.go
@@ -1,6 +1,10 @@
package main

+import "encoding/json"
+import "net/http"
+import "net/http/httptest"
import "os"
+import "strings"
import "testing"
import "time"

@@ -138,3 +142,128 @@ func TestConfigDefaultHost(t *testing.T) {
cfg.Host)
}
}
+
+func TestRunRepoCreateArgs(t *testing.T) {
+ // Mock server that accepts /.add-repo.
+ mock := newMockKlee(t, 204, "")
+ defer mock.Close()
+
+ os.Setenv("OKG_HOST", mock.URL)
+ os.Setenv("KLEX_API_KEY", "test-key")
+ defer os.Unsetenv("OKG_HOST")
+ defer os.Unsetenv("KLEX_API_KEY")
+
+ err := runRepoCreate([]string{
+ "my-repo", "--reader", "igor.agents",
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if mock.req.RepoName != "my-repo" {
+ t.Errorf("repo_name: want my-repo, got %q",
+ mock.req.RepoName)
+ }
+ if mock.req.ReaderUsername != "igor.agents" {
+ t.Errorf("reader: want igor.agents, got %q",
+ mock.req.ReaderUsername)
+ }
+}
+
+func TestRunRepoCreateNoReader(t *testing.T) {
+ mock := newMockKlee(t, 204, "")
+ defer mock.Close()
+
+ os.Setenv("OKG_HOST", mock.URL)
+ os.Setenv("KLEX_API_KEY", "test-key")
+ defer os.Unsetenv("OKG_HOST")
+ defer os.Unsetenv("KLEX_API_KEY")
+
+ err := runRepoCreate([]string{"my-repo"})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if mock.req.ReaderUsername != "" {
+ t.Errorf("reader: want empty, got %q",
+ mock.req.ReaderUsername)
+ }
+}
+
+func TestRunRepoCreateMissingName(t *testing.T) {
+ err := runRepoCreate(nil)
+ if err == nil {
+ t.Fatal("want error for missing name")
+ }
+}
+
+func TestRunRepoCreateReaderMissingValue(t *testing.T) {
+ err := runRepoCreate([]string{
+ "my-repo", "--reader",
+ })
+ if err == nil {
+ t.Fatal("want error for --reader without value")
+ }
+}
+
+func TestRunRepoCreateUnknownFlag(t *testing.T) {
+ err := runRepoCreate([]string{
+ "my-repo", "--bogus",
+ })
+ if err == nil {
+ t.Fatal("want error for unknown flag")
+ }
+}
+
+func TestRunRepoCreateDuplicateName(t *testing.T) {
+ err := runRepoCreate([]string{
+ "my-repo", "extra",
+ })
+ if err == nil {
+ t.Fatal("want error for extra positional arg")
+ }
+}
+
+func TestRunRepoCreateServerError(t *testing.T) {
+ mock := newMockKlee(t, 403,
+ "you are not an owner of "+
+ "klee://code.oscarkilo.com/.new-repo")
+ defer mock.Close()
+
+ os.Setenv("OKG_HOST", mock.URL)
+ os.Setenv("KLEX_API_KEY", "test-key")
+ defer os.Unsetenv("OKG_HOST")
+ defer os.Unsetenv("KLEX_API_KEY")
+
+ err := runRepoCreate([]string{"my-repo"})
+ if err == nil {
+ t.Fatal("want error on 403")
+ }
+ if !strings.Contains(err.Error(), "403") {
+ t.Errorf("want 403 in error, got %q", err)
+ }
+}
+
+// mockKlee captures the /.add-repo request body.
+type mockKlee struct {
+ *httptest.Server
+ req createRepoRequest
+}
+
+func newMockKlee(
+ t *testing.T, status int, body string,
+) *mockKlee {
+ t.Helper()
+ mk := &mockKlee{}
+ mk.Server = httptest.NewServer(
+ http.HandlerFunc(func(
+ w http.ResponseWriter, r *http.Request,
+ ) {
+ json.NewDecoder(r.Body).Decode(&mk.req)
+ w.WriteHeader(status)
+ if body != "" {
+ w.Write([]byte(body))
+ }
+ }))
+ return mk
+}
diff --git a/repo.go b/repo.go
index fe1e54f..e34c3cd 100644
--- a/repo.go
+++ b/repo.go
@@ -3,6 +3,7 @@ package main
import "encoding/json"
import "fmt"
import "os"
+import "strings"
import "text/tabwriter"

type repoInfo struct {
@@ -25,16 +26,75 @@ type lsResponse struct {

func runRepo(args []string) error {
if len(args) == 0 {
- return fmt.Errorf("usage: okg repo list")
+ return fmt.Errorf(
+ "usage: okg repo <list|create>")
}
switch args[0] {
case "list":
return runRepoList(args[1:])
+ case "create":
+ return runRepoCreate(args[1:])
default:
- return fmt.Errorf("unknown repo command: %s", args[0])
+ return fmt.Errorf(
+ "unknown repo command: %s", args[0])
}
}

+type createRepoRequest struct {
+ RepoName string `json:"repo_name"`
+ ReaderUsername string `json:"reader_username"`
+}
+
+func runRepoCreate(args []string) error {
+ reader := ""
+ name := ""
+ for i := 0; i < len(args); i++ {
+ switch args[i] {
+ case "--reader":
+ i++
+ if i >= len(args) {
+ return fmt.Errorf("--reader requires a value")
+ }
+ reader = args[i]
+ default:
+ if strings.HasPrefix(args[i], "-") {
+ return fmt.Errorf(
+ "unknown flag: %s", args[i])
+ }
+ if name != "" {
+ return fmt.Errorf(
+ "unexpected argument: %s", args[i])
+ }
+ name = args[i]
+ }
+ }
+ if name == "" {
+ return fmt.Errorf(
+ "usage: okg repo create NAME [--reader USER]")
+ }
+
+ cfg, err := loadConfig()
+ if err != nil {
+ return err
+ }
+ cl := newClient(cfg)
+
+ req := createRepoRequest{
+ RepoName: name,
+ ReaderUsername: reader,
+ }
+ err = cl.postJSON("/.add-repo", req, nil)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("Created repo %s\n", name)
+ if reader != "" {
+ fmt.Printf(" reader: %s\n", reader)
+ }
+ fmt.Printf(" url: %s/%s\n", cfg.Host, name)
+ return nil
+}
+
func runRepoList(args []string) error {
as_json := false
for _, a := range args {
a/main.go
b/main.go
1
package main
1
package main
2
2
3
import "fmt"
3
import "fmt"
4
import "os"
4
import "os"
5
5
6
func main() {
6
func main() {
7
args := os.Args[1:]
7
args := os.Args[1:]
8
if len(args) == 0 {
8
if len(args) == 0 {
9
printUsage()
9
printUsage()
10
os.Exit(1)
10
os.Exit(1)
11
}
11
}
12
12
13
var err error
13
var err error
14
switch args[0] {
14
switch args[0] {
15
case "pr":
15
case "pr":
16
err = runPR(args[1:])
16
err = runPR(args[1:])
17
case "repo":
17
case "repo":
18
err = runRepo(args[1:])
18
err = runRepo(args[1:])
19
case "auth":
19
case "auth":
20
err = runAuth(args[1:])
20
err = runAuth(args[1:])
21
case "help", "--help", "-h":
21
case "help", "--help", "-h":
22
printUsage()
22
printUsage()
23
return
23
return
24
default:
24
default:
25
fmt.Fprintf(os.Stderr, "unknown command: %s\n", args[0])
25
fmt.Fprintf(os.Stderr, "unknown command: %s\n", args[0])
26
printUsage()
26
printUsage()
27
os.Exit(1)
27
os.Exit(1)
28
}
28
}
29
29
30
if err != nil {
30
if err != nil {
31
fmt.Fprintf(os.Stderr, "error: %v\n", err)
31
fmt.Fprintf(os.Stderr, "error: %v\n", err)
32
os.Exit(1)
32
os.Exit(1)
33
}
33
}
34
}
34
}
35
35
36
func printUsage() {
36
func printUsage() {
37
fmt.Fprintf(os.Stderr, `okg — Oscar Kilo Git CLI
37
fmt.Fprintf(os.Stderr, `okg — Oscar Kilo Git CLI
38
38
39
Usage:
39
Usage:
40
okg pr list [--state open|closed] [--json]
40
okg pr list [--state open|closed] [--json]
41
okg pr create --head BRANCH [--base master] \
41
okg pr create --head BRANCH [--base master] \
42
--title TITLE [--body BODY] [--json]
42
--title TITLE [--body BODY] [--json]
43
okg pr view NUMBER [--json]
43
okg pr view NUMBER [--json]
44
okg pr diff NUMBER
44
okg pr diff NUMBER
45
okg pr comment NUMBER --body BODY \
45
okg pr comment NUMBER --body BODY \
46
[--approve | --request-changes]
46
[--approve | --request-changes]
47
okg pr merge NUMBER [--json]
47
okg pr merge NUMBER [--json]
48
okg pr close NUMBER [--json]
48
okg pr close NUMBER [--json]
49
okg pr reopen NUMBER [--json]
49
okg pr reopen NUMBER [--json]
50
okg repo create NAME [--reader USER]
50
okg repo list [--json]
51
okg repo list [--json]
51
okg auth login [--host HOST] [--user USERNAME]
52
okg auth login [--host HOST] [--user USERNAME]
52
53
53
Flags:
54
Flags:
54
--repo REPO Override auto-detected repo name
55
--repo REPO Override auto-detected repo name
55
--json Output raw JSON
56
--json Output raw JSON
56
`)
57
`)
57
}
58
}
a/okg_test.go
b/okg_test.go
1
package main
1
package main
2
2
3
import "encoding/json"
4
import "net/http"
5
import "net/http/httptest"
3
import "os"
6
import "os"
7
import "strings"
4
import "testing"
8
import "testing"
5
import "time"
9
import "time"
6
10
7
func TestRepoRegex(t *testing.T) {
11
func TestRepoRegex(t *testing.T) {
8
check := func(url, want string) {
12
check := func(url, want string) {
9
t.Helper()
13
t.Helper()
10
m := repoRegex.FindStringSubmatch(url)
14
m := repoRegex.FindStringSubmatch(url)
11
if want == "" {
15
if want == "" {
12
if m != nil {
16
if m != nil {
13
t.Errorf("%q: want no match, got %q", url, m[1])
17
t.Errorf("%q: want no match, got %q", url, m[1])
14
}
18
}
15
return
19
return
16
}
20
}
17
if m == nil {
21
if m == nil {
18
t.Errorf("%q: want %q, got no match", url, want)
22
t.Errorf("%q: want %q, got no match", url, want)
19
return
23
return
20
}
24
}
21
if m[1] != want {
25
if m[1] != want {
22
t.Errorf("%q: want %q, got %q", url, want, m[1])
26
t.Errorf("%q: want %q, got %q", url, want, m[1])
23
}
27
}
24
}
28
}
25
check("https://code.oscarkilo.com/widget.git", "widget")
29
check("https://code.oscarkilo.com/widget.git", "widget")
26
check("https://code.oscarkilo.com/klee.git", "klee")
30
check("https://code.oscarkilo.com/klee.git", "klee")
27
check("https://code.oscarkilo.com/my-repo.git", "my-repo")
31
check("https://code.oscarkilo.com/my-repo.git", "my-repo")
28
check("https://code.oscarkilo.com/a123.git", "a123")
32
check("https://code.oscarkilo.com/a123.git", "a123")
29
check("https://github.com/foo/bar.git", "")
33
check("https://github.com/foo/bar.git", "")
30
check("not-a-url", "")
34
check("not-a-url", "")
31
}
35
}
32
36
33
func TestResolveRepo(t *testing.T) {
37
func TestResolveRepo(t *testing.T) {
34
// Flag takes priority.
38
// Flag takes priority.
35
repo, err := resolveRepo("from-flag")
39
repo, err := resolveRepo("from-flag")
36
if err != nil {
40
if err != nil {
37
t.Fatal(err)
41
t.Fatal(err)
38
}
42
}
39
if repo != "from-flag" {
43
if repo != "from-flag" {
40
t.Errorf("want from-flag, got %q", repo)
44
t.Errorf("want from-flag, got %q", repo)
41
}
45
}
42
46
43
// Env var takes priority over detection.
47
// Env var takes priority over detection.
44
os.Setenv("OKG_REPO", "from-env")
48
os.Setenv("OKG_REPO", "from-env")
45
defer os.Unsetenv("OKG_REPO")
49
defer os.Unsetenv("OKG_REPO")
46
repo, err = resolveRepo("")
50
repo, err = resolveRepo("")
47
if err != nil {
51
if err != nil {
48
t.Fatal(err)
52
t.Fatal(err)
49
}
53
}
50
if repo != "from-env" {
54
if repo != "from-env" {
51
t.Errorf("want from-env, got %q", repo)
55
t.Errorf("want from-env, got %q", repo)
52
}
56
}
53
}
57
}
54
58
55
func TestParsePRFlags(t *testing.T) {
59
func TestParsePRFlags(t *testing.T) {
56
f, rest, err := parsePRFlags([]string{
60
f, rest, err := parsePRFlags([]string{
57
"--repo", "widget", "--json", "42",
61
"--repo", "widget", "--json", "42",
58
})
62
})
59
if err != nil {
63
if err != nil {
60
t.Fatal(err)
64
t.Fatal(err)
61
}
65
}
62
if f.repo != "widget" {
66
if f.repo != "widget" {
63
t.Errorf("repo: want widget, got %q", f.repo)
67
t.Errorf("repo: want widget, got %q", f.repo)
64
}
68
}
65
if !f.asJSON {
69
if !f.asJSON {
66
t.Error("asJSON: want true")
70
t.Error("asJSON: want true")
67
}
71
}
68
if len(rest) != 1 || rest[0] != "42" {
72
if len(rest) != 1 || rest[0] != "42" {
69
t.Errorf("rest: want [42], got %v", rest)
73
t.Errorf("rest: want [42], got %v", rest)
70
}
74
}
71
}
75
}
72
76
73
func TestParsePRFlagsEmpty(t *testing.T) {
77
func TestParsePRFlagsEmpty(t *testing.T) {
74
f, rest, err := parsePRFlags(nil)
78
f, rest, err := parsePRFlags(nil)
75
if err != nil {
79
if err != nil {
76
t.Fatal(err)
80
t.Fatal(err)
77
}
81
}
78
if f.repo != "" {
82
if f.repo != "" {
79
t.Errorf("repo: want empty, got %q", f.repo)
83
t.Errorf("repo: want empty, got %q", f.repo)
80
}
84
}
81
if f.asJSON {
85
if f.asJSON {
82
t.Error("asJSON: want false")
86
t.Error("asJSON: want false")
83
}
87
}
84
if len(rest) != 0 {
88
if len(rest) != 0 {
85
t.Errorf("rest: want empty, got %v", rest)
89
t.Errorf("rest: want empty, got %v", rest)
86
}
90
}
87
}
91
}
88
92
89
func TestParsePRFlagsMissingValue(t *testing.T) {
93
func TestParsePRFlagsMissingValue(t *testing.T) {
90
_, _, err := parsePRFlags([]string{"--repo"})
94
_, _, err := parsePRFlags([]string{"--repo"})
91
if err == nil {
95
if err == nil {
92
t.Error("want error for --repo without value")
96
t.Error("want error for --repo without value")
93
}
97
}
94
}
98
}
95
99
96
func TestAge(t *testing.T) {
100
func TestAge(t *testing.T) {
97
check := func(d time.Duration, want string) {
101
check := func(d time.Duration, want string) {
98
t.Helper()
102
t.Helper()
99
got := age(time.Now().Add(-d))
103
got := age(time.Now().Add(-d))
100
if got != want {
104
if got != want {
101
t.Errorf("age(-%v): want %q, got %q", d, want, got)
105
t.Errorf("age(-%v): want %q, got %q", d, want, got)
102
}
106
}
103
}
107
}
104
check(30*time.Second, "just now")
108
check(30*time.Second, "just now")
105
check(5*time.Minute, "5m")
109
check(5*time.Minute, "5m")
106
check(3*time.Hour, "3h")
110
check(3*time.Hour, "3h")
107
check(48*time.Hour, "2d")
111
check(48*time.Hour, "2d")
108
}
112
}
109
113
110
func TestConfigEnvOverrides(t *testing.T) {
114
func TestConfigEnvOverrides(t *testing.T) {
111
os.Setenv("OKG_HOST", "http://test:1234")
115
os.Setenv("OKG_HOST", "http://test:1234")
112
os.Setenv("KLEX_API_KEY", "env-key")
116
os.Setenv("KLEX_API_KEY", "env-key")
113
defer os.Unsetenv("OKG_HOST")
117
defer os.Unsetenv("OKG_HOST")
114
defer os.Unsetenv("KLEX_API_KEY")
118
defer os.Unsetenv("KLEX_API_KEY")
115
119
116
cfg, err := loadConfig()
120
cfg, err := loadConfig()
117
if err != nil {
121
if err != nil {
118
t.Fatal(err)
122
t.Fatal(err)
119
}
123
}
120
if cfg.Host != "http://test:1234" {
124
if cfg.Host != "http://test:1234" {
121
t.Errorf("Host: want http://test:1234, got %q", cfg.Host)
125
t.Errorf("Host: want http://test:1234, got %q", cfg.Host)
122
}
126
}
123
if cfg.ApiKey != "env-key" {
127
if cfg.ApiKey != "env-key" {
124
t.Errorf("ApiKey: want env-key, got %q", cfg.ApiKey)
128
t.Errorf("ApiKey: want env-key, got %q", cfg.ApiKey)
125
}
129
}
126
}
130
}
127
131
128
func TestConfigDefaultHost(t *testing.T) {
132
func TestConfigDefaultHost(t *testing.T) {
129
os.Unsetenv("OKG_HOST")
133
os.Unsetenv("OKG_HOST")
130
os.Unsetenv("KLEX_API_KEY")
134
os.Unsetenv("KLEX_API_KEY")
131
cfg, err := loadConfig()
135
cfg, err := loadConfig()
132
if err != nil {
136
if err != nil {
133
t.Fatal(err)
137
t.Fatal(err)
134
}
138
}
135
if cfg.Host != "http://localhost:42069" {
139
if cfg.Host != "http://localhost:42069" {
136
t.Errorf(
140
t.Errorf(
137
"Host: want http://localhost:42069, got %q",
141
"Host: want http://localhost:42069, got %q",
138
cfg.Host)
142
cfg.Host)
139
}
143
}
140
}
144
}
145
146
func TestRunRepoCreateArgs(t *testing.T) {
147
// Mock server that accepts /.add-repo.
148
mock := newMockKlee(t, 204, "")
149
defer mock.Close()
150
151
os.Setenv("OKG_HOST", mock.URL)
152
os.Setenv("KLEX_API_KEY", "test-key")
153
defer os.Unsetenv("OKG_HOST")
154
defer os.Unsetenv("KLEX_API_KEY")
155
156
err := runRepoCreate([]string{
157
"my-repo", "--reader", "igor.agents",
158
})
159
if err != nil {
160
t.Fatal(err)
161
}
162
163
if mock.req.RepoName != "my-repo" {
164
t.Errorf("repo_name: want my-repo, got %q",
165
mock.req.RepoName)
166
}
167
if mock.req.ReaderUsername != "igor.agents" {
168
t.Errorf("reader: want igor.agents, got %q",
169
mock.req.ReaderUsername)
170
}
171
}
172
173
func TestRunRepoCreateNoReader(t *testing.T) {
174
mock := newMockKlee(t, 204, "")
175
defer mock.Close()
176
177
os.Setenv("OKG_HOST", mock.URL)
178
os.Setenv("KLEX_API_KEY", "test-key")
179
defer os.Unsetenv("OKG_HOST")
180
defer os.Unsetenv("KLEX_API_KEY")
181
182
err := runRepoCreate([]string{"my-repo"})
183
if err != nil {
184
t.Fatal(err)
185
}
186
187
if mock.req.ReaderUsername != "" {
188
t.Errorf("reader: want empty, got %q",
189
mock.req.ReaderUsername)
190
}
191
}
192
193
func TestRunRepoCreateMissingName(t *testing.T) {
194
err := runRepoCreate(nil)
195
if err == nil {
196
t.Fatal("want error for missing name")
197
}
198
}
199
200
func TestRunRepoCreateReaderMissingValue(t *testing.T) {
201
err := runRepoCreate([]string{
202
"my-repo", "--reader",
203
})
204
if err == nil {
205
t.Fatal("want error for --reader without value")
206
}
207
}
208
209
func TestRunRepoCreateUnknownFlag(t *testing.T) {
210
err := runRepoCreate([]string{
211
"my-repo", "--bogus",
212
})
213
if err == nil {
214
t.Fatal("want error for unknown flag")
215
}
216
}
217
218
func TestRunRepoCreateDuplicateName(t *testing.T) {
219
err := runRepoCreate([]string{
220
"my-repo", "extra",
221
})
222
if err == nil {
223
t.Fatal("want error for extra positional arg")
224
}
225
}
226
227
func TestRunRepoCreateServerError(t *testing.T) {
228
mock := newMockKlee(t, 403,
229
"you are not an owner of "+
230
"klee://code.oscarkilo.com/.new-repo")
231
defer mock.Close()
232
233
os.Setenv("OKG_HOST", mock.URL)
234
os.Setenv("KLEX_API_KEY", "test-key")
235
defer os.Unsetenv("OKG_HOST")
236
defer os.Unsetenv("KLEX_API_KEY")
237
238
err := runRepoCreate([]string{"my-repo"})
239
if err == nil {
240
t.Fatal("want error on 403")
241
}
242
if !strings.Contains(err.Error(), "403") {
243
t.Errorf("want 403 in error, got %q", err)
244
}
245
}
246
247
// mockKlee captures the /.add-repo request body.
248
type mockKlee struct {
249
*httptest.Server
250
req createRepoRequest
251
}
252
253
func newMockKlee(
254
t *testing.T, status int, body string,
255
) *mockKlee {
256
t.Helper()
257
mk := &mockKlee{}
258
mk.Server = httptest.NewServer(
259
http.HandlerFunc(func(
260
w http.ResponseWriter, r *http.Request,
261
) {
262
json.NewDecoder(r.Body).Decode(&mk.req)
263
w.WriteHeader(status)
264
if body != "" {
265
w.Write([]byte(body))
266
}
267
}))
268
return mk
269
}
a/repo.go
b/repo.go
1
package main
1
package main
2
2
3
import "encoding/json"
3
import "encoding/json"
4
import "fmt"
4
import "fmt"
5
import "os"
5
import "os"
6
import "strings"
6
import "text/tabwriter"
7
import "text/tabwriter"
7
8
8
type repoInfo struct {
9
type repoInfo struct {
9
Name string `json:"name"`
10
Name string `json:"name"`
10
IsPublic bool `json:"is_public"`
11
IsPublic bool `json:"is_public"`
11
Authz *repoInfoAuthz `json:"authz"`
12
Authz *repoInfoAuthz `json:"authz"`
12
}
13
}
13
14
14
type repoInfoAuthz struct {
15
type repoInfoAuthz struct {
15
IsOwner bool `json:"is_owner"`
16
IsOwner bool `json:"is_owner"`
16
IsReader bool `json:"is_reader"`
17
IsReader bool `json:"is_reader"`
17
OwnerUsername string `json:"owner_username"`
18
OwnerUsername string `json:"owner_username"`
18
ReaderUsername string `json:"reader_username"`
19
ReaderUsername string `json:"reader_username"`
19
}
20
}
20
21
21
type lsResponse struct {
22
type lsResponse struct {
22
Repos []repoInfo `json:"repos"`
23
Repos []repoInfo `json:"repos"`
23
CanCreateRepos bool `json:"can_create_repos"`
24
CanCreateRepos bool `json:"can_create_repos"`
24
}
25
}
25
26
26
func runRepo(args []string) error {
27
func runRepo(args []string) error {
27
if len(args) == 0 {
28
if len(args) == 0 {
28
return fmt.Errorf("usage: okg repo list")
29
return fmt.Errorf(
30
"usage: okg repo <list|create>")
29
}
31
}
30
switch args[0] {
32
switch args[0] {
31
case "list":
33
case "list":
32
return runRepoList(args[1:])
34
return runRepoList(args[1:])
35
case "create":
36
return runRepoCreate(args[1:])
33
default:
37
default:
34
return fmt.Errorf("unknown repo command: %s", args[0])
38
return fmt.Errorf(
39
"unknown repo command: %s", args[0])
35
}
40
}
36
}
41
}
37
42
43
type createRepoRequest struct {
44
RepoName string `json:"repo_name"`
45
ReaderUsername string `json:"reader_username"`
46
}
47
48
func runRepoCreate(args []string) error {
49
reader := ""
50
name := ""
51
for i := 0; i < len(args); i++ {
52
switch args[i] {
53
case "--reader":
54
i++
55
if i >= len(args) {
56
return fmt.Errorf("--reader requires a value")
57
}
58
reader = args[i]
59
default:
60
if strings.HasPrefix(args[i], "-") {
61
return fmt.Errorf(
62
"unknown flag: %s", args[i])
63
}
64
if name != "" {
65
return fmt.Errorf(
66
"unexpected argument: %s", args[i])
67
}
68
name = args[i]
69
}
70
}
71
if name == "" {
72
return fmt.Errorf(
73
"usage: okg repo create NAME [--reader USER]")
74
}
75
76
cfg, err := loadConfig()
77
if err != nil {
78
return err
79
}
80
cl := newClient(cfg)
81
82
req := createRepoRequest{
83
RepoName: name,
84
ReaderUsername: reader,
85
}
86
err = cl.postJSON("/.add-repo", req, nil)
87
if err != nil {
88
return err
89
}
90
fmt.Printf("Created repo %s\n", name)
91
if reader != "" {
92
fmt.Printf(" reader: %s\n", reader)
93
}
94
fmt.Printf(" url: %s/%s\n", cfg.Host, name)
95
return nil
96
}
97
38
func runRepoList(args []string) error {
98
func runRepoList(args []string) error {
39
as_json := false
99
as_json := false
40
for _, a := range args {
100
for _, a := range args {
41
if a == "--json" {
101
if a == "--json" {
42
as_json = true
102
as_json = true
43
}
103
}
44
}
104
}
45
105
46
cfg, err := loadConfig()
106
cfg, err := loadConfig()
47
if err != nil {
107
if err != nil {
48
return err
108
return err
49
}
109
}
50
cl := newClient(cfg)
110
cl := newClient(cfg)
51
111
52
var res lsResponse
112
var res lsResponse
53
if err := cl.getJSON("/.ls", &res); err != nil {
113
if err := cl.getJSON("/.ls", &res); err != nil {
54
return err
114
return err
55
}
115
}
56
116
57
if as_json {
117
if as_json {
58
enc := json.NewEncoder(os.Stdout)
118
enc := json.NewEncoder(os.Stdout)
59
enc.SetIndent("", " ")
119
enc.SetIndent("", " ")
60
return enc.Encode(res)
120
return enc.Encode(res)
61
}
121
}
62
122
63
tw := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
123
tw := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
64
fmt.Fprintln(tw, "NAME\tOWNER\tREADER")
124
fmt.Fprintln(tw, "NAME\tOWNER\tREADER")
65
for _, r := range res.Repos {
125
for _, r := range res.Repos {
66
owner := ""
126
owner := ""
67
reader := ""
127
reader := ""
68
if r.Authz != nil {
128
if r.Authz != nil {
69
owner = r.Authz.OwnerUsername
129
owner = r.Authz.OwnerUsername
70
reader = r.Authz.ReaderUsername
130
reader = r.Authz.ReaderUsername
71
}
131
}
72
fmt.Fprintf(tw, "%s\t%s\t%s\n", r.Name, owner, reader)
132
fmt.Fprintf(tw, "%s\t%s\t%s\n", r.Name, owner, reader)
73
}
133
}
74
return tw.Flush()
134
return tw.Flush()
75
}
135
}