-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathexample_middleware_test.go
More file actions
107 lines (96 loc) · 3.61 KB
/
Copy pathexample_middleware_test.go
File metadata and controls
107 lines (96 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// This example demonstrates middleware: a wrapper around a command's
// handler, declared once on an ancestor and inherited by every command
// beneath it, for the work every command in a subtree has to do.
package climux
import (
"context"
"fmt"
"os"
"time"
)
// auditFlags is the settings struct the fictional platform team owns. Its
// actor is set from a flag on the root command, and the middleware below
// reads it back at call time rather than being handed a copy at
// registration, so mounting order cannot matter.
var auditFlags struct {
actor string
}
// requireActor refuses to run anything until the caller has said who they
// are. A middleware that returns without calling next stops the
// invocation, which is what makes it an authorization check rather than a
// hook, and Exitf names the exit code the refusal exits with.
func requireActor(next HandlerFunc) HandlerFunc {
return func(ctx context.Context, inv *Invocation) error {
if auditFlags.actor == "" {
return Exitf(ExitCodeUsage, "%s: pass --actor to say who is running this", inv.Cmd.FullName)
}
fmt.Fprintf(inv.Stderr, "audit: %s by %s\n", inv.Cmd.FullName, auditFlags.actor)
return next(ctx, inv)
}
}
// timing reports how long the handler took. Wrapping means the handler's
// error passes through untouched: a middleware that only observes must
// return what it was given.
func timing(next HandlerFunc) HandlerFunc {
return func(ctx context.Context, inv *Invocation) error {
start := time.Now()
err := next(ctx, inv)
_ = time.Since(start) // a real one would report it
fmt.Fprintf(inv.Stderr, "trace: %s finished\n", inv.Cmd.FullName)
return err
}
}
func Example_middleware() {
ctx := context.Background()
// Both wrappers are declared once, on the root. Every handler in the
// tree runs inside them, in the order given here, and neither
// subcommand below knows they exist.
//
// The tree is built per run because one command line is all a tree
// reads: this example shows three, where a program shows one.
newApp := func() *Command {
return NewCommand("fleet", "Operate the fleet").
Middleware(requireActor, timing).
Flags(String(&auditFlags.actor, "actor", "", "Who is running this")).
Subcommands(
NewCommand("restart", "Restart a service").
HandleFunc(func(ctx context.Context, inv *Invocation) error {
fmt.Fprintln(inv.Stdout, "restarted")
return nil
}),
NewCommand("status", "Report what is running").
HandleFunc(func(ctx context.Context, inv *Invocation) error {
fmt.Fprintln(inv.Stdout, "all green")
return nil
}),
)
}
// Both wrappers report on stderr, as does Run when one of them
// refuses, so the example points that stream at stdout to show
// everything in one place. A real program would leave it alone.
toStdout := WithStderr(os.Stdout)
fmt.Println("+ fleet --actor=alice restart")
Run(ctx, newApp(), WithArgs("--actor=alice", "restart"), toStdout)
fmt.Println()
fmt.Println("+ fleet --actor=alice status")
Run(ctx, newApp(), WithArgs("--actor=alice", "status"), toStdout)
// The wrapper refuses before the handler runs, so nothing is
// restarted and the exit code is the one it named.
fmt.Println()
fmt.Println("+ fleet restart")
fmt.Printf("exit code %d\n", Run(ctx, newApp(), WithArgs("restart"), toStdout))
// Output:
// + fleet --actor=alice restart
// audit: fleet restart by alice
// restarted
// trace: fleet restart finished
//
// + fleet --actor=alice status
// audit: fleet status by alice
// all green
// trace: fleet status finished
//
// + fleet restart
// Error: fleet restart: pass --actor to say who is running this
// exit code 2
}