mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
Preparing for v1 API
Moving (most) source to v1 root package to prepare for v0/v1 API separation. Signed-off-by: Johan Fylling <johan.dev@fylling.se>
This commit is contained in:
@@ -1,64 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
// logBuffer implements a circular FIFO buffer for the plugin that caps memory
|
||||
// usage at the configured limit. If the buffer size is exceeded, events from
|
||||
// the front of the buffer are dropped.
|
||||
type logBuffer struct {
|
||||
usage int64
|
||||
limit int64
|
||||
l *list.List
|
||||
}
|
||||
|
||||
type logBufferElem struct {
|
||||
bs []byte
|
||||
}
|
||||
|
||||
func newLogBuffer(limit int64) *logBuffer {
|
||||
return &logBuffer{
|
||||
limit: limit,
|
||||
usage: 0,
|
||||
l: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (lb *logBuffer) Push(bs []byte) (dropped int) {
|
||||
size := int64(len(bs))
|
||||
|
||||
if lb.limit > 0 {
|
||||
for elem := lb.l.Front(); elem != nil && (lb.usage+size > lb.limit); elem = elem.Next() {
|
||||
drop := elem.Value.(logBufferElem).bs
|
||||
lb.l.Remove(elem)
|
||||
lb.usage -= int64(len(drop))
|
||||
dropped++
|
||||
}
|
||||
}
|
||||
|
||||
elem := logBufferElem{bs}
|
||||
|
||||
lb.l.PushBack(elem)
|
||||
lb.usage += size
|
||||
return dropped
|
||||
}
|
||||
|
||||
func (lb *logBuffer) Pop() []byte {
|
||||
elem := lb.l.Front()
|
||||
if elem != nil {
|
||||
e := elem.Value.(logBufferElem)
|
||||
lb.usage -= int64(len(e.bs))
|
||||
lb.l.Remove(elem)
|
||||
return e.bs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lb *logBuffer) Len() int {
|
||||
return lb.l.Len()
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogBuffer(t *testing.T) {
|
||||
|
||||
buffer := newLogBuffer(int64(20)) // 20 byte limit for test purposes
|
||||
|
||||
dropped := buffer.Push(make([]byte, 20))
|
||||
if dropped != 0 {
|
||||
t.Fatal("Expected dropped to be zero")
|
||||
}
|
||||
|
||||
bs := buffer.Pop()
|
||||
if len(bs) != 20 {
|
||||
t.Fatal("Expected buffer size to be 20")
|
||||
}
|
||||
|
||||
bs = buffer.Pop()
|
||||
if bs != nil {
|
||||
t.Fatal("Expected buffer to be nil")
|
||||
}
|
||||
|
||||
dropped = buffer.Push(bytes.Repeat([]byte(`1`), 10))
|
||||
if dropped != 0 {
|
||||
t.Fatal("Expected dropped to be zero")
|
||||
}
|
||||
|
||||
dropped = buffer.Push(bytes.Repeat([]byte(`2`), 10))
|
||||
if dropped != 0 {
|
||||
t.Fatal("Expected dropped to be zero")
|
||||
}
|
||||
|
||||
dropped = buffer.Push(bytes.Repeat([]byte(`3`), 10))
|
||||
if dropped != 1 {
|
||||
t.Fatal("Expected dropped to be 1")
|
||||
}
|
||||
|
||||
bs = buffer.Pop()
|
||||
exp := bytes.Repeat([]byte(`2`), 10)
|
||||
if !bytes.Equal(bs, exp) {
|
||||
t.Fatalf("Expected %v but got %v", exp, bs)
|
||||
}
|
||||
|
||||
if buffer.usage != 10 {
|
||||
t.Fatalf("Expected buffer usage to be 10 but got %v", buffer.usage)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/open-policy-agent/opa/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
encHardLimitThreshold = 0.9
|
||||
softLimitBaseFactor = 2
|
||||
softLimitExponentScaleFactor = 0.2
|
||||
encLogExUploadSizeLimitCounterName = "enc_log_exceeded_upload_size_limit_bytes"
|
||||
encSoftLimitScaleUpCounterName = "enc_soft_limit_scale_up"
|
||||
encSoftLimitScaleDownCounterName = "enc_soft_limit_scale_down"
|
||||
encSoftLimitStableCounterName = "enc_soft_limit_stable"
|
||||
)
|
||||
|
||||
// chunkEncoder implements log buffer chunking and compression. Log events are
|
||||
// written to the encoder and the encoder outputs chunks that are fit to the
|
||||
// configured limit.
|
||||
type chunkEncoder struct {
|
||||
limit int64
|
||||
softLimit int64
|
||||
softLimitScaleUpExponent float64
|
||||
softLimitScaleDownExponent float64
|
||||
bytesWritten int
|
||||
buf *bytes.Buffer
|
||||
w *gzip.Writer
|
||||
metrics metrics.Metrics
|
||||
}
|
||||
|
||||
func newChunkEncoder(limit int64) *chunkEncoder {
|
||||
enc := &chunkEncoder{
|
||||
limit: limit,
|
||||
softLimit: limit,
|
||||
softLimitScaleUpExponent: 0,
|
||||
softLimitScaleDownExponent: 0,
|
||||
}
|
||||
enc.update()
|
||||
|
||||
return enc
|
||||
}
|
||||
|
||||
func (enc *chunkEncoder) WithMetrics(m metrics.Metrics) *chunkEncoder {
|
||||
enc.metrics = m
|
||||
return enc
|
||||
}
|
||||
|
||||
func (enc *chunkEncoder) Write(event EventV1) (result [][]byte, err error) {
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(event); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return enc.WriteBytes(buf.Bytes())
|
||||
}
|
||||
|
||||
func (enc *chunkEncoder) WriteBytes(bs []byte) (result [][]byte, err error) {
|
||||
if len(bs) == 0 {
|
||||
return nil, nil
|
||||
} else if int64(len(bs)+2) > enc.limit {
|
||||
if enc.metrics != nil {
|
||||
enc.metrics.Counter(encLogExUploadSizeLimitCounterName).Incr()
|
||||
}
|
||||
return nil, fmt.Errorf("upload chunk size (%d) exceeds upload_size_limit_bytes (%d)",
|
||||
int64(len(bs)+2), enc.limit)
|
||||
}
|
||||
|
||||
if int64(len(bs)+enc.bytesWritten+1) > enc.softLimit {
|
||||
if err := enc.writeClose(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = enc.reset()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if enc.bytesWritten == 0 {
|
||||
n, err := enc.w.Write([]byte(`[`))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc.bytesWritten += n
|
||||
} else {
|
||||
n, err := enc.w.Write([]byte(`,`))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc.bytesWritten += n
|
||||
}
|
||||
|
||||
n, err := enc.w.Write(bs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enc.bytesWritten += n
|
||||
return
|
||||
}
|
||||
|
||||
func (enc *chunkEncoder) writeClose() error {
|
||||
if _, err := enc.w.Write([]byte(`]`)); err != nil {
|
||||
return err
|
||||
}
|
||||
return enc.w.Close()
|
||||
}
|
||||
|
||||
func (enc *chunkEncoder) Flush() ([][]byte, error) {
|
||||
if enc.bytesWritten == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if err := enc.writeClose(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enc.reset()
|
||||
}
|
||||
|
||||
//nolint:unconvert
|
||||
func (enc *chunkEncoder) reset() ([][]byte, error) {
|
||||
|
||||
// Adjust the encoder's soft limit based on the current amount of
|
||||
// data written to the underlying buffer. The soft limit decides when to flush a chunk.
|
||||
// The soft limit is modified based on the below algorithm:
|
||||
// 1) Scale Up: If the current chunk size is within 90% of the user-configured limit, exponentially increase
|
||||
// the soft limit. The exponential function is 2^x where x has a minimum value of 1
|
||||
// 2) Scale Down: If the current chunk size exceeds the hard limit, decrease the soft limit and re-encode the
|
||||
// decisions in the last chunk.
|
||||
// 3) Equilibrium: If the chunk size is between 90% and 100% of the user-configured limit, maintain soft limit value.
|
||||
|
||||
if enc.buf.Len() < int(float64(enc.limit)*encHardLimitThreshold) {
|
||||
if enc.metrics != nil {
|
||||
enc.metrics.Counter(encSoftLimitScaleUpCounterName).Incr()
|
||||
}
|
||||
|
||||
mul := int64(math.Pow(float64(softLimitBaseFactor), float64(enc.softLimitScaleUpExponent+1)))
|
||||
enc.softLimit *= mul
|
||||
enc.softLimitScaleUpExponent += softLimitExponentScaleFactor
|
||||
return enc.update(), nil
|
||||
}
|
||||
|
||||
if int(enc.limit) > enc.buf.Len() && enc.buf.Len() >= int(float64(enc.limit)*encHardLimitThreshold) {
|
||||
if enc.metrics != nil {
|
||||
enc.metrics.Counter(encSoftLimitStableCounterName).Incr()
|
||||
}
|
||||
|
||||
enc.softLimitScaleDownExponent = enc.softLimitScaleUpExponent
|
||||
return enc.update(), nil
|
||||
}
|
||||
|
||||
if enc.softLimit > enc.limit {
|
||||
if enc.metrics != nil {
|
||||
enc.metrics.Counter(encSoftLimitScaleDownCounterName).Incr()
|
||||
}
|
||||
|
||||
if enc.softLimitScaleDownExponent < enc.softLimitScaleUpExponent {
|
||||
enc.softLimitScaleDownExponent = enc.softLimitScaleUpExponent
|
||||
}
|
||||
|
||||
den := int64(math.Pow(float64(softLimitBaseFactor), float64(enc.softLimitScaleDownExponent-enc.softLimitScaleUpExponent+1)))
|
||||
enc.softLimit /= den
|
||||
|
||||
if enc.softLimitScaleUpExponent > 0 {
|
||||
enc.softLimitScaleUpExponent -= softLimitExponentScaleFactor
|
||||
}
|
||||
}
|
||||
|
||||
events, decErr := newChunkDecoder(enc.buf.Bytes()).decode()
|
||||
if decErr != nil {
|
||||
return nil, decErr
|
||||
}
|
||||
|
||||
enc.initialize()
|
||||
|
||||
var result [][]byte
|
||||
for _, event := range events {
|
||||
chunk, err := enc.Write(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if chunk != nil {
|
||||
result = append(result, chunk...)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (enc *chunkEncoder) update() [][]byte {
|
||||
buf := enc.buf
|
||||
enc.initialize()
|
||||
if buf != nil {
|
||||
return [][]byte{buf.Bytes()}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enc *chunkEncoder) initialize() {
|
||||
enc.buf = new(bytes.Buffer)
|
||||
enc.bytesWritten = 0
|
||||
enc.w = gzip.NewWriter(enc.buf)
|
||||
}
|
||||
|
||||
// chunkDecoder decodes the encoded chunks and outputs the log events
|
||||
type chunkDecoder struct {
|
||||
raw []byte
|
||||
}
|
||||
|
||||
func newChunkDecoder(raw []byte) *chunkDecoder {
|
||||
return &chunkDecoder{
|
||||
raw: raw,
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *chunkDecoder) decode() ([]EventV1, error) {
|
||||
gr, err := gzip.NewReader(bytes.NewReader(dec.raw))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var events []EventV1
|
||||
if err := json.NewDecoder(gr).Decode(&events); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gr.Close()
|
||||
|
||||
return events, nil
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/metrics"
|
||||
)
|
||||
|
||||
func TestChunkEncoder(t *testing.T) {
|
||||
|
||||
enc := newChunkEncoder(1000)
|
||||
var result interface{} = false
|
||||
var expInput interface{} = map[string]interface{}{"method": "GET"}
|
||||
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
event := EventV1{
|
||||
Labels: map[string]string{
|
||||
"id": "test-instance-id",
|
||||
"app": "example-app",
|
||||
},
|
||||
Revision: "a",
|
||||
DecisionID: "a",
|
||||
Path: "foo/bar",
|
||||
Input: &expInput,
|
||||
Result: &result,
|
||||
RequestedBy: "test",
|
||||
Timestamp: ts,
|
||||
}
|
||||
|
||||
bs, err := enc.Write(event)
|
||||
if bs != nil || err != nil {
|
||||
t.Fatalf("Unexpected error or chunk produced: err: %v", err)
|
||||
}
|
||||
|
||||
bs, err = enc.Flush()
|
||||
if bs == nil || err != nil {
|
||||
t.Fatalf("Unexpected error or NO chunk produced: err: %v", err)
|
||||
}
|
||||
|
||||
bs, err = enc.Flush()
|
||||
if bs != nil || err != nil {
|
||||
t.Fatalf("Unexpected error chunk produced: err: %v", err)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestChunkEncoderSizeLimit(t *testing.T) {
|
||||
enc := newChunkEncoder(1).WithMetrics(metrics.New())
|
||||
var result interface{} = false
|
||||
var expInput interface{} = map[string]interface{}{"method": "GET"}
|
||||
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
event := EventV1{
|
||||
Labels: map[string]string{
|
||||
"id": "test-instance-id",
|
||||
"app": "example-app",
|
||||
},
|
||||
DecisionID: "123",
|
||||
Path: "foo/bar",
|
||||
Input: &expInput,
|
||||
Result: &result,
|
||||
RequestedBy: "test",
|
||||
Timestamp: ts,
|
||||
}
|
||||
_, err = enc.Write(event)
|
||||
if err == nil {
|
||||
t.Error("Expected error as upload chunk size exceeds configured limit")
|
||||
}
|
||||
expected := "upload chunk size (200) exceeds upload_size_limit_bytes (1)"
|
||||
if err.Error() != expected {
|
||||
t.Errorf("expected: '%s', got: '%s'", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestChunkEncoderAdaptive(t *testing.T) {
|
||||
|
||||
enc := newChunkEncoder(1000).WithMetrics(metrics.New())
|
||||
var result interface{} = false
|
||||
var expInput interface{} = map[string]interface{}{"method": "GET"}
|
||||
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var chunks [][]byte
|
||||
numEvents := 400
|
||||
for i := 0; i < numEvents; i++ {
|
||||
|
||||
bundles := map[string]BundleInfoV1{}
|
||||
bundles["authz"] = BundleInfoV1{Revision: fmt.Sprint(i)}
|
||||
|
||||
event := EventV1{
|
||||
Labels: map[string]string{
|
||||
"id": "test-instance-id",
|
||||
"app": "example-app",
|
||||
},
|
||||
Bundles: bundles,
|
||||
DecisionID: fmt.Sprint(i),
|
||||
Path: "foo/bar",
|
||||
Input: &expInput,
|
||||
Result: &result,
|
||||
RequestedBy: "test",
|
||||
Timestamp: ts,
|
||||
}
|
||||
|
||||
chunk, err := enc.Write(event)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if chunk != nil {
|
||||
chunks = append(chunks, chunk...)
|
||||
}
|
||||
}
|
||||
|
||||
// decode the chunks and check the number of events is equal to the encoded events
|
||||
|
||||
numEventsActual := decodeChunks(t, chunks)
|
||||
|
||||
// flush the encoder
|
||||
for {
|
||||
bs, err := enc.Flush()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(bs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
numEventsActual += decodeChunks(t, bs)
|
||||
}
|
||||
|
||||
if numEvents != numEventsActual {
|
||||
t.Fatalf("Expected %v events but got %v", numEvents, numEventsActual)
|
||||
}
|
||||
|
||||
actualScaleUpEvents := enc.metrics.Counter(encSoftLimitScaleUpCounterName).Value().(uint64)
|
||||
actualScaleDownEvents := enc.metrics.Counter(encSoftLimitScaleDownCounterName).Value().(uint64)
|
||||
actualEquiEvents := enc.metrics.Counter(encSoftLimitStableCounterName).Value().(uint64)
|
||||
|
||||
expectedScaleUpEvents := uint64(8)
|
||||
expectedScaleDownEvents := uint64(3)
|
||||
expectedEquiEvents := uint64(0)
|
||||
|
||||
if actualScaleUpEvents != expectedScaleUpEvents {
|
||||
t.Fatalf("Expected scale up events %v but got %v", expectedScaleUpEvents, actualScaleUpEvents)
|
||||
}
|
||||
|
||||
if actualScaleDownEvents != expectedScaleDownEvents {
|
||||
t.Fatalf("Expected scale down events %v but got %v", expectedScaleDownEvents, actualScaleDownEvents)
|
||||
}
|
||||
|
||||
if actualEquiEvents != expectedEquiEvents {
|
||||
t.Fatalf("Expected equilibrium events %v but got %v", expectedEquiEvents, actualEquiEvents)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeChunks(t *testing.T, bs [][]byte) int {
|
||||
t.Helper()
|
||||
|
||||
numEvents := 0
|
||||
for _, chunk := range bs {
|
||||
events, err := newChunkDecoder(chunk).decode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
numEvents += len(events)
|
||||
}
|
||||
return numEvents
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/deepcopy"
|
||||
)
|
||||
|
||||
type maskOP string
|
||||
|
||||
const (
|
||||
maskOPRemove maskOP = "remove"
|
||||
maskOPUpsert maskOP = "upsert"
|
||||
|
||||
partInput = "input"
|
||||
partResult = "result"
|
||||
partNDBCache = "nd_builtin_cache"
|
||||
)
|
||||
|
||||
var (
|
||||
errMaskInvalidObject = fmt.Errorf("mask upsert invalid object")
|
||||
)
|
||||
|
||||
type maskRule struct {
|
||||
OP maskOP `json:"op"`
|
||||
Path string `json:"path"`
|
||||
Value interface{} `json:"value"`
|
||||
escapedParts []string
|
||||
modifyFullObj bool
|
||||
failUndefinedPath bool
|
||||
}
|
||||
|
||||
type maskRuleSet struct {
|
||||
OnRuleError func(*maskRule, error)
|
||||
Rules []*maskRule
|
||||
resultCopied bool
|
||||
}
|
||||
|
||||
func (r maskRule) String() string {
|
||||
return "/" + strings.Join(r.escapedParts, "/")
|
||||
}
|
||||
|
||||
type maskRuleOption func(*maskRule) error
|
||||
|
||||
func newMaskRule(path string, opts ...maskRuleOption) (*maskRule, error) {
|
||||
const (
|
||||
defaultOP = maskOPRemove
|
||||
defaultFailUndefinedPath = false
|
||||
)
|
||||
|
||||
if len(path) == 0 {
|
||||
return nil, fmt.Errorf("mask must be non-empty")
|
||||
} else if !strings.HasPrefix(path, "/") {
|
||||
return nil, fmt.Errorf("mask must be slash-prefixed")
|
||||
}
|
||||
|
||||
parts := strings.Split(path[1:], "/")
|
||||
|
||||
switch parts[0] {
|
||||
case partInput, partResult, partNDBCache: // OK
|
||||
default:
|
||||
return nil, fmt.Errorf("mask prefix not allowed: %v", parts[0])
|
||||
}
|
||||
|
||||
escapedParts := make([]string, len(parts))
|
||||
for i := range parts {
|
||||
_, err := url.PathUnescape(parts[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
escapedParts[i] = url.PathEscape(parts[i])
|
||||
}
|
||||
|
||||
modifyFullObj := false
|
||||
if len(escapedParts) == 1 {
|
||||
modifyFullObj = true
|
||||
}
|
||||
|
||||
r := &maskRule{
|
||||
OP: defaultOP,
|
||||
Path: path,
|
||||
escapedParts: escapedParts,
|
||||
failUndefinedPath: defaultFailUndefinedPath,
|
||||
modifyFullObj: modifyFullObj,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func withOP(op maskOP) maskRuleOption {
|
||||
return func(r *maskRule) error {
|
||||
switch op {
|
||||
case maskOPRemove, maskOPUpsert:
|
||||
r.OP = op
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("mask op is not supported: %s", op)
|
||||
}
|
||||
}
|
||||
|
||||
func withValue(val interface{}) maskRuleOption {
|
||||
return func(r *maskRule) error {
|
||||
r.Value = val
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func withFailUndefinedPath() maskRuleOption {
|
||||
return func(r *maskRule) error {
|
||||
r.failUndefinedPath = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r maskRule) Mask(event *EventV1) error {
|
||||
|
||||
var maskObj *interface{} // pointer to event Input|Result|NDBCache object
|
||||
var maskObjPtr **interface{} // pointer to the event Input|Result|NDBCache pointer itself
|
||||
|
||||
switch p := r.escapedParts[0]; p {
|
||||
case partInput:
|
||||
if event.Input == nil {
|
||||
if r.failUndefinedPath {
|
||||
return errMaskInvalidObject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
maskObj = event.Input
|
||||
maskObjPtr = &event.Input
|
||||
case partResult:
|
||||
if event.Result == nil {
|
||||
if r.failUndefinedPath {
|
||||
return errMaskInvalidObject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
maskObj = event.Result
|
||||
maskObjPtr = &event.Result
|
||||
case partNDBCache:
|
||||
if event.NDBuiltinCache == nil {
|
||||
if r.failUndefinedPath {
|
||||
return errMaskInvalidObject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
maskObj = event.NDBuiltinCache
|
||||
maskObjPtr = &event.NDBuiltinCache
|
||||
default:
|
||||
return fmt.Errorf("illegal path value: %s", p)
|
||||
}
|
||||
|
||||
switch r.OP {
|
||||
case maskOPRemove:
|
||||
if r.modifyFullObj {
|
||||
*maskObjPtr = nil
|
||||
} else {
|
||||
|
||||
parent, err := r.lookup(r.escapedParts[1:len(r.escapedParts)-1], *maskObj)
|
||||
if err != nil {
|
||||
if err == errMaskInvalidObject && r.failUndefinedPath {
|
||||
return err
|
||||
}
|
||||
}
|
||||
parentObj, ok := parent.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
fld := r.escapedParts[len(r.escapedParts)-1]
|
||||
if _, ok := parentObj[fld]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(parentObj, fld)
|
||||
|
||||
}
|
||||
event.Erased = append(event.Erased, r.String())
|
||||
|
||||
case maskOPUpsert:
|
||||
if r.modifyFullObj {
|
||||
*maskObjPtr = &r.Value
|
||||
} else {
|
||||
inputObj, ok := (*maskObj).(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.mkdirp(inputObj, r.escapedParts[1:len(r.escapedParts)], r.Value); err != nil {
|
||||
if r.failUndefinedPath {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
event.Masked = append(event.Masked, r.String())
|
||||
|
||||
default:
|
||||
return fmt.Errorf("illegal mask op value: %s", r.OP)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r maskRule) lookup(p []string, node interface{}) (interface{}, error) {
|
||||
for i := 0; i < len(p); i++ {
|
||||
switch v := node.(type) {
|
||||
case map[string]interface{}:
|
||||
var ok bool
|
||||
if node, ok = v[p[i]]; !ok {
|
||||
return nil, errMaskInvalidObject
|
||||
}
|
||||
case []interface{}:
|
||||
idx, err := strconv.Atoi(p[i])
|
||||
if err != nil {
|
||||
return nil, errMaskInvalidObject
|
||||
} else if idx < 0 || idx >= len(v) {
|
||||
return nil, errMaskInvalidObject
|
||||
}
|
||||
node = v[idx]
|
||||
default:
|
||||
return nil, errMaskInvalidObject
|
||||
}
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (r maskRule) mkdirp(node map[string]interface{}, path []string, value interface{}) error {
|
||||
if len(path) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// create intermediate nodes
|
||||
for i := 0; i < len(path)-1; i++ {
|
||||
child, ok := node[path[i]]
|
||||
|
||||
if !ok {
|
||||
child := map[string]interface{}{}
|
||||
node[path[i]] = child
|
||||
node = child
|
||||
continue
|
||||
}
|
||||
|
||||
switch obj := child.(type) {
|
||||
case map[string]interface{}:
|
||||
node = obj
|
||||
default:
|
||||
return errMaskInvalidObject
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
node[path[len(path)-1]] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMaskRuleSet(rv interface{}, onRuleError func(*maskRule, error)) (*maskRuleSet, error) {
|
||||
var mRuleSet = &maskRuleSet{
|
||||
OnRuleError: onRuleError,
|
||||
}
|
||||
rawRules, ok := rv.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected rule format %v (%[1]T)", rv)
|
||||
}
|
||||
|
||||
for _, iface := range rawRules {
|
||||
|
||||
switch v := iface.(type) {
|
||||
|
||||
case string:
|
||||
// preserve default behavior of remove when
|
||||
// structured mask format is not provided
|
||||
rule, err := newMaskRule(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mRuleSet.Rules = append(mRuleSet.Rules, rule)
|
||||
|
||||
case map[string]interface{}:
|
||||
rule := &maskRule{}
|
||||
op, set := getString(v, "op")
|
||||
if set && op == "" {
|
||||
return nil, fmt.Errorf("invalid \"op\" value: %v %[1]T", v["op"])
|
||||
}
|
||||
rule.OP = maskOP(op)
|
||||
|
||||
path, set := getString(v, "path")
|
||||
if set && path == "" {
|
||||
return nil, fmt.Errorf("invalid \"path\" value: %v %[1]T", v["path"])
|
||||
}
|
||||
rule.Path = path
|
||||
|
||||
rule.Value = v["value"]
|
||||
|
||||
// use unmarshalled values to create new Mask Rule
|
||||
rule, err := newMaskRule(rule.Path, withOP(rule.OP), withValue(rule.Value))
|
||||
|
||||
// TODO add withFailUndefinedPath() option based on
|
||||
// A) new syntax in user defined mask rule
|
||||
// B) passed in/global configuration option
|
||||
// rule precedence A>B
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mRuleSet.Rules = append(mRuleSet.Rules, rule)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid mask rule format encountered: %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
return mRuleSet, nil
|
||||
}
|
||||
|
||||
func (rs maskRuleSet) Mask(event *EventV1) {
|
||||
for _, mRule := range rs.Rules {
|
||||
// result must be deep copied if there are any mask rules
|
||||
// targeting it, to avoid modifying the result sent
|
||||
// to the consumer
|
||||
if mRule.escapedParts[0] == partResult && event.Result != nil && !rs.resultCopied {
|
||||
resultCopy := deepcopy.DeepCopy(*event.Result)
|
||||
event.Result = &resultCopy
|
||||
rs.resultCopied = true
|
||||
}
|
||||
err := mRule.Mask(event)
|
||||
if err != nil {
|
||||
rs.OnRuleError(mRule, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bool return means the field was set, if the string is still "", the
|
||||
// value was invalid
|
||||
func getString(x map[string]any, key string) (string, bool) {
|
||||
y, ok := x[key]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
s, ok := y.(string)
|
||||
if !ok {
|
||||
return "", true
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
@@ -1,766 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
)
|
||||
|
||||
func TestNewMaskRule(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
input *maskRule
|
||||
expErr error
|
||||
expPtr *maskRule
|
||||
}{
|
||||
{
|
||||
note: "empty",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "",
|
||||
},
|
||||
expErr: fmt.Errorf("mask must be non-empty"),
|
||||
},
|
||||
{
|
||||
note: "missing slash",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "foo",
|
||||
},
|
||||
expErr: fmt.Errorf("mask must be slash-prefixed"),
|
||||
},
|
||||
{
|
||||
note: "no prefix",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/",
|
||||
},
|
||||
expErr: fmt.Errorf("mask prefix not allowed"),
|
||||
},
|
||||
{
|
||||
note: "bad prefix key",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/labels/foo",
|
||||
},
|
||||
expErr: fmt.Errorf("mask prefix not allowed"),
|
||||
},
|
||||
{
|
||||
note: "standard",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/a/b/c",
|
||||
},
|
||||
expPtr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/a/b/c",
|
||||
escapedParts: []string{"input", "a", "b", "c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "fail with object path undefined",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/a/b/c",
|
||||
failUndefinedPath: true,
|
||||
},
|
||||
expPtr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/a/b/c",
|
||||
failUndefinedPath: true,
|
||||
escapedParts: []string{"input", "a", "b", "c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "fail with invalid OP",
|
||||
input: &maskRule{
|
||||
OP: maskOP("undefinedOP"),
|
||||
Path: "/input/a/b/c",
|
||||
},
|
||||
expErr: fmt.Errorf("mask op is not supported: undefinedOP"),
|
||||
},
|
||||
{
|
||||
note: "escaping",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/a/%2F%2F/b",
|
||||
},
|
||||
expPtr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/a/%2F%2F/b",
|
||||
escapedParts: []string{"input", "a", "%252F%252F", "b"},
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "bad escape",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/a/%F/b",
|
||||
},
|
||||
expErr: fmt.Errorf("invalid URL escape"),
|
||||
},
|
||||
{
|
||||
note: "empty component",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input//foo",
|
||||
},
|
||||
expPtr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input//foo",
|
||||
escapedParts: []string{"input", "", "foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "result",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/result/a",
|
||||
},
|
||||
expPtr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/result/a",
|
||||
escapedParts: []string{"result", "a"},
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "root",
|
||||
input: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input",
|
||||
},
|
||||
expPtr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input",
|
||||
escapedParts: []string{"input"},
|
||||
modifyFullObj: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
note: "unsupported mask op",
|
||||
input: &maskRule{
|
||||
OP: maskOP("unsupported"),
|
||||
Path: "/input",
|
||||
},
|
||||
expErr: fmt.Errorf("mask op is not supported: unsupported"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
result, err := newMaskRule(tc.input.Path, withOP(tc.input.OP), withValue(tc.input.Value))
|
||||
if tc.input.failUndefinedPath {
|
||||
_ = withFailUndefinedPath()(result)
|
||||
}
|
||||
|
||||
if tc.expErr != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but got: %v", result)
|
||||
} else if !strings.Contains(err.Error(), tc.expErr.Error()) {
|
||||
t.Fatalf("Expected error: %v, but got error: %v", tc.expErr, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
}
|
||||
if !reflect.DeepEqual(result, tc.expPtr) {
|
||||
t.Fatalf("Expected %#+v but got %#+v", tc.expPtr, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskRuleMask(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
ptr *maskRule
|
||||
event string
|
||||
exp string
|
||||
expErr error
|
||||
}{
|
||||
{
|
||||
note: "erase input",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input",
|
||||
},
|
||||
event: `{"input": {"a": 1}}`,
|
||||
exp: `{"erased": ["/input"]}`,
|
||||
},
|
||||
{
|
||||
note: "upsert input",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input",
|
||||
Value: struct {
|
||||
RandoString string
|
||||
}{RandoString: "foo"},
|
||||
},
|
||||
event: `{"input": {"a": 1}}`,
|
||||
exp: `{"masked": ["/input"], "input": {"RandoString": "foo"}}`,
|
||||
},
|
||||
{
|
||||
note: "erase result",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/result",
|
||||
},
|
||||
event: `{"result": "foo"}`,
|
||||
exp: `{"erased": ["/result"]}`,
|
||||
},
|
||||
{
|
||||
note: "upsert result",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/result",
|
||||
Value: "upserted",
|
||||
},
|
||||
event: `{"result": "foo"}`,
|
||||
exp: `{"masked": ["/result"], "result": "upserted"}`,
|
||||
},
|
||||
{
|
||||
note: "erase undefined input",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo",
|
||||
},
|
||||
event: `{}`,
|
||||
exp: `{}`,
|
||||
},
|
||||
{
|
||||
note: "erase undefined input: fail unknown object path on",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo",
|
||||
failUndefinedPath: true,
|
||||
},
|
||||
event: `{}`,
|
||||
exp: `{}`,
|
||||
expErr: errMaskInvalidObject,
|
||||
},
|
||||
{
|
||||
note: "upsert undefined input",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo",
|
||||
},
|
||||
event: `{}`,
|
||||
exp: `{}`,
|
||||
},
|
||||
{
|
||||
note: "upsert undefined input: fail unknown object path on",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo",
|
||||
failUndefinedPath: true,
|
||||
},
|
||||
event: `{}`,
|
||||
exp: `{}`,
|
||||
expErr: errMaskInvalidObject,
|
||||
},
|
||||
{
|
||||
note: "erase undefined result",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/result/foo",
|
||||
},
|
||||
event: `{}`,
|
||||
exp: `{}`,
|
||||
},
|
||||
{
|
||||
note: "erase undefined result: fail unknown object path on",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/result/foo",
|
||||
failUndefinedPath: true,
|
||||
},
|
||||
event: `{}`,
|
||||
exp: `{}`,
|
||||
expErr: errMaskInvalidObject,
|
||||
},
|
||||
{
|
||||
note: "upsert undefined result",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/result/foo",
|
||||
},
|
||||
event: `{}`,
|
||||
exp: `{}`,
|
||||
},
|
||||
{
|
||||
note: "upsert undefined result: fail unknown object path on",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/result/foo",
|
||||
failUndefinedPath: true,
|
||||
},
|
||||
event: `{}`,
|
||||
exp: `{}`,
|
||||
expErr: errMaskInvalidObject,
|
||||
},
|
||||
{
|
||||
note: "erase undefined node",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo",
|
||||
},
|
||||
event: `{"input": {"bar": 1}}`,
|
||||
exp: `{"input": {"bar": 1}}`,
|
||||
},
|
||||
{
|
||||
note: "erase undefined node: fail unknown object path on",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo",
|
||||
failUndefinedPath: true,
|
||||
},
|
||||
event: `{"input": {"bar": 1}}`,
|
||||
exp: `{"input": {"bar": 1}}`,
|
||||
expErr: errMaskInvalidObject,
|
||||
},
|
||||
{
|
||||
note: "upsert undefined node with nil value",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo",
|
||||
},
|
||||
event: `{"input": {"bar": 1}}`,
|
||||
exp: `{"input": {"bar": 1, "foo": null}, "masked": ["/input/foo"]}`,
|
||||
},
|
||||
{
|
||||
note: "upsert undefined node with nil value: fail unknown object path on",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo",
|
||||
failUndefinedPath: true,
|
||||
},
|
||||
event: `{"input": {"bar": 1}}`,
|
||||
exp: `{"input": {"bar": 1, "foo": null}, "masked": ["/input/foo"]}`,
|
||||
expErr: errMaskInvalidObject,
|
||||
},
|
||||
{
|
||||
note: "upsert undefined node with a value",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo",
|
||||
Value: "upserted",
|
||||
},
|
||||
event: `{"input": {"bar": 1}}`,
|
||||
exp: `{"input": {"bar": 1, "foo": "upserted"}, "masked": ["/input/foo"]}`,
|
||||
},
|
||||
{
|
||||
note: "erase undefined node-2",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo/bar",
|
||||
},
|
||||
event: `{"input": {"foo": 1}}`,
|
||||
exp: `{"input": {"foo": 1}}`,
|
||||
},
|
||||
{
|
||||
note: "upsert unsupported nested object type (json.Number) #1",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/bar",
|
||||
},
|
||||
event: `{"input": {"foo": 1}}`,
|
||||
exp: `{"input": {"foo": 1}}`,
|
||||
},
|
||||
{
|
||||
note: "upsert unsupported nested object type (string) #1",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/bar",
|
||||
},
|
||||
event: `{"input": {"foo": "bar"}}`,
|
||||
exp: `{"input": {"foo": "bar"}}`,
|
||||
},
|
||||
{
|
||||
note: "erase: undefined object: missing key",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo/bar/baz",
|
||||
},
|
||||
event: `{"input": {"foo": {}}}`,
|
||||
exp: `{"input": {"foo": {}}}`,
|
||||
},
|
||||
{
|
||||
note: "upsert: undefined object: missing key, no value",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/bar/baz",
|
||||
},
|
||||
event: `{"input": {"foo": {}}}`,
|
||||
exp: `{"input": {"foo": {"bar": {"baz": null}}}, "masked": ["/input/foo/bar/baz"]}`,
|
||||
},
|
||||
{
|
||||
note: "upsert: undefined object: missing key, provided value",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/bar/baz",
|
||||
Value: 100,
|
||||
},
|
||||
event: `{"input": {"foo": {}}}`,
|
||||
exp: `{"input": {"foo": {"bar": {"baz": 100}}}, "masked": ["/input/foo/bar/baz"]}`,
|
||||
},
|
||||
{
|
||||
note: "erase: undefined scalar",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo/bar/baz",
|
||||
},
|
||||
event: `{"input": {"foo": 1}}`,
|
||||
exp: `{"input": {"foo": 1}}`,
|
||||
},
|
||||
{
|
||||
note: "upsert: unsupported nested object type (json.Number) #2",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/bar/baz",
|
||||
},
|
||||
event: `{"input": {"foo": 1}}`,
|
||||
exp: `{"input": {"foo": 1}}`,
|
||||
},
|
||||
{
|
||||
note: "erase: undefined array: non-int index",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo/bar/baz", // bar is invalid
|
||||
},
|
||||
event: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
exp: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
},
|
||||
{
|
||||
note: "upsert: unsupported type: []interface {}",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/bar/baz", // foo is []interface
|
||||
},
|
||||
event: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
exp: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
},
|
||||
{
|
||||
note: "erase: undefined array: negative index",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo/-1/baz",
|
||||
},
|
||||
event: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
exp: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
},
|
||||
{
|
||||
note: "upsert: undefined array: negative index",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/-1/baz", // foo is an []interface {}
|
||||
},
|
||||
event: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
exp: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
},
|
||||
{
|
||||
note: "erase: undefined array: index out of range",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo/1/baz",
|
||||
},
|
||||
event: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
exp: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
},
|
||||
{
|
||||
note: "upsert: unsupported nested object type (array) #1",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/1/baz", // foo is an []interface {}
|
||||
},
|
||||
event: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
exp: `{"input": {"foo": [{"baz": 1}]}}`,
|
||||
},
|
||||
{
|
||||
note: "erase: undefined array: remove element",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo/0",
|
||||
},
|
||||
event: `{"input": {"foo": [1]}}`,
|
||||
exp: `{"input": {"foo": [1]}}`,
|
||||
},
|
||||
{
|
||||
note: "upsert: unsupported nested object type (array) #2",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo/0",
|
||||
},
|
||||
event: `{"input": {"foo": [1]}}`,
|
||||
exp: `{"input": {"foo": [1]}}`,
|
||||
},
|
||||
{
|
||||
note: "erase: object key",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo",
|
||||
},
|
||||
event: `{"input": {"bar": 1, "foo": [{"baz": 1}]}}`,
|
||||
exp: `{"input": {"bar": 1}, "erased": ["/input/foo"]}`,
|
||||
},
|
||||
{
|
||||
note: "upsert: object key",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/foo",
|
||||
Value: []map[string]int{{"nabs": 1}},
|
||||
},
|
||||
event: `{"input": {"bar": 1, "foo": [{"baz": 1}]}}`,
|
||||
exp: `{"input": {"bar": 1, "foo": [{"nabs": 1}]}, "masked": ["/input/foo"]}`,
|
||||
},
|
||||
{
|
||||
note: "erase: object key (multiple)",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/bar",
|
||||
},
|
||||
event: `{"input": {"bar": 1}, "erased": ["/input/foo"]}`,
|
||||
exp: `{"input": {}, "erased": ["/input/foo", "/input/bar"]}`,
|
||||
},
|
||||
{
|
||||
note: "erase: object key (nested array)",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/foo/0/bar",
|
||||
},
|
||||
event: `{"input": {"foo": [{"bar": 1, "baz": 2}]}}`,
|
||||
exp: `{"input": {"foo": [{"baz": 2}]}, "erased": ["/input/foo/0/bar"]}`,
|
||||
},
|
||||
{
|
||||
note: "erase input: special character in path",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/:path",
|
||||
},
|
||||
event: `{"input": {"bar": 1, ":path": "token"}}`,
|
||||
exp: `{"input": {"bar": 1}, "erased": ["/input/:path"]}`,
|
||||
},
|
||||
{
|
||||
note: "upsert input: special character in path",
|
||||
ptr: &maskRule{
|
||||
OP: maskOPUpsert,
|
||||
Path: "/input/:path",
|
||||
Value: "upserted",
|
||||
},
|
||||
event: `{"input": {"bar": 1, ":path": "token"}}`,
|
||||
exp: `{"input": {"bar": 1, ":path": "upserted"}, "masked": ["/input/:path"]}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
|
||||
ptr, err := newMaskRule(tc.ptr.Path, withOP(tc.ptr.OP), withValue(tc.ptr.Value))
|
||||
if tc.ptr.failUndefinedPath {
|
||||
_ = withFailUndefinedPath()(ptr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var exp EventV1
|
||||
if err := util.UnmarshalJSON([]byte(tc.exp), &exp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var event EventV1
|
||||
if err := util.UnmarshalJSON([]byte(tc.event), &event); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ptr.Mask(&event)
|
||||
if err != nil {
|
||||
if tc.expErr == nil {
|
||||
t.Fatalf("no expected error, but received '%s'", err.Error())
|
||||
}
|
||||
if tc.expErr.Error() != err.Error() {
|
||||
t.Fatalf("expected error '%s', got '%s'", tc.expErr.Error(), err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// compare via json marshall to map tc input types
|
||||
bs1, _ := json.MarshalIndent(exp, "", " ")
|
||||
bs2, _ := json.MarshalIndent(event, "", " ")
|
||||
if !bytes.Equal(bs1, bs2) {
|
||||
t.Fatalf("Expected: %s\nGot: %s", string(bs1), string(bs2))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMaskRuleSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
value interface{}
|
||||
exp *maskRuleSet
|
||||
err error
|
||||
}{
|
||||
{
|
||||
note: "invalid format: not []interface{}",
|
||||
value: map[string]int{"invalid": 1},
|
||||
err: fmt.Errorf("unexpected rule format map[invalid:1] (map[string]int)"),
|
||||
},
|
||||
{
|
||||
note: "invalid format: nested type not string or map[string]interface{}",
|
||||
value: []interface{}{
|
||||
[]int{1, 2},
|
||||
},
|
||||
err: fmt.Errorf("invalid mask rule format encountered: []int"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
_, err := newMaskRuleSet(tc.value, func(_ *maskRule, _ error) {})
|
||||
if err != nil {
|
||||
if exp, act := tc.err.Error(), err.Error(); exp != act {
|
||||
t.Fatalf("Expected: %s\nGot: %s", exp, act)
|
||||
}
|
||||
} else if tc.err != nil {
|
||||
t.Errorf("expected error %v, got nil", tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskRuleSetMask(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []*maskRule
|
||||
event string
|
||||
exp string
|
||||
expErr error
|
||||
}{
|
||||
{
|
||||
note: "erase input",
|
||||
rules: []*maskRule{
|
||||
{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input",
|
||||
},
|
||||
},
|
||||
event: `{"input": {"a": 1}}`,
|
||||
exp: `{"erased": ["/input"]}`,
|
||||
},
|
||||
{
|
||||
note: "erase result",
|
||||
rules: []*maskRule{
|
||||
{
|
||||
OP: maskOPRemove,
|
||||
Path: "/result",
|
||||
},
|
||||
},
|
||||
event: `{"result": {"a": 1}}`,
|
||||
exp: `{"erased": ["/result"]}`,
|
||||
},
|
||||
{
|
||||
note: "erase input and result nested",
|
||||
rules: []*maskRule{
|
||||
{
|
||||
OP: maskOPRemove,
|
||||
Path: "/input/a/b",
|
||||
},
|
||||
{
|
||||
OP: maskOPRemove,
|
||||
Path: "/result/c/d",
|
||||
},
|
||||
},
|
||||
event: `{"input":{"a":{"b":"removeme","y":"stillhere"}},"result":{"c":{"d":"removeme","z":"stillhere"}}}`,
|
||||
exp: `{"input":{"a":{"y":"stillhere"}},"result":{"c":{"z":"stillhere"}},"erased":["/input/a/b", "/result/c/d"]}`,
|
||||
},
|
||||
{
|
||||
note: "expected rule error",
|
||||
rules: []*maskRule{
|
||||
{
|
||||
OP: maskOPRemove,
|
||||
Path: "/result",
|
||||
failUndefinedPath: true,
|
||||
},
|
||||
},
|
||||
event: `{"input":"foo"}`,
|
||||
exp: `{"input":"foo"}`,
|
||||
expErr: errMaskInvalidObject,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
ptr := &maskRuleSet{}
|
||||
var ruleErr error
|
||||
if tc.expErr != nil {
|
||||
ptr.OnRuleError = func(_ *maskRule, err error) {
|
||||
ruleErr = err
|
||||
}
|
||||
} else {
|
||||
ptr.OnRuleError = func(mRule *maskRule, err error) {
|
||||
t.Fatalf("unexpected rule error, rule: %s, error: %s", mRule.String(), err.Error())
|
||||
}
|
||||
}
|
||||
for _, rule := range tc.rules {
|
||||
var mRule *maskRule
|
||||
var err error
|
||||
if rule.failUndefinedPath {
|
||||
mRule, err = newMaskRule(rule.Path, withOP(rule.OP), withValue(rule.Value), withFailUndefinedPath())
|
||||
} else {
|
||||
mRule, err = newMaskRule(rule.Path, withOP(rule.OP), withValue(rule.Value))
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ptr.Rules = append(ptr.Rules, mRule)
|
||||
}
|
||||
|
||||
var exp EventV1
|
||||
if err := util.UnmarshalJSON([]byte(tc.exp), &exp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var event EventV1
|
||||
var origEvent EventV1
|
||||
if err := util.UnmarshalJSON([]byte(tc.event), &event); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
origEvent = event
|
||||
|
||||
ptr.Mask(&event)
|
||||
|
||||
// compare via json marshall to map tc input types
|
||||
bs1, _ := json.MarshalIndent(exp, "", " ")
|
||||
bs2, _ := json.MarshalIndent(event, "", " ")
|
||||
if !bytes.Equal(bs1, bs2) {
|
||||
t.Fatalf("Expected: %s\nGot: %s", string(bs1), string(bs2))
|
||||
}
|
||||
|
||||
if origEvent.Result != nil && reflect.DeepEqual(origEvent.Result, event.Result) {
|
||||
t.Fatal("Expected event.Result to be deep copied during masking, so that the event's original Result is not modified")
|
||||
}
|
||||
|
||||
if tc.expErr != nil {
|
||||
if ruleErr == nil {
|
||||
t.Fatalf("Expected: %s\nGot:%s", tc.expErr.Error(), "nil")
|
||||
}
|
||||
if tc.expErr != ruleErr {
|
||||
t.Fatalf("Expected: %s\nGot:%s", tc.expErr.Error(), ruleErr.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,267 +0,0 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/plugins"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
inmem "github.com/open-policy-agent/opa/storage/inmem/test"
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
)
|
||||
|
||||
const largeEvent = `{
|
||||
"_id": "15596749567705615560",
|
||||
"decision_id": "0e67fda0-170b-454d-9f5e-29691073f97e",
|
||||
"input": {
|
||||
"apiVersion": "admission.k8s.io/v1beta1",
|
||||
"kind": "AdmissionReview",
|
||||
"request": {
|
||||
"kind": {
|
||||
"group": "",
|
||||
"kind": "Pod",
|
||||
"version": "v1"
|
||||
},
|
||||
"namespace": "demo",
|
||||
"object": {
|
||||
"metadata": {
|
||||
"creationTimestamp": "2019-06-04T19:02:35Z",
|
||||
"labels": {
|
||||
"run": "nginx"
|
||||
},
|
||||
"name": "nginx",
|
||||
"namespace": "demo",
|
||||
"uid": "507e4c3c-86fb-11e9-b289-42010a8000b2"
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"image": "nginx",
|
||||
"imagePullPolicy": "Always",
|
||||
"name": "nginx",
|
||||
"resources": {},
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"terminationMessagePolicy": "File",
|
||||
"volumeMounts": [
|
||||
{
|
||||
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
|
||||
"name": "default-token-5vjbc",
|
||||
"readOnly": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"dnsPolicy": "ClusterFirst",
|
||||
"priority": 0,
|
||||
"restartPolicy": "Never",
|
||||
"schedulerName": "default-scheduler",
|
||||
"securityContext": {},
|
||||
"serviceAccount": "default",
|
||||
"serviceAccountName": "default",
|
||||
"terminationGracePeriodSeconds": 30,
|
||||
"tolerations": [
|
||||
{
|
||||
"effect": "NoExecute",
|
||||
"key": "node.kubernetes.io/not-ready",
|
||||
"operator": "Exists",
|
||||
"tolerationSeconds": 300
|
||||
},
|
||||
{
|
||||
"effect": "NoExecute",
|
||||
"key": "node.kubernetes.io/unreachable",
|
||||
"operator": "Exists",
|
||||
"tolerationSeconds": 300
|
||||
}
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
"name": "default-token-5vjbc",
|
||||
"secret": {
|
||||
"secretName": "default-token-5vjbc"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"phase": "Pending",
|
||||
"qosClass": "BestEffort"
|
||||
}
|
||||
},
|
||||
"oldObject": null,
|
||||
"operation": "CREATE",
|
||||
"resource": {
|
||||
"group": "",
|
||||
"resource": "pods",
|
||||
"version": "v1"
|
||||
},
|
||||
"userInfo": {
|
||||
"groups": [
|
||||
"system:serviceaccounts",
|
||||
"system:serviceaccounts:opa-system",
|
||||
"system:authenticated"
|
||||
],
|
||||
"username": "system:serviceaccount:opa-system:default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"id": "462a43bd-6a5f-4530-9386-30b0f4e0c8af",
|
||||
"policy-type": "kubernetes/admission_control",
|
||||
"system-type": "kubernetes",
|
||||
"version": "0.10.5"
|
||||
},
|
||||
"metrics": {
|
||||
"timer_rego_module_compile_ns": 222,
|
||||
"timer_rego_module_parse_ns": 313,
|
||||
"timer_rego_query_compile_ns": 121360,
|
||||
"timer_rego_query_eval_ns": 923279,
|
||||
"timer_rego_query_parse_ns": 287152,
|
||||
"timer_server_handler_ns": 2563846
|
||||
},
|
||||
"path": "admission_control/main",
|
||||
"requested_by": "10.52.0.1:53848",
|
||||
"result": {
|
||||
"apiVersion": "admission.k8s.io/v1beta1",
|
||||
"kind": "AdmissionReview",
|
||||
"response": {
|
||||
"allowed": false,
|
||||
"status": {
|
||||
"message": "Resource Pod/demo/nginx includes container image 'nginx' from prohibited registry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"revision": "jafsdkjfhaslkdfjlaksdjflaksjdflkajsdlkfjasldkfjlaksdjflkasdjflkasjdflkajsdflkjasdklfjalsdjf",
|
||||
"timestamp": "2019-06-04T19:02:35.692Z"
|
||||
}`
|
||||
|
||||
func BenchmarkMaskingNop(b *testing.B) {
|
||||
|
||||
ctx := context.Background()
|
||||
store := inmem.New()
|
||||
|
||||
manager, err := plugins.New(nil, "test", store)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
} else if err := manager.Start(ctx); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
cfg := &Config{Service: "svc"}
|
||||
t := plugins.DefaultTriggerMode
|
||||
if err := cfg.validateAndInjectDefaults([]string{"svc"}, nil, &t); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
plugin := New(cfg, manager)
|
||||
|
||||
var event EventV1
|
||||
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
input, err := event.AST()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := plugin.maskEvent(ctx, nil, input, &event); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMaskingRuleCountsNop(b *testing.B) {
|
||||
numRules := []int{1, 10, 100, 1000}
|
||||
|
||||
ctx := context.Background()
|
||||
store := inmem.New()
|
||||
|
||||
manager, err := plugins.New(nil, "test", store)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
} else if err := manager.Start(ctx); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
cfg := &Config{Service: "svc"}
|
||||
t := plugins.DefaultTriggerMode
|
||||
if err := cfg.validateAndInjectDefaults([]string{"svc"}, nil, &t); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
plugin := New(cfg, manager)
|
||||
|
||||
var event EventV1
|
||||
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
input, err := event.AST()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
for _, ruleCount := range numRules {
|
||||
b.Run(fmt.Sprintf("%dRules", ruleCount), func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := plugin.maskEvent(ctx, nil, input, &event); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMaskingErase(b *testing.B) {
|
||||
|
||||
ctx := context.Background()
|
||||
store := inmem.New()
|
||||
|
||||
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
||||
return store.UpsertPolicy(ctx, txn, "test.rego", []byte(`
|
||||
package system.log
|
||||
|
||||
mask["/input"] {
|
||||
input.input.request.kind.kind == "Pod"
|
||||
}
|
||||
`))
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
manager, err := plugins.New(nil, "test", store)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
} else if err := manager.Start(ctx); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
cfg := &Config{Service: "svc"}
|
||||
t := plugins.DefaultTriggerMode
|
||||
if err := cfg.validateAndInjectDefaults([]string{"svc"}, nil, &t); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
plugin := New(cfg, manager)
|
||||
var event EventV1
|
||||
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
input, err := event.AST()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := plugin.maskEvent(ctx, nil, input, &event); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if event.Input != nil {
|
||||
b.Fatal("Expected input to be erased")
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
// Copyright 2023 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package status
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/open-policy-agent/opa/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
errCode = "decision_log_error"
|
||||
)
|
||||
|
||||
// Status represents the status of processing a decision log.
|
||||
type Status struct {
|
||||
Code string `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
HTTPCode json.Number `json:"http_code,omitempty"`
|
||||
Metrics metrics.Metrics `json:"metrics,omitempty"`
|
||||
}
|
||||
|
||||
// SetError updates the status object to reflect a failure to upload or
|
||||
// process a log. If err is nil, the error status is cleared.
|
||||
func (s *Status) SetError(err error) {
|
||||
var httpError HTTPError
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
s.Code = ""
|
||||
s.HTTPCode = ""
|
||||
s.Message = ""
|
||||
|
||||
case errors.As(err, &httpError):
|
||||
s.Code = errCode
|
||||
s.HTTPCode = json.Number(strconv.Itoa(httpError.StatusCode))
|
||||
s.Message = err.Error()
|
||||
|
||||
default:
|
||||
s.Code = errCode
|
||||
s.HTTPCode = ""
|
||||
s.Message = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
type HTTPError struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (e HTTPError) Error() string {
|
||||
return fmt.Sprintf("log upload failed, server replied with HTTP %v %v", e.StatusCode, http.StatusText(e.StatusCode))
|
||||
}
|
||||
Reference in New Issue
Block a user