1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package reference
18
19import (
20	"errors"
21	"fmt"
22
23	"k8s.io/api/core/v1"
24	"k8s.io/apimachinery/pkg/api/meta"
25	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26	"k8s.io/apimachinery/pkg/runtime"
27)
28
29var (
30	// Errors that could be returned by GetReference.
31	ErrNilObject = errors.New("can't reference a nil object")
32)
33
34// GetReference returns an ObjectReference which refers to the given
35// object, or an error if the object doesn't follow the conventions
36// that would allow this.
37// TODO: should take a meta.Interface see http://issue.k8s.io/7127
38func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*v1.ObjectReference, error) {
39	if obj == nil {
40		return nil, ErrNilObject
41	}
42	if ref, ok := obj.(*v1.ObjectReference); ok {
43		// Don't make a reference to a reference.
44		return ref, nil
45	}
46
47	// An object that implements only List has enough metadata to build a reference
48	var listMeta metav1.Common
49	objectMeta, err := meta.Accessor(obj)
50	if err != nil {
51		listMeta, err = meta.CommonAccessor(obj)
52		if err != nil {
53			return nil, err
54		}
55	} else {
56		listMeta = objectMeta
57	}
58
59	gvk := obj.GetObjectKind().GroupVersionKind()
60
61	// If object meta doesn't contain data about kind and/or version,
62	// we are falling back to scheme.
63	//
64	// TODO: This doesn't work for CRDs, which are not registered in scheme.
65	if gvk.Empty() {
66		gvks, _, err := scheme.ObjectKinds(obj)
67		if err != nil {
68			return nil, err
69		}
70		if len(gvks) == 0 || gvks[0].Empty() {
71			return nil, fmt.Errorf("unexpected gvks registered for object %T: %v", obj, gvks)
72		}
73		// TODO: The same object can be registered for multiple group versions
74		// (although in practise this doesn't seem to be used).
75		// In such case, the version set may not be correct.
76		gvk = gvks[0]
77	}
78
79	kind := gvk.Kind
80	version := gvk.GroupVersion().String()
81
82	// only has list metadata
83	if objectMeta == nil {
84		return &v1.ObjectReference{
85			Kind:            kind,
86			APIVersion:      version,
87			ResourceVersion: listMeta.GetResourceVersion(),
88		}, nil
89	}
90
91	return &v1.ObjectReference{
92		Kind:            kind,
93		APIVersion:      version,
94		Name:            objectMeta.GetName(),
95		Namespace:       objectMeta.GetNamespace(),
96		UID:             objectMeta.GetUID(),
97		ResourceVersion: objectMeta.GetResourceVersion(),
98	}, nil
99}
100
101// GetPartialReference is exactly like GetReference, but allows you to set the FieldPath.
102func GetPartialReference(scheme *runtime.Scheme, obj runtime.Object, fieldPath string) (*v1.ObjectReference, error) {
103	ref, err := GetReference(scheme, obj)
104	if err != nil {
105		return nil, err
106	}
107	ref.FieldPath = fieldPath
108	return ref, nil
109}
110