Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/proxy/plugins/jsonselect/plugin.go
2500 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package jsonselect
6
7
import (
8
"bytes"
9
"fmt"
10
"strings"
11
12
"github.com/buger/jsonparser"
13
"github.com/caddyserver/caddy/v2"
14
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
15
"github.com/caddyserver/caddy/v2/modules/logging"
16
"go.uber.org/zap/buffer"
17
"go.uber.org/zap/zapcore"
18
)
19
20
const (
21
moduleName = "jsonselect"
22
moduleID = "caddy.logging.encoders." + moduleName
23
)
24
25
func init() {
26
caddy.RegisterModule(JSONSelectEncoder{})
27
}
28
29
type JSONSelectEncoder struct {
30
logging.LogEncoderConfig
31
zapcore.Encoder `json:"-"`
32
Selector string `json:"selector,omitempty"`
33
34
getters [][]string
35
setters [][]string
36
}
37
38
func (JSONSelectEncoder) CaddyModule() caddy.ModuleInfo {
39
return caddy.ModuleInfo{
40
ID: moduleID,
41
New: func() caddy.Module {
42
return &JSONSelectEncoder{
43
Encoder: new(logging.JSONEncoder),
44
}
45
},
46
}
47
}
48
49
func (e *JSONSelectEncoder) Provision(ctx caddy.Context) error {
50
if e.Selector == "" {
51
return fmt.Errorf("selector is mandatory")
52
}
53
54
e.setters = [][]string{}
55
e.getters = [][]string{}
56
r := caddy.NewReplacer()
57
r.Map(func(sel string) (interface{}, bool) {
58
var set, get string
59
60
parts := strings.Split(sel, ":")
61
if len(parts) == 1 {
62
set = parts[0]
63
get = set
64
} else if len(parts) == 2 {
65
set = parts[0]
66
get = parts[1]
67
} else {
68
// todo > error out - how?
69
return nil, false
70
}
71
72
e.setters = append(e.setters, strings.Split(set, ">"))
73
e.getters = append(e.getters, strings.Split(get, ">"))
74
return nil, false
75
})
76
r.ReplaceAll(e.Selector, "")
77
78
if len(e.setters) != len(e.getters) {
79
return fmt.Errorf("selector must have the same number of setters and getters")
80
}
81
82
e.Encoder = zapcore.NewJSONEncoder(e.ZapcoreEncoderConfig())
83
return nil
84
}
85
86
func (e JSONSelectEncoder) Clone() zapcore.Encoder {
87
return JSONSelectEncoder{
88
LogEncoderConfig: e.LogEncoderConfig,
89
Encoder: e.Encoder.Clone(),
90
Selector: e.Selector,
91
getters: e.getters,
92
setters: e.setters,
93
}
94
}
95
96
func (e JSONSelectEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
97
buf, err := e.Encoder.EncodeEntry(entry, fields)
98
if err != nil {
99
return buf, err
100
}
101
102
res := []byte{'{', '}'}
103
// Temporary workaround the bug https://github.com/buger/jsonparser/issues/232
104
// TODO(leo): switch back to EachKey (see git history) for perf reasons when fixed
105
for idx, paths := range e.getters {
106
val, typ, _, err := jsonparser.Get(buf.Bytes(), paths...)
107
if err == jsonparser.KeyPathNotFoundError {
108
// path not found, skip
109
continue
110
}
111
if err != nil {
112
return nil, err
113
}
114
switch typ {
115
case jsonparser.NotExist:
116
// path not found, skip
117
case jsonparser.String:
118
res, _ = jsonparser.Set(res, append(append([]byte{'"'}, val...), '"'), e.setters[idx]...)
119
default:
120
res, _ = jsonparser.Set(res, val, e.setters[idx]...)
121
}
122
}
123
124
// Reset the buffer to output our own content
125
buf.Reset()
126
// Insert the new content
127
nl := []byte("\n")
128
if !bytes.HasSuffix(res, nl) {
129
res = append(res, nl...)
130
}
131
buf.Write(res)
132
133
return buf, err
134
}
135
136
// Interface guards
137
var (
138
_ zapcore.Encoder = (*JSONSelectEncoder)(nil)
139
_ caddy.Provisioner = (*JSONSelectEncoder)(nil)
140
_ caddyfile.Unmarshaler = (*JSONSelectEncoder)(nil)
141
)
142
143