* Update the mask manager to always allow fields containing a prefix which matchs a passedThroughPrefix. This will be useful when updating plugin based host catalog and sets since attributes and secrets cannot be contained in the mask manager mapping.
120 lines
3.8 KiB
Go
120 lines
3.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/types/descriptorpb"
|
|
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions"
|
|
)
|
|
|
|
type MaskManager map[string]string
|
|
|
|
type (
|
|
MaskDestination []protoreflect.ProtoMessage
|
|
MaskSource []protoreflect.ProtoMessage
|
|
)
|
|
|
|
// NewMaskManager returns a mask manager that can translate field masks into
|
|
// the first proto from all subsequent protos assuming they are both using the
|
|
// mask_mapping custom option. Error is returned if no mappings are
|
|
// found or if one of the passed protos has a mapping that doesn't reciprocate.
|
|
func NewMaskManager(dest MaskDestination, src MaskSource) (MaskManager, error) {
|
|
const op = "handlers.NewMaskManager"
|
|
srcToDest, err := mapFromProto(src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
destToSrc, err := mapFromProto(dest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make(map[string]string)
|
|
for k, v := range srcToDest {
|
|
ov, ok := destToSrc[v]
|
|
if !ok || ov != k {
|
|
return nil, errors.NewDeprecated(errors.Encode, op, fmt.Sprintf("mapping src field %q maps to %q, dest %q maps to %q", k, v, v, ov))
|
|
}
|
|
result[k] = v
|
|
}
|
|
|
|
// Now check to make sure there aren't any dangling dest mappings.
|
|
for k, v := range destToSrc {
|
|
if ov, ok := srcToDest[v]; !ok || ov != k {
|
|
return nil, errors.NewDeprecated(errors.Encode, op, fmt.Sprintf("mapping src field %q maps to %q, dest %q maps to %q", k, v, v, ov))
|
|
}
|
|
}
|
|
|
|
if len(result) == 0 {
|
|
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "mask mapping generated is zero")
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func mapFromProto(ps []protoreflect.ProtoMessage) (map[string]string, error) {
|
|
const op = "handlers.mapFromProto"
|
|
mapping := make(map[string]string)
|
|
for _, p := range ps {
|
|
m := p.ProtoReflect()
|
|
fields := m.Descriptor().Fields()
|
|
for i := 0; i < fields.Len(); i++ {
|
|
f := fields.Get(i)
|
|
opts := f.Options().(*descriptorpb.FieldOptions)
|
|
if nameMap := proto.GetExtension(opts, pb.E_MaskMapping).(*pb.MaskMapping); !proto.Equal(nameMap, &pb.MaskMapping{}) && nameMap != nil {
|
|
if _, ok := mapping[nameMap.GetThis()]; ok {
|
|
return nil, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("duplicate mapping from field %q with the mapping key %q", f.Name(), nameMap.GetThis()))
|
|
}
|
|
mapping[nameMap.GetThis()] = nameMap.GetThat()
|
|
}
|
|
}
|
|
}
|
|
return mapping, nil
|
|
}
|
|
|
|
// Translate takes a field mask's paths and returns paths translated for the
|
|
// destination's protobuf. If a translation doesn't exist for a specific path
|
|
// entry then nothing is returned for that specific path entry unless
|
|
// passedThroughPrefix is used and applies to that entry.
|
|
//
|
|
// passedThroughPrefix is helpful when the field mask's paths cannot be mapped
|
|
// through the mask mapper for some reason (we don't know the mapping at compile
|
|
// time, for example) but we still want certain paths to be passed on. If a
|
|
// path entry isn't mapped to anything by the MaskManager but does contain
|
|
// a prefix which matches a passedThroughPrefix it will be added unmodified
|
|
// to the returned value.
|
|
func (m MaskManager) Translate(paths []string, passedThroughPrefix ...string) []string {
|
|
var result []string
|
|
for _, v := range paths {
|
|
vSplit := strings.Split(v, ",")
|
|
for _, v := range vSplit {
|
|
candidate := strings.TrimSpace(v)
|
|
if ov, ok := m[candidate]; ok {
|
|
result = append(result, ov)
|
|
} else {
|
|
for _, pre := range passedThroughPrefix {
|
|
if strings.HasPrefix(candidate, pre) {
|
|
result = append(result, candidate)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func MaskContains(paths []string, s string) bool {
|
|
for _, p := range paths {
|
|
if p == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|