// roundTrip applies a single round-trip test to the given runtime object // using the given codec. The round-trip test ensures that an object can be // deep-copied and converted from internal -> versioned -> internal without // loss of data. func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { printer := spew.ConfigState{DisableMethods: true} original := item // deep copy the original object copied, err := api.Scheme.DeepCopy(item) if err != nil { panic(fmt.Sprintf("unable to copy: %v", err)) } item = copied.(runtime.Object) name := reflect.TypeOf(item).Elem().Name() // encode (serialize) the deep copy using the provided codec data, err := runtime.Encode(codec, item) if err != nil { if runtime.IsNotRegisteredError(err) { t.Logf("%v: not registered: %v (%s)", name, err, printer.Sprintf("%#v", item)) } else { t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item)) } return } // ensure that the deep copy is equal to the original; neither the deep // copy or conversion should alter the object if !api.Semantic.DeepEqual(original, item) { t.Errorf("0: %v: encode altered the object, diff: %v", name, diff.ObjectReflectDiff(original, item)) return } // decode (deserialize) the encoded data back into an object obj2, err := runtime.Decode(codec, data) if err != nil { t.Errorf("0: %v: %v\nCodec: %#v\nData: %s\nSource: %#v", name, err, codec, dataAsString(data), printer.Sprintf("%#v", item)) panic("failed") } // ensure that the object produced from decoding the encoded data is equal // to the original object if !api.Semantic.DeepEqual(original, obj2) { t.Errorf("\n1: %v: diff: %v\nCodec: %#v\nSource:\n\n%#v\n\nEncoded:\n\n%s\n\nFinal:\n\n%#v", name, diff.ObjectReflectDiff(item, obj2), codec, printer.Sprintf("%#v", item), dataAsString(data), printer.Sprintf("%#v", obj2)) return } // decode the encoded data into a new object (instead of letting the codec // create a new object) obj3 := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Errorf("2: %v: %v", name, err) return } // ensure that the new runtime object is equal to the original after being // decoded into if !api.Semantic.DeepEqual(item, obj3) { t.Errorf("3: %v: diff: %v\nCodec: %#v", name, diff.ObjectReflectDiff(item, obj3), codec) return } }
func (versionedParameterEncoderWithV1Fallback) EncodeParameters(obj runtime.Object, to schema.GroupVersion) (url.Values, error) { ret, err := api.ParameterCodec.EncodeParameters(obj, to) if err != nil && runtime.IsNotRegisteredError(err) { // fallback to v1 return api.ParameterCodec.EncodeParameters(obj, v1.SchemeGroupVersion) } return ret, err }
// ReadAdmissionConfiguration reads the admission configuration at the specified path. // It returns the loaded admission configuration if the input file aligns with the required syntax. // If it does not align with the provided syntax, it returns a default configuration for the enumerated // set of pluginNames whose config location references the specified configFilePath. // It does this to preserve backward compatibility when admission control files were opaque. // It returns an error if the file did not exist. func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (admission.ConfigProvider, error) { if configFilePath == "" { return configProvider{config: &componentconfig.AdmissionConfiguration{}}, nil } // a file was provided, so we just read it. data, err := ioutil.ReadFile(configFilePath) if err != nil { return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err) } decoder := api.Codecs.UniversalDecoder() decodedObj, err := runtime.Decode(decoder, data) // we were able to decode the file successfully if err == nil { decodedConfig, ok := decodedObj.(*componentconfig.AdmissionConfiguration) if !ok { return nil, fmt.Errorf("unexpected type: %T", decodedObj) } baseDir := path.Dir(configFilePath) for i := range decodedConfig.Plugins { if decodedConfig.Plugins[i].Path == "" { continue } // we update relative file paths to absolute paths absPath, err := makeAbs(decodedConfig.Plugins[i].Path, baseDir) if err != nil { return nil, err } decodedConfig.Plugins[i].Path = absPath } return configProvider{config: decodedConfig}, nil } // we got an error where the decode wasn't related to a missing type if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) { return nil, err } // convert the legacy format to the new admission control format // in order to preserve backwards compatibility, we set plugins that // previously read input from a non-versioned file configuration to the // current input file. legacyPluginsWithUnversionedConfig := sets.NewString("ImagePolicyWebhook", "PodNodeSelector") externalConfig := &componentconfigv1alpha1.AdmissionConfiguration{} for _, pluginName := range pluginNames { if legacyPluginsWithUnversionedConfig.Has(pluginName) { externalConfig.Plugins = append(externalConfig.Plugins, componentconfigv1alpha1.AdmissionPluginConfiguration{ Name: pluginName, Path: configFilePath}) } } api.Scheme.Default(externalConfig) internalConfig := &componentconfig.AdmissionConfiguration{} if err := api.Scheme.Convert(externalConfig, internalConfig, nil); err != nil { return nil, err } return configProvider{config: internalConfig}, nil }
// Encode does not do conversion. It sets the gvk during serialization. func (e DirectEncoder) Encode(obj runtime.Object, stream io.Writer) error { gvks, _, err := e.ObjectTyper.ObjectKinds(obj) if err != nil { if runtime.IsNotRegisteredError(err) { return e.Encoder.Encode(obj, stream) } return err } kind := obj.GetObjectKind() oldGVK := kind.GroupVersionKind() gvk := gvks[0] if e.Version != nil { preferredGVK, ok := e.Version.KindForGroupVersionKinds(gvks) if ok { gvk = preferredGVK } } kind.SetGroupVersionKind(gvk) err = e.Encoder.Encode(obj, stream) kind.SetGroupVersionKind(oldGVK) return err }
// AsVersionedObjects converts a list of infos into versioned objects. The provided // version will be preferred as the conversion target, but the Object's mapping version will be // used if that version is not present. func AsVersionedObjects(infos []*Info, version schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) { objects := []runtime.Object{} for _, info := range infos { if info.Object == nil { continue } // TODO: use info.VersionedObject as the value? switch obj := info.Object.(type) { case *extensions.ThirdPartyResourceData: objects = append(objects, &runtime.Unknown{Raw: obj.Data}) continue } // objects that are not part of api.Scheme must be converted to JSON // TODO: convert to map[string]interface{}, attach to runtime.Unknown? if !version.Empty() { if _, _, err := api.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) { // TODO: ideally this would encode to version, but we don't expose multiple codecs here. data, err := runtime.Encode(encoder, info.Object) if err != nil { return nil, err } // TODO: Set ContentEncoding and ContentType. objects = append(objects, &runtime.Unknown{Raw: data}) continue } } converted, err := TryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion()) if err != nil { return nil, err } objects = append(objects, converted) } return objects, nil }
// Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then // load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, the raw data will be // extracted and no decoding will be performed. If into is not registered with the typer, then the object will be straight decoded using // normal JSON/YAML unmarshalling. If into is provided and the original data is not fully qualified with kind/version/group, the type of // the into will be used to alter the returned gvk. On success or most errors, the method will return the calculated schema kind. func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { if versioned, ok := into.(*runtime.VersionedObjects); ok { into = versioned.Last() obj, actual, err := s.Decode(originalData, gvk, into) if err != nil { return nil, actual, err } versioned.Objects = []runtime.Object{obj} return versioned, actual, nil } data := originalData if s.yaml { altered, err := yaml.YAMLToJSON(data) if err != nil { return nil, nil, err } data = altered } actual, err := s.meta.Interpret(data) if err != nil { return nil, nil, err } if gvk != nil { // apply kind and version defaulting from provided default if len(actual.Kind) == 0 { actual.Kind = gvk.Kind } if len(actual.Version) == 0 && len(actual.Group) == 0 { actual.Group = gvk.Group actual.Version = gvk.Version } if len(actual.Version) == 0 && actual.Group == gvk.Group { actual.Version = gvk.Version } } if unk, ok := into.(*runtime.Unknown); ok && unk != nil { unk.Raw = originalData unk.ContentType = runtime.ContentTypeJSON unk.GetObjectKind().SetGroupVersionKind(*actual) return unk, actual, nil } if into != nil { types, _, err := s.typer.ObjectKinds(into) switch { case runtime.IsNotRegisteredError(err): if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(into); err != nil { return nil, actual, err } return into, actual, nil case err != nil: return nil, actual, err default: typed := types[0] if len(actual.Kind) == 0 { actual.Kind = typed.Kind } if len(actual.Version) == 0 && len(actual.Group) == 0 { actual.Group = typed.Group actual.Version = typed.Version } if len(actual.Version) == 0 && actual.Group == typed.Group { actual.Version = typed.Version } } } if len(actual.Kind) == 0 { return nil, actual, runtime.NewMissingKindErr(string(originalData)) } if len(actual.Version) == 0 { return nil, actual, runtime.NewMissingVersionErr(string(originalData)) } // use the target if necessary obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into) if err != nil { return nil, actual, err } if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(obj); err != nil { return nil, actual, err } return obj, actual, nil }
// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default // gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, // the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will // be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is // not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most // errors, the method will return the calculated schema kind. func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { if versioned, ok := into.(*runtime.VersionedObjects); ok { into = versioned.Last() obj, actual, err := s.Decode(originalData, gvk, into) if err != nil { return nil, actual, err } // the last item in versioned becomes into, so if versioned was not originally empty we reset the object // array so the first position is the decoded object and the second position is the outermost object. // if there were no objects in the versioned list passed to us, only add ourselves. if into != nil && into != obj { versioned.Objects = []runtime.Object{obj, into} } else { versioned.Objects = []runtime.Object{obj} } return versioned, actual, err } prefixLen := len(s.prefix) switch { case len(originalData) == 0: // TODO: treat like decoding {} from JSON with defaulting return nil, nil, fmt.Errorf("empty data") case len(originalData) < prefixLen || !bytes.Equal(s.prefix, originalData[:prefixLen]): return nil, nil, fmt.Errorf("provided data does not appear to be a protobuf message, expected prefix %v", s.prefix) case len(originalData) == prefixLen: // TODO: treat like decoding {} from JSON with defaulting return nil, nil, fmt.Errorf("empty body") } data := originalData[prefixLen:] unk := runtime.Unknown{} if err := unk.Unmarshal(data); err != nil { return nil, nil, err } actual := unk.GroupVersionKind() copyKindDefaults(&actual, gvk) if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { *intoUnknown = unk if ok, _, _ := s.RecognizesData(bytes.NewBuffer(unk.Raw)); ok { intoUnknown.ContentType = s.contentType } return intoUnknown, &actual, nil } if into != nil { types, _, err := s.typer.ObjectKinds(into) switch { case runtime.IsNotRegisteredError(err): pb, ok := into.(proto.Message) if !ok { return nil, &actual, errNotMarshalable{reflect.TypeOf(into)} } if err := proto.Unmarshal(unk.Raw, pb); err != nil { return nil, &actual, err } return into, &actual, nil case err != nil: return nil, &actual, err default: copyKindDefaults(&actual, &types[0]) // if the result of defaulting did not set a version or group, ensure that at least group is set // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group // of into is set if there is no better information from the caller or object. if len(actual.Version) == 0 && len(actual.Group) == 0 { actual.Group = types[0].Group } } } if len(actual.Kind) == 0 { return nil, &actual, runtime.NewMissingKindErr(fmt.Sprintf("%#v", unk.TypeMeta)) } if len(actual.Version) == 0 { return nil, &actual, runtime.NewMissingVersionErr(fmt.Sprintf("%#v", unk.TypeMeta)) } return unmarshalToObject(s.typer, s.creater, &actual, into, unk.Raw) }
// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default // gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, // the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will // be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is // not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most // errors, the method will return the calculated schema kind. func (s *RawSerializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { if into == nil { return nil, nil, fmt.Errorf("this serializer requires an object to decode into: %#v", s) } if versioned, ok := into.(*runtime.VersionedObjects); ok { into = versioned.Last() obj, actual, err := s.Decode(originalData, gvk, into) if err != nil { return nil, actual, err } if into != nil && into != obj { versioned.Objects = []runtime.Object{obj, into} } else { versioned.Objects = []runtime.Object{obj} } return versioned, actual, err } if len(originalData) == 0 { // TODO: treat like decoding {} from JSON with defaulting return nil, nil, fmt.Errorf("empty data") } data := originalData actual := &schema.GroupVersionKind{} copyKindDefaults(actual, gvk) if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { intoUnknown.Raw = data intoUnknown.ContentEncoding = "" intoUnknown.ContentType = s.contentType intoUnknown.SetGroupVersionKind(*actual) return intoUnknown, actual, nil } types, _, err := s.typer.ObjectKinds(into) switch { case runtime.IsNotRegisteredError(err): pb, ok := into.(proto.Message) if !ok { return nil, actual, errNotMarshalable{reflect.TypeOf(into)} } if err := proto.Unmarshal(data, pb); err != nil { return nil, actual, err } return into, actual, nil case err != nil: return nil, actual, err default: copyKindDefaults(actual, &types[0]) // if the result of defaulting did not set a version or group, ensure that at least group is set // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group // of into is set if there is no better information from the caller or object. if len(actual.Version) == 0 && len(actual.Group) == 0 { actual.Group = types[0].Group } } if len(actual.Kind) == 0 { return nil, actual, runtime.NewMissingKindErr("<protobuf encoded body - must provide default type>") } if len(actual.Version) == 0 { return nil, actual, runtime.NewMissingVersionErr("<protobuf encoded body - must provide default type>") } return unmarshalToObject(s.typer, s.creater, actual, into, data) }
func TestConvertToVersion(t *testing.T) { testCases := []struct { scheme *runtime.Scheme in runtime.Object gv runtime.GroupVersioner same bool out runtime.Object errFn func(error) bool }{ // errors if the type is not registered in the scheme { scheme: GetTestScheme(), in: &UnknownType{}, errFn: func(err error) bool { return err != nil && runtime.IsNotRegisteredError(err) }, }, // errors if the group versioner returns no target { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{}, errFn: func(err error) bool { return err != nil && strings.Contains(err.Error(), "is not suitable for converting") }, }, // converts to internal { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersion{Version: "__internal"}, out: &TestType1{A: "test"}, }, // prefers the best match { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "__internal"}, {Version: "v1"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // unversioned type returned as-is { scheme: GetTestScheme(), in: &UnversionedType{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}}, same: true, out: &UnversionedType{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, A: "test", }, }, // unversioned type returned when not included in the target types { scheme: GetTestScheme(), in: &UnversionedType{A: "test"}, gv: schema.GroupVersions{{Group: "other", Version: "v2"}}, same: true, out: &UnversionedType{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, A: "test", }, }, // detected as already being in the target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // detected as already being in the first target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}, {Version: "__internal"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // detected as already being in the first target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}, {Version: "__internal"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (1/3): different kind { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (2/3): different gv { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Group: "custom", Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (3/3): different gvk { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Group: "custom", Version: "v1", Kind: "TestType5"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner is unable to find a match when kind AND group don't match (there is no TestType1 kind in group "other", and no kind "TestType5" in the default group) { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "custom", Version: "v1"}, schema.GroupKind{Group: "other"}, schema.GroupKind{Kind: "TestType5"}), errFn: func(err error) bool { return err != nil && strings.Contains(err.Error(), "is not suitable for converting") }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // group versioner can choose a particular target kind for a given input when kind is the same across group versions { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Version: "v1", Kind: "TestType3"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, A: "test", }, }, // group versioner can choose a different kind { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType5", Group: "custom", Version: "v1"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, A: "test", }, }, } for i, test := range testCases { original, _ := test.scheme.DeepCopy(test.in) out, err := test.scheme.ConvertToVersion(test.in, test.gv) switch { case test.errFn != nil: if !test.errFn(err) { t.Errorf("%d: unexpected error: %v", i, err) } continue case err != nil: t.Errorf("%d: unexpected error: %v", i, err) continue } if out == test.in { t.Errorf("%d: ConvertToVersion should always copy out: %#v", i, out) continue } if test.same { if !reflect.DeepEqual(original, test.in) { t.Errorf("%d: unexpected mutation of input: %s", i, diff.ObjectReflectDiff(original, test.in)) continue } if !reflect.DeepEqual(out, test.out) { t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out)) continue } unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) continue } if !reflect.DeepEqual(unsafe, test.out) { t.Errorf("%d: unexpected unsafe: %s", i, diff.ObjectReflectDiff(unsafe, test.out)) continue } if unsafe != test.in { t.Errorf("%d: UnsafeConvertToVersion should return same object: %#v", i, unsafe) continue } continue } if !reflect.DeepEqual(out, test.out) { t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out)) continue } } }
// TODO: Have policies be created via an API call and stored in REST storage. func NewFromFile(path string) (policyList, error) { // File format is one map per line. This allows easy concatentation of files, // comments in files, and identification of errors by line number. file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() scanner := bufio.NewScanner(file) pl := make(policyList, 0) decoder := api.Codecs.UniversalDecoder() i := 0 unversionedLines := 0 for scanner.Scan() { i++ p := &api.Policy{} b := scanner.Bytes() // skip comment lines and blank lines trimmed := strings.TrimSpace(string(b)) if len(trimmed) == 0 || strings.HasPrefix(trimmed, "#") { continue } decodedObj, _, err := decoder.Decode(b, nil, nil) if err != nil { if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) { return nil, policyLoadError{path, i, b, err} } unversionedLines++ // Migrate unversioned policy object oldPolicy := &v0.Policy{} if err := runtime.DecodeInto(decoder, b, oldPolicy); err != nil { return nil, policyLoadError{path, i, b, err} } if err := api.Scheme.Convert(oldPolicy, p, nil); err != nil { return nil, policyLoadError{path, i, b, err} } pl = append(pl, p) continue } decodedPolicy, ok := decodedObj.(*api.Policy) if !ok { return nil, policyLoadError{path, i, b, fmt.Errorf("unrecognized object: %#v", decodedObj)} } pl = append(pl, decodedPolicy) } if unversionedLines > 0 { glog.Warningf("Policy file %s contained unversioned rules. See docs/admin/authorization.md#abac-mode for ABAC file format details.", path) } if err := scanner.Err(); err != nil { return nil, policyLoadError{path, -1, nil, err} } return pl, nil }