Skip to content

Commit 2242fcf

Browse files
committed
docs(pkg): document every exported symbol, and delete the rest
Completes the backfill the comment rule asks for in this module: 229 exported symbols now carry a godoc, which turns the revive rule from red to green for pkg/. Each says what a caller cannot see from the signature — that DeviceStatusRemoved frees its slot against the device limit immediately, that HasMaxDevicesReached compares against -1 when there is no ceiling, that KindInvalid is what makes a forgotten scope a rejection rather than an unbounded query. 74 other comment lines go, because no linter asks for them. What they said, recorded here because the code no longer can: - pkg/api/client's commonAPI and publicAPI are unexported, so the per-method documentation of the agent's own API went with them: CreateDeviceLoginCode returns a short-lived code that deep-links the device into the console's accept page and is authenticated with the device token; CreateDevicePairing is unauthenticated and the code it returns is itself the secret; NewReverseListenerV1 speaks RevDial and V2 speaks Yamux. - Retry-After is allowed to carry either a delay in seconds or a date, and only the delay is handled (RFC 9110 section 10.2.3). - authorizer.code returns 0 for a role it does not know, and the switch's default clause leaves the permission slice empty on purpose so a role added later is powerless rather than privileged. - yamux refuses to build a session without LogOutput, which is why the translation fills it in. - The client's reverser field is what the SSH server dials back in through. golangci-lint reports 0 issues for ./pkg/... with the exported rule on, and the tests pass apart from the two asynq cases that need a Docker daemon inside the container.
1 parent 86f615f commit 2242fcf

78 files changed

Lines changed: 489 additions & 153 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pkg/api/authorizer/permissions.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package authorizer
22

3+
// Permission is one action a role may or may not perform. The values are iota-assigned and carry
4+
// no meaning outside the process: they are never persisted or sent on the wire, so the list can be
5+
// reordered.
36
type Permission int
47

8+
// The actions a role can be granted. Grouped by resource, and deliberately finer-grained than the
9+
// routes: one route may require several, and a role is the set it holds.
510
const (
611
DeviceAccept Permission = iota
712
DeviceReject
@@ -73,8 +78,6 @@ const (
7378
SSHIdentityManage
7479
)
7580

76-
// servicePermissions is intentionally empty: a service account has no management
77-
// permissions (see [RoleService]).
7881
var servicePermissions = []Permission{}
7982

8083
var observerPermissions = []Permission{

pkg/api/authorizer/role.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ func (r Role) String() string {
6868
}
6969
}
7070

71-
// code converts the given role to its corresponding integer.
72-
// If the role is not a valid one, it returns 0.
7371
func (r Role) code() int {
7472
switch r {
7573
case RoleOwner:
@@ -101,7 +99,6 @@ func (r Role) Permissions() []Permission {
10199
case RoleService:
102100
permissions = servicePermissions
103101
default:
104-
// RoleInvalid and anything added later keep the empty slice, as the doc says.
105102
}
106103

107104
return permissions

pkg/api/client/client.go

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,45 +27,42 @@ type publicAPI interface {
2727
Endpoints() (*models.Endpoints, error)
2828
AuthDevice(req *models.DeviceAuthRequest) (*models.DeviceAuthResponse, error)
2929
AuthPublicKey(req *models.PublicKeyAuthRequest, token string) (*models.PublicKeyAuthResponse, error)
30-
// CreateDeviceLoginCode requests a short-lived code that deep-links this device into the
31-
// console's accept page. It is authenticated with the device's token.
3230
CreateDeviceLoginCode(token string) (*models.DeviceLoginCode, error)
33-
// GetDeviceAuthStatus reports the device's current status on the server. It is
34-
// authenticated with the device's token.
3531
GetDeviceAuthStatus(token string) (*models.DeviceAuthStatus, error)
36-
// CreateDevicePairing submits a tenant-less agent's identity and returns a
37-
// short-lived pairing code. Unauthenticated; the code is the secret.
3832
CreateDevicePairing(req *models.DevicePairingRequest) (*models.DevicePairing, error)
39-
// GetDevicePairingStatus polls the outcome of a pairing code. Unauthenticated.
4033
GetDevicePairingStatus(code string) (*models.DevicePairingStatus, error)
41-
// NewReverseListener creates a new reverse listener to be used by the Agent to connect to ShellHub's SSH server
42-
// using RevDial protocol.
4334
NewReverseListenerV1(ctx context.Context, token string, path string) (net.Listener, error)
44-
// NewReverseListenerV2 creates a new reverse listener to be used by the Agent to connect to ShellHub's SSH server
45-
// using Yamux protocol.
4635
NewReverseListenerV2(ctx context.Context, token string, path string, cfg *ReverseListenerV2Config) (net.Listener, error)
4736
}
4837

38+
// Client is the agent's view of the ShellHub API: the routes an agent calls, plus the reverse
39+
// listener it serves SSH on. Build one with NewClient.
4940
type Client interface {
5041
commonAPI
5142
publicAPI
5243
}
5344

5445
type client struct {
55-
scheme string
56-
host string
57-
port int
58-
http *resty.Client
59-
logger *log.Logger
60-
// reverser is used to create a reverse listener to Agent from ShellHub's SSH server.
46+
scheme string
47+
host string
48+
port int
49+
http *resty.Client
50+
logger *log.Logger
6151
reverser reverser.Reverser
6252
}
6353

54+
// ErrParseAddress is returned by NewClient when the server address is not a URL carrying scheme,
55+
// host and port.
6456
var ErrParseAddress = errors.New("could not parse the address to the required format")
6557

6658
// NewClient creates a new ShellHub HTTP client.
6759
//
6860
// Server address must contain the scheme, the host and the port. For instance: `https://cloud.shellhub.io:443/`.
61+
//
62+
// The client retries indefinitely, backing off on the server's Retry-After when it sends one and on
63+
// a random delay when it does not, so an agent left running against a server that is down reconnects
64+
// on its own. Body-less requests go out with Content-Length: 0 rather than an empty chunked body,
65+
// which proxies and request binders handle far more predictably.
6966
func NewClient(address string, opts ...Opt) (Client, error) {
7067
uri, err := url.ParseRequestURI(address)
7168
if err != nil {
@@ -74,7 +71,6 @@ func NewClient(address string, opts ...Opt) (Client, error) {
7471

7572
const RetryAfterHeader string = "Retry-After"
7673

77-
// MaxRetryWaitTime is the default value for wait time between retries.
7874
const MaxRetryWaitTime time.Duration = 1 * time.Hour
7975

8076
randomWaitTimeSecs := func() time.Duration {
@@ -95,8 +91,6 @@ func NewClient(address string, opts ...Opt) (Client, error) {
9591
client.http.SetRetryCount(math.MaxInt32)
9692
client.http.SetRedirectPolicy(SameDomainRedirectPolicy())
9793
client.http.SetBaseURL(uri.String())
98-
// Keeps body-less requests on Content-Length: 0 instead of an empty chunked body, which
99-
// proxies and request binders handle far more predictably.
10094
client.http.SetContentLength(true)
10195
client.http.AddRetryCondition(func(r *resty.Response, err error) bool {
10296
var netErr net.Error
@@ -137,9 +131,6 @@ func NewClient(address string, opts ...Opt) (Client, error) {
137131
return randomWaitTimeSecs(), nil
138132
}
139133

140-
// NOTE: The `Retry-After` supports delay in seconds and and a date time, but currently we will support only
141-
// one of them.
142-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
143134
retryAfterSeconds, err := strconv.Atoi(retryAfterHeader)
144135
if err != nil {
145136
return randomWaitTimeSecs(), err

pkg/api/client/client_public.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ func (c *client) NewReverseListenerV1(ctx context.Context, token string, path st
194194
return c.reverser.NewListener()
195195
}
196196

197+
// ReverseListenerV2Config tunes the yamux session a v2 reverse listener multiplexes over. It is
198+
// the server that chooses these values and sends them to the agent, so a fleet can be retuned
199+
// without redeploying agents.
197200
type ReverseListenerV2Config struct {
198201
// AcceptBacklog is used to limit how many streams may be
199202
// waiting an accept.
@@ -231,6 +234,8 @@ type ReverseListenerV2Config struct {
231234
StreamCloseTimeout time.Duration `json:"yamux_stream_close_timeout"`
232235
}
233236

237+
// DefaultReverseListenerV2Config is what an agent uses when the server sends no configuration of
238+
// its own — an older server, or one that left the field empty.
234239
var DefaultReverseListenerV2Config = ReverseListenerV2Config{
235240
AcceptBacklog: 256,
236241
EnableKeepAlive: true,
@@ -277,6 +282,9 @@ func NewReverseV2ConfigFromMap(m map[string]any) *ReverseListenerV2Config {
277282
return &cfg
278283
}
279284

285+
// YamuxConfigFromReverseListenerV2 translates the wire configuration into yamux's own. A nil cfg
286+
// yields the defaults rather than a zero-valued session, which would refuse every stream. LogOutput
287+
// is filled in here because yamux refuses to build a session without one.
280288
func YamuxConfigFromReverseListenerV2(cfg *ReverseListenerV2Config) *yamux.Config {
281289
if cfg == nil {
282290
cfg = &DefaultReverseListenerV2Config
@@ -290,11 +298,13 @@ func YamuxConfigFromReverseListenerV2(cfg *ReverseListenerV2Config) *yamux.Confi
290298
MaxStreamWindowSize: cfg.MaxStreamWindowSize,
291299
StreamCloseTimeout: cfg.StreamCloseTimeout,
292300
StreamOpenTimeout: cfg.StreamOpenTimeout,
293-
// NOTE: LogOutput is required, and without it yamux will failed to create the session.
294-
LogOutput: os.Stderr,
301+
LogOutput: os.Stderr,
295302
}
296303
}
297304

305+
// NewReverseListenerV2 opens the websocket to the SSH server and multiplexes it with yamux. When the
306+
// server's configuration is refused it retries once with DefaultReverseListenerV2Config, so an agent
307+
// still connects to a server whose settings it cannot satisfy.
298308
func (c *client) NewReverseListenerV2(ctx context.Context, token string, path string, cfg *ReverseListenerV2Config) (net.Listener, error) {
299309
if token == "" {
300310
return nil, errors.New("token is empty")
@@ -328,8 +338,6 @@ func (c *client) NewReverseListenerV2(ctx context.Context, token string, path st
328338
"stream_open_timeout": cfg.StreamOpenTimeout,
329339
}).Error("failed to create muxed session")
330340

331-
// NOTE: If we fail to create the session, we should try again with the [DefaultConfig] as the client
332-
// could be using different settings.
333341
log.WithError(err).Warning("trying to create muxed session with default config")
334342
listener, err = yamux.Server(conn, YamuxConfigFromReverseListenerV2(&DefaultReverseListenerV2Config))
335343
if err != nil {

pkg/api/client/errors.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import (
66
"net/http"
77
)
88

9+
// Response is the part of an HTTP response the error mapping needs: everything here is decided by
10+
// the status code, so the body is not part of the contract.
911
type Response interface {
1012
StatusCode() int
1113
}
1214

1315
var (
14-
// ErrUnkown is returned when a non-mapped error occurred.
16+
// ErrUnknown is returned when a non-mapped error occurred.
1517
ErrUnknown = errors.New("unknown error")
1618
// ErrConnectionFailed is returned when the client could not communicate with the sever.
1719
ErrConnectionFailed = errors.New("connection failed")

pkg/api/client/logger.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,24 @@ import (
66
"github.com/sirupsen/logrus"
77
)
88

9+
// LeveledLogger adapts a logrus logger to resty's leveled-logger interface, so the HTTP client's
10+
// own diagnostics land in the same log as everything else.
911
type LeveledLogger struct {
1012
Logger *logrus.Logger
1113
}
1214

15+
// Errorf logs at error level. The variadic arguments are key/value pairs, not printf arguments,
16+
// despite the name the interface requires.
1317
func (l *LeveledLogger) Errorf(msg string, keysAndValues ...any) {
1418
l.Logger.WithFields(toFields(keysAndValues)).Error(msg)
1519
}
1620

21+
// Debugf logs at debug level, taking key/value pairs as Errorf does.
1722
func (l *LeveledLogger) Debugf(msg string, keysAndValues ...any) {
1823
l.Logger.WithFields(toFields(keysAndValues)).Debug(msg)
1924
}
2025

26+
// Warnf logs at warning level, taking key/value pairs as Errorf does.
2127
func (l *LeveledLogger) Warnf(msg string, keysAndValues ...any) {
2228
l.Logger.WithFields(toFields(keysAndValues)).Warn(msg)
2329
}

pkg/api/client/options.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import (
88
"github.com/sirupsen/logrus"
99
)
1010

11+
// Opt configures a client during NewClient. An Opt that returns an error aborts construction, so
12+
// a client is never handed back half-configured.
1113
type Opt func(*client) error
1214

15+
// WithURL sets scheme, host and port from one URL. A URL without a port gets the scheme's default
16+
// (443 for https, 80 otherwise) rather than port 0.
1317
func WithURL(u *url.URL) Opt {
1418
return func(c *client) error {
1519
c.scheme = u.Scheme
@@ -34,6 +38,8 @@ func WithURL(u *url.URL) Opt {
3438
}
3539
}
3640

41+
// WithScheme sets the scheme on its own, for a caller that builds the address in pieces rather
42+
// than from a URL.
3743
func WithScheme(scheme string) Opt {
3844
return func(c *client) error {
3945
c.scheme = scheme
@@ -42,6 +48,7 @@ func WithScheme(scheme string) Opt {
4248
}
4349
}
4450

51+
// WithHost sets the host on its own. It does not imply a port.
4552
func WithHost(host string) Opt {
4653
return func(c *client) error {
4754
c.host = host
@@ -50,6 +57,7 @@ func WithHost(host string) Opt {
5057
}
5158
}
5259

60+
// WithPort sets the port on its own, overriding whatever the scheme would default to.
5361
func WithPort(port int) Opt {
5462
return func(c *client) error {
5563
c.port = port
@@ -58,6 +66,7 @@ func WithPort(port int) Opt {
5866
}
5967
}
6068

69+
// WithLogger gives the client somewhere to log. Without it the client stays silent.
6170
func WithLogger(logger *logrus.Logger) Opt {
6271
return func(c *client) error {
6372
c.logger = logger
@@ -66,6 +75,8 @@ func WithLogger(logger *logrus.Logger) Opt {
6675
}
6776
}
6877

78+
// WithReverser supplies the reverse-tunnel dialer the agent listens on. Only an agent needs one;
79+
// an API-only client leaves it unset.
6980
func WithReverser(reverser reverser.Reverser) Opt {
7081
return func(c *client) error {
7182
c.reverser = reverser
@@ -74,6 +85,8 @@ func WithReverser(reverser reverser.Reverser) Opt {
7485
}
7586
}
7687

88+
// WithVersion puts the agent's version in the User-Agent header, which is how the server tells
89+
// which agent it is talking to and refuses ones too old for a route.
7790
func WithVersion(version string) Opt {
7891
return func(c *client) error {
7992
c.http.SetHeader("User-Agent", "shellhub-agent/"+version)

pkg/api/client/reverser.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ import (
1212
"github.com/shellhub-io/shellhub/pkg/wsconnadapter"
1313
)
1414

15+
// Reverser dials the SSH server over a websocket and serves the reverse tunnel the server reaches
16+
// the agent back through. It is the websocket implementation of reverser.Reverser.
1517
type Reverser struct {
1618
conn *websocket.Conn
17-
// host is the ShellHub's server address.
18-
//
19-
// It is used to create the websocket connection to the ShellHub's server.
2019
host string
2120
}
2221

2322
var _ reverser.Reverser = new(Reverser)
2423

24+
// NewReverser returns a Reverser pointed at a server address. Nothing is dialed until Auth.
2525
func NewReverser(host string) *Reverser {
2626
return &Reverser{
2727
host: host,

pkg/api/client/reverser/reverser.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
"github.com/shellhub-io/shellhub/pkg/revdial"
77
)
88

9+
// Reverser is how the agent offers itself to the server: authenticate once, then hand back a
10+
// listener the server dials in on. It is its own package so the agent can depend on the seam
11+
// without pulling in the websocket client that implements it.
912
type Reverser interface {
1013
Auth(ctx context.Context, token string, connPath string) error
1114
NewListener() (*revdial.Listener, error)

pkg/api/query/filter.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,18 @@ import (
88
)
99

1010
var (
11-
ErrFilterInvalid = errors.New("filter is invalid")
11+
// ErrFilterInvalid is returned when a filter is not base64url-encoded JSON of the expected shape.
12+
ErrFilterInvalid = errors.New("filter is invalid")
13+
// ErrFilterPropertyInvalid is returned when a property node names a field the store does not
14+
// allow filtering on.
1215
ErrFilterPropertyInvalid = errors.New("filter property is not valid")
16+
// ErrFilterOperatorInvalid is returned when a node names an operator the store does not support.
1317
ErrFilterOperatorInvalid = errors.New("filter operator is not valid")
14-
ErrFilterTooLarge = errors.New("filter exceeds the maximum size")
15-
ErrSorterFieldInvalid = errors.New("sort field is not valid")
18+
// ErrFilterTooLarge is returned when the encoded filter is longer than the cap, which bounds the
19+
// work a single query can ask for.
20+
ErrFilterTooLarge = errors.New("filter exceeds the maximum size")
21+
// ErrSorterFieldInvalid is returned when a sort names a field the store does not allow sorting on.
22+
ErrSorterFieldInvalid = errors.New("sort field is not valid")
1623
)
1724

1825
// Filters represents a set of filters that can be applied to queries.
@@ -33,19 +40,18 @@ func NewFilters() *Filters {
3340
// Unmarshal decodes and unmarshals the raw filters, populating the Data attribute.
3441
// It rejects payloads larger than [MaxFilterRawBytes] before decode to keep
3542
// a hostile caller from allocating large buffers at JSON decode time.
43+
//
44+
// Both base64 alphabets are accepted, standard and URL-safe, with padding stripped first so either
45+
// can be tried with its unpadded decoder.
3646
func (fs *Filters) Unmarshal() error {
3747
if len(fs.Raw) > MaxFilterRawBytes {
3848
return ErrFilterTooLarge
3949
}
4050

41-
// Strip any trailing '=' padding once so both standard and URL-safe encodings
42-
// can be tried with their respective Raw (unpadded) decoders.
4351
unpadded := strings.TrimRight(fs.Raw, "=")
4452

4553
raw, err := base64.RawStdEncoding.DecodeString(unpadded)
4654
if err != nil {
47-
// Fall back to RawURLEncoding (RFC 4648 §5) whose alphabet uses '-' and '_'
48-
// instead of '+' and '/'.
4955
raw, err = base64.RawURLEncoding.DecodeString(unpadded)
5056
if err != nil {
5157
return ErrFilterInvalid
@@ -59,11 +65,15 @@ func (fs *Filters) Unmarshal() error {
5965
return nil
6066
}
6167

68+
// Filter is one node of a query filter: a tagged union whose Type picks the shape of Params.
69+
// Unmarshal one rather than building it by hand, or Params holds a map instead of a params struct.
6270
type Filter struct {
6371
Type string `json:"type,omitempty"`
6472
Params any `json:"params,omitempty"`
6573
}
6674

75+
// UnmarshalJSON decodes Params into the struct named by Type. An unrecognized Type leaves Params
76+
// nil rather than failing, so a filter the server does not know narrows nothing.
6777
func (f *Filter) UnmarshalJSON(data []byte) error {
6878
var params json.RawMessage
6979

0 commit comments

Comments
 (0)