1
32 package ctx
33
34 import (
35 "bytes"
36 "context"
37 "encoding/base64"
38 "fmt"
39
40 "golang.conradwood.net/apis/auth"
41 ge "golang.conradwood.net/apis/goeasyops"
42 "golang.conradwood.net/go-easyops/cmdline"
43 "golang.conradwood.net/go-easyops/common"
44 "golang.conradwood.net/go-easyops/ctx/ctxv2"
45 "golang.conradwood.net/go-easyops/ctx/shared"
46 "golang.conradwood.net/go-easyops/utils"
47
48
49 "strings"
50 "time"
51
52 "google.golang.org/grpc/metadata"
53 )
54
55 const (
56 SER_PREFIX_STR = "CTX_SER_STRING"
57 )
58
59 var (
60 SER_PREFIX_BYT = []byte("CTX_SER_BYTE")
61 )
62
63
64 func NewContextBuilder() shared.ContextBuilder {
65 i := cmdline.GetContextBuilderVersion()
66 if i == 1 {
67 panic("obsolete codepath")
68 } else if i == 2 {
69 return ctxv2.NewContextBuilder()
70 } else {
71
72 return ctxv2.NewContextBuilder()
73 }
74 }
75
76
77 func GetLocalState(ctx context.Context) shared.LocalState {
78 return shared.GetLocalState(ctx)
79 }
80
81
82 func getAllContextBuilders() map[int]shared.ContextBuilder {
83 return map[int]shared.ContextBuilder{
84 2: ctxv2.NewContextBuilder(),
85 }
86 }
87
88
92 func Inbound2Outbound(in_ctx context.Context, local_service *auth.SignedUser) context.Context {
93 for version, cb := range getAllContextBuilders() {
94 octx, found := cb.Inbound2Outbound(in_ctx, local_service)
95 if found {
96 svc := common.VerifySignedUser(local_service)
97 svs := "[none]"
98 if svc != nil {
99 svs = fmt.Sprintf("%s (%s)", svc.ID, svc.Email)
100 }
101 cmdline.DebugfContext("converted inbound (version=%d) to outbound context (me.service=%s)", version, svs)
102 cmdline.DebugfContext("New Context: %s", Context2String(octx))
103 ls := GetLocalState(octx)
104 if ls == nil || shared.IsEmptyLocalState(ls) {
105 utils.PrintStack("[go-easyops] no localstate for newly created context")
106 return nil
107 }
108 cmdline.DebugfContext("Localstate %s: %#v\n", ls.Info(), ls)
109 cmdline.DebugfContext("Localstate Detail:\n%s\n", shared.LocalState2string(ls))
110
111 return octx
112 }
113 }
114 cmdline.DebugfContext("[go-easyops] could not parse inbound context!")
115 return in_ctx
116 }
117
118 func add_context_to_builder(cb shared.ContextBuilder, ctx context.Context) {
119 ls := GetLocalState(ctx)
120 cb.WithCreatorService(ls.CreatorService())
121 if ls.Debug() {
122 cb.WithDebug()
123 }
124 cb.WithRequestID(ls.RequestID())
125 if ls.Trace() {
126 cb.WithTrace()
127 }
128 for _, e := range ls.Experiments() {
129 cb.EnableExperiment(e.Name)
130 }
131 cb.WithUser(ls.User())
132 cb.WithSession(ls.Session())
133 }
134
135
136 func getMetadataFromContext(ctx context.Context) (string, int, int) {
137 source := 1
138 md, ex := metadata.FromIncomingContext(ctx)
139 if !ex {
140 source = 2
141 md, ex = metadata.FromOutgoingContext(ctx)
142 if !ex {
143
144 return "", 0, 0
145 }
146 }
147
148 mdas, fd := md[ctxv2.METANAME]
149 if fd {
150 if len(mdas) != 1 {
151 return "", source, 2
152 }
153 return mdas[0], source, 2
154 }
155
156 return "", 0, 0
157 }
158 func shortSessionText(ls shared.LocalState, maxlen int) string {
159 s := ls.Session()
160 if s == nil {
161 return "nosession"
162 }
163 sl := s.SessionID
164 if len(sl) > maxlen {
165 sl = sl[:maxlen]
166 }
167 return sl
168 }
169
170 func Context2DetailString(ctx context.Context) string {
171 return "TODO context_builder.go"
172 }
173
174
175 func Context2String(ctx context.Context) string {
176 md, src, version := getMetadataFromContext(ctx)
177
178 ls := GetLocalState(ctx)
179 if ls == nil || shared.IsEmptyLocalState(ls) {
180 return fmt.Sprintf("[no localstate] md[src=%d,version=%d]", src, version)
181 }
182 if ls.User() != nil || ls.CallingService() != nil {
183 sesstxt := shortSessionText(ls, 20)
184 return fmt.Sprintf("Localstate[userid=%s,callingservice=%s,session=%s] md[src=%d,version=%d]", shared.PrettyUser(ls.User()), shared.PrettyUser(ls.CallingService()), sesstxt, src, version)
185 }
186 if src == 0 {
187 return fmt.Sprintf("no localstate, no metadata (%v)\n", ctx)
188 }
189 if version == 2 {
190 res := &ge.InContext{}
191 err := utils.Unmarshal(md, res)
192 if err != nil {
193 return fmt.Sprintf("v2 %d metadata invalid (%s)", src, err)
194 }
195 return fmt.Sprintf("v2 (%d) metadata: %#v %#v\n,ls=[%s]", src, res.ImCtx, res.MCtx, shared.LocalState2string(ls))
196 } else if version == 1 {
197 panic("unsupported context version")
198 }
199 return fmt.Sprintf("Unsupported metadata version %d\n", version)
200
201 }
202
203
204 func IsSerialisedByBuilder(buf []byte) bool {
205 if len(buf) < 2 {
206 return false
207 }
208 if strings.HasPrefix(string(buf), SER_PREFIX_STR) {
209
210 return true
211 }
212 if bytes.HasPrefix(buf, SER_PREFIX_BYT) {
213 return true
214 }
215
229 cmdline.DebugfContext("[go-easyops] Not a ctxbuilder context (%s)", utils.HexStr(buf))
230
231
232 return false
233 }
234
235
236 func SerialiseContext(ctx context.Context) ([]byte, error) {
237 if !IsContextFromBuilder(ctx) {
238 utils.PrintStack("incompatible context")
239 return nil, fmt.Errorf("cannot serialise a context which was not built by builder")
240 }
241 version := byte(2)
242 b, err := ctxv2.Serialise(ctx)
243
244 if err != nil {
245 return nil, err
246 }
247 chk := shared.Checksum(b)
248 b = append([]byte{version, chk}, b...)
249 b = append(SER_PREFIX_BYT, b...)
250 return b, nil
251 }
252
253
254 func SerialiseContextToString(ctx context.Context) (string, error) {
255 b, err := SerialiseContext(ctx)
256 if err != nil {
257 return "", err
258 }
259 s := base64.StdEncoding.EncodeToString(b)
260 s = SER_PREFIX_STR + s
261 return s, nil
262 }
263
264
265 func DeserialiseContextFromString(s string) (context.Context, error) {
266 return DeserialiseContextFromStringWithTimeout(time.Duration(10)*time.Second, s)
267 }
268
269
270 func DeserialiseContext(buf []byte) (context.Context, error) {
271 return DeserialiseContextWithTimeout(time.Duration(10)*time.Second, buf)
272 }
273
274
275 func DeserialiseContextFromStringWithTimeout(t time.Duration, s string) (context.Context, error) {
276 if !strings.HasPrefix(s, SER_PREFIX_STR) {
277 return nil, fmt.Errorf("not a valid string to deserialise into a context")
278 }
279 s = strings.TrimPrefix(s, SER_PREFIX_STR)
280 userdata, err := base64.StdEncoding.DecodeString(s)
281 if err != nil {
282 return nil, err
283 }
284 return DeserialiseContextWithTimeout(t, userdata)
285 }
286
287
288 func DeserialiseContextWithTimeout(t time.Duration, buf []byte) (context.Context, error) {
289 if !IsSerialisedByBuilder(buf) {
290 panic("context not serialised by builder")
291 }
292 if len(buf) < 2 {
293 return nil, fmt.Errorf("invalid byte array to deserialise into a context")
294 }
295 cmdline.DebugfContext("Deserialising %s", utils.HexStr(buf))
296 tbuf := buf[len(SER_PREFIX_BYT):]
297 s := string(buf)
298 if strings.HasPrefix(s, SER_PREFIX_STR) {
299
300 return DeserialiseContextFromStringWithTimeout(t, s)
301 }
302 if !bytes.HasPrefix(buf, SER_PREFIX_BYT) {
303
304 return nil, fmt.Errorf("context does not have ser_prefix_byt (%s)", utils.HexStr(buf))
305 }
306
307 version := tbuf[0]
308 chk := tbuf[1]
309 tbuf = tbuf[2:]
310 c := shared.Checksum(tbuf)
311 if c != chk {
312 cmdline.DebugfContext("ERROR IN CHECKSUM (%d vs %d)", c, chk)
313 }
314 cmdline.DebugfContext("deserialising from version %d\n", version)
315 var err error
316 var res context.Context
317 if version == 1 {
318
319 res, err = ctxv2.DeserialiseContextWithTimeout(t, tbuf)
320 } else if version == 2 {
321 res, err = ctxv2.DeserialiseContextWithTimeout(t, tbuf)
322 } else {
323 cmdline.DebugfContext("a: %s", utils.HexStr(buf))
324 utils.PrintStack("incompatible version %d", version)
325 return nil, fmt.Errorf("(2) attempt to deserialise incompatible version (%d) to context", version)
326 }
327 if err != nil {
328 cmdline.DebugfContext("unable to create context (%s)\n", err)
329 return nil, err
330 }
331 cerr := res.Err()
332 if cerr != nil {
333 if cerr != nil {
334 fmt.Printf("[go-easyops] created faulty context\n")
335 }
336 }
337 cmdline.DebugfContext("Deserialised context: %s\n", Context2String(res))
338 return res, err
339 }
340
341
342 func IsContextFromBuilder(ctx context.Context) bool {
343 if ctx.Value(shared.LOCALSTATENAME) != nil {
344 return true
345 }
346 return false
347 }
348
View as plain text