mirror of
https://github.com/getsops/sops.git
synced 2026-02-05 12:45:21 +01:00
It's been around 9 months since our last vendor update. This is also needed for some new features being worked on for sops workspace. Additionally, this PR regenerates the kms mocks.
430 lines
11 KiB
Go
430 lines
11 KiB
Go
// Copyright 2017 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package firestore
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
|
)
|
|
|
|
func setFromProtoValue(x interface{}, vproto *pb.Value, c *Client) error {
|
|
v := reflect.ValueOf(x)
|
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
|
return errors.New("firestore: nil or not a pointer")
|
|
}
|
|
return setReflectFromProtoValue(v.Elem(), vproto, c)
|
|
}
|
|
|
|
// setReflectFromProtoValue sets v from a Firestore Value.
|
|
// v must be a settable value.
|
|
func setReflectFromProtoValue(v reflect.Value, vproto *pb.Value, c *Client) error {
|
|
typeErr := func() error {
|
|
return fmt.Errorf("firestore: cannot set type %s to %s", v.Type(), typeString(vproto))
|
|
}
|
|
|
|
val := vproto.ValueType
|
|
// A Null value sets anything nullable to nil, and has no effect
|
|
// on anything else.
|
|
if _, ok := val.(*pb.Value_NullValue); ok {
|
|
switch v.Kind() {
|
|
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
|
v.Set(reflect.Zero(v.Type()))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Handle special types first.
|
|
switch v.Type() {
|
|
case typeOfByteSlice:
|
|
x, ok := val.(*pb.Value_BytesValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
v.SetBytes(x.BytesValue)
|
|
return nil
|
|
|
|
case typeOfGoTime:
|
|
x, ok := val.(*pb.Value_TimestampValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
t, err := ptypes.Timestamp(x.TimestampValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.Set(reflect.ValueOf(t))
|
|
return nil
|
|
|
|
case typeOfProtoTimestamp:
|
|
x, ok := val.(*pb.Value_TimestampValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
v.Set(reflect.ValueOf(x.TimestampValue))
|
|
return nil
|
|
|
|
case typeOfLatLng:
|
|
x, ok := val.(*pb.Value_GeoPointValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
v.Set(reflect.ValueOf(x.GeoPointValue))
|
|
return nil
|
|
|
|
case typeOfDocumentRef:
|
|
x, ok := val.(*pb.Value_ReferenceValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
dr, err := pathToDoc(x.ReferenceValue, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.Set(reflect.ValueOf(dr))
|
|
return nil
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.Bool:
|
|
x, ok := val.(*pb.Value_BooleanValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
v.SetBool(x.BooleanValue)
|
|
|
|
case reflect.String:
|
|
x, ok := val.(*pb.Value_StringValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
v.SetString(x.StringValue)
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
var i int64
|
|
switch x := val.(type) {
|
|
case *pb.Value_IntegerValue:
|
|
i = x.IntegerValue
|
|
case *pb.Value_DoubleValue:
|
|
f := x.DoubleValue
|
|
i = int64(f)
|
|
if float64(i) != f {
|
|
return fmt.Errorf("firestore: float %f does not fit into %s", f, v.Type())
|
|
}
|
|
default:
|
|
return typeErr()
|
|
}
|
|
if v.OverflowInt(i) {
|
|
return overflowErr(v, i)
|
|
}
|
|
v.SetInt(i)
|
|
|
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
|
var u uint64
|
|
switch x := val.(type) {
|
|
case *pb.Value_IntegerValue:
|
|
u = uint64(x.IntegerValue)
|
|
case *pb.Value_DoubleValue:
|
|
f := x.DoubleValue
|
|
u = uint64(f)
|
|
if float64(u) != f {
|
|
return fmt.Errorf("firestore: float %f does not fit into %s", f, v.Type())
|
|
}
|
|
default:
|
|
return typeErr()
|
|
}
|
|
if v.OverflowUint(u) {
|
|
return overflowErr(v, u)
|
|
}
|
|
v.SetUint(u)
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
var f float64
|
|
switch x := val.(type) {
|
|
case *pb.Value_DoubleValue:
|
|
f = x.DoubleValue
|
|
case *pb.Value_IntegerValue:
|
|
f = float64(x.IntegerValue)
|
|
if int64(f) != x.IntegerValue {
|
|
return overflowErr(v, x.IntegerValue)
|
|
}
|
|
default:
|
|
return typeErr()
|
|
}
|
|
if v.OverflowFloat(f) {
|
|
return overflowErr(v, f)
|
|
}
|
|
v.SetFloat(f)
|
|
|
|
case reflect.Slice:
|
|
x, ok := val.(*pb.Value_ArrayValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
vals := x.ArrayValue.Values
|
|
vlen := v.Len()
|
|
xlen := len(vals)
|
|
// Make a slice of the right size, avoiding allocation if possible.
|
|
switch {
|
|
case vlen < xlen:
|
|
v.Set(reflect.MakeSlice(v.Type(), xlen, xlen))
|
|
case vlen > xlen:
|
|
v.SetLen(xlen)
|
|
}
|
|
return populateRepeated(v, vals, xlen, c)
|
|
|
|
case reflect.Array:
|
|
x, ok := val.(*pb.Value_ArrayValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
vals := x.ArrayValue.Values
|
|
xlen := len(vals)
|
|
vlen := v.Len()
|
|
minlen := vlen
|
|
// Set extra elements to their zero value.
|
|
if vlen > xlen {
|
|
z := reflect.Zero(v.Type().Elem())
|
|
for i := xlen; i < vlen; i++ {
|
|
v.Index(i).Set(z)
|
|
}
|
|
minlen = xlen
|
|
}
|
|
return populateRepeated(v, vals, minlen, c)
|
|
|
|
case reflect.Map:
|
|
x, ok := val.(*pb.Value_MapValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
return populateMap(v, x.MapValue.Fields, c)
|
|
|
|
case reflect.Ptr:
|
|
// If the pointer is nil, set it to a zero value.
|
|
if v.IsNil() {
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
return setReflectFromProtoValue(v.Elem(), vproto, c)
|
|
|
|
case reflect.Struct:
|
|
x, ok := val.(*pb.Value_MapValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
return populateStruct(v, x.MapValue.Fields, c)
|
|
|
|
case reflect.Interface:
|
|
if v.NumMethod() == 0 { // empty interface
|
|
// If v holds a pointer, set the pointer.
|
|
if !v.IsNil() && v.Elem().Kind() == reflect.Ptr {
|
|
return setReflectFromProtoValue(v.Elem(), vproto, c)
|
|
}
|
|
// Otherwise, create a fresh value.
|
|
x, err := createFromProtoValue(vproto, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.Set(reflect.ValueOf(x))
|
|
return nil
|
|
}
|
|
// Any other kind of interface is an error.
|
|
fallthrough
|
|
|
|
default:
|
|
return fmt.Errorf("firestore: cannot set type %s", v.Type())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// populateRepeated sets the first n elements of vr, which must be a slice or
|
|
// array, to the corresponding elements of vals.
|
|
func populateRepeated(vr reflect.Value, vals []*pb.Value, n int, c *Client) error {
|
|
for i := 0; i < n; i++ {
|
|
if err := setReflectFromProtoValue(vr.Index(i), vals[i], c); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// populateMap sets the elements of vm, which must be a map, from the
|
|
// corresponding elements of pm.
|
|
//
|
|
// Since a map value is not settable, this function always creates a new
|
|
// element for each corresponding map key. Existing values of vm are
|
|
// overwritten. This happens even if the map value is something like a pointer
|
|
// to a struct, where we could in theory populate the existing struct value
|
|
// instead of discarding it. This behavior matches encoding/json.
|
|
func populateMap(vm reflect.Value, pm map[string]*pb.Value, c *Client) error {
|
|
t := vm.Type()
|
|
if t.Key().Kind() != reflect.String {
|
|
return errors.New("firestore: map key type is not string")
|
|
}
|
|
if vm.IsNil() {
|
|
vm.Set(reflect.MakeMap(t))
|
|
}
|
|
et := t.Elem()
|
|
for k, vproto := range pm {
|
|
el := reflect.New(et).Elem()
|
|
if err := setReflectFromProtoValue(el, vproto, c); err != nil {
|
|
return err
|
|
}
|
|
vm.SetMapIndex(reflect.ValueOf(k), el)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// createMapFromValueMap creates a fresh map and populates it with pm.
|
|
func createMapFromValueMap(pm map[string]*pb.Value, c *Client) (map[string]interface{}, error) {
|
|
m := map[string]interface{}{}
|
|
for k, pv := range pm {
|
|
v, err := createFromProtoValue(pv, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m[k] = v
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// populateStruct sets the fields of vs, which must be a struct, from
|
|
// the matching elements of pm.
|
|
func populateStruct(vs reflect.Value, pm map[string]*pb.Value, c *Client) error {
|
|
fields, err := fieldCache.Fields(vs.Type())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k, vproto := range pm {
|
|
f := fields.Match(k)
|
|
if f == nil {
|
|
continue
|
|
}
|
|
if err := setReflectFromProtoValue(vs.FieldByIndex(f.Index), vproto, c); err != nil {
|
|
return fmt.Errorf("%s.%s: %v", vs.Type(), f.Name, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createFromProtoValue(vproto *pb.Value, c *Client) (interface{}, error) {
|
|
switch v := vproto.ValueType.(type) {
|
|
case *pb.Value_NullValue:
|
|
return nil, nil
|
|
case *pb.Value_BooleanValue:
|
|
return v.BooleanValue, nil
|
|
case *pb.Value_IntegerValue:
|
|
return v.IntegerValue, nil
|
|
case *pb.Value_DoubleValue:
|
|
return v.DoubleValue, nil
|
|
case *pb.Value_TimestampValue:
|
|
return ptypes.Timestamp(v.TimestampValue)
|
|
case *pb.Value_StringValue:
|
|
return v.StringValue, nil
|
|
case *pb.Value_BytesValue:
|
|
return v.BytesValue, nil
|
|
case *pb.Value_ReferenceValue:
|
|
return pathToDoc(v.ReferenceValue, c)
|
|
case *pb.Value_GeoPointValue:
|
|
return v.GeoPointValue, nil
|
|
case *pb.Value_ArrayValue:
|
|
vals := v.ArrayValue.Values
|
|
ret := make([]interface{}, len(vals))
|
|
for i, v := range vals {
|
|
r, err := createFromProtoValue(v, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret[i] = r
|
|
}
|
|
return ret, nil
|
|
|
|
case *pb.Value_MapValue:
|
|
fields := v.MapValue.Fields
|
|
ret := make(map[string]interface{}, len(fields))
|
|
for k, v := range fields {
|
|
r, err := createFromProtoValue(v, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret[k] = r
|
|
}
|
|
return ret, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("firestore: unknown value type %T", v)
|
|
}
|
|
}
|
|
|
|
// Convert a document path to a DocumentRef.
|
|
func pathToDoc(docPath string, c *Client) (*DocumentRef, error) {
|
|
projID, dbID, docIDs, err := parseDocumentPath(docPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parentResourceName := fmt.Sprintf("projects/%s/databases/%s", projID, dbID)
|
|
_, doc := c.idsToRef(docIDs, parentResourceName)
|
|
return doc, nil
|
|
}
|
|
|
|
// A document path should be of the form "projects/P/databases/D/documents/coll1/doc1/coll2/doc2/...".
|
|
func parseDocumentPath(path string) (projectID, databaseID string, docPath []string, err error) {
|
|
parts := strings.Split(path, "/")
|
|
if len(parts) < 6 || parts[0] != "projects" || parts[2] != "databases" || parts[4] != "documents" {
|
|
return "", "", nil, fmt.Errorf("firestore: malformed document path %q", path)
|
|
}
|
|
docp := parts[5:]
|
|
if len(docp)%2 != 0 {
|
|
return "", "", nil, fmt.Errorf("firestore: path %q refers to collection, not document", path)
|
|
}
|
|
return parts[1], parts[3], docp, nil
|
|
}
|
|
|
|
func typeString(vproto *pb.Value) string {
|
|
switch vproto.ValueType.(type) {
|
|
case *pb.Value_NullValue:
|
|
return "null"
|
|
case *pb.Value_BooleanValue:
|
|
return "bool"
|
|
case *pb.Value_IntegerValue:
|
|
return "int"
|
|
case *pb.Value_DoubleValue:
|
|
return "float"
|
|
case *pb.Value_TimestampValue:
|
|
return "timestamp"
|
|
case *pb.Value_StringValue:
|
|
return "string"
|
|
case *pb.Value_BytesValue:
|
|
return "bytes"
|
|
case *pb.Value_ReferenceValue:
|
|
return "reference"
|
|
case *pb.Value_GeoPointValue:
|
|
return "GeoPoint"
|
|
case *pb.Value_MapValue:
|
|
return "map"
|
|
case *pb.Value_ArrayValue:
|
|
return "array"
|
|
default:
|
|
return "<unknown Value type>"
|
|
}
|
|
}
|
|
|
|
func overflowErr(v reflect.Value, x interface{}) error {
|
|
return fmt.Errorf("firestore: value %v overflows type %s", x, v.Type())
|
|
}
|