mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Convert List query parameters via object conversion
Convert url.Values -> an object, with appropriate versioning. ListOptions should also expose parameter names to swagger.
This commit is contained in:
parent
a2801a5a18
commit
1618c39a46
@ -43,6 +43,16 @@ kube::util::wait_for_url() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Create a temp dir that'll be deleted at the end of this bash session.
|
||||
#
|
||||
# Vars set:
|
||||
# KUBE_TEMP
|
||||
kube::util::ensure-temp-dir() {
|
||||
if [[ -z ${KUBE_TEMP-} ]]; then
|
||||
KUBE_TEMP=$(mktemp -d -t kubernetes.XXXXXX)
|
||||
fi
|
||||
}
|
||||
|
||||
# This figures out the host platform without relying on golang. We need this as
|
||||
# we don't want a golang install to be a prerequisite to building yet we need
|
||||
# this info to figure out where the final binaries are placed.
|
||||
|
@ -33,12 +33,14 @@ function cleanup()
|
||||
[[ -n ${PROXY_PID-} ]] && kill ${PROXY_PID} 1>&2 2>/dev/null
|
||||
|
||||
kube::etcd::cleanup
|
||||
rm -rf "${KUBE_TEMP}"
|
||||
|
||||
kube::log::status "Clean up complete"
|
||||
}
|
||||
|
||||
trap cleanup EXIT SIGINT
|
||||
|
||||
kube::util::ensure-temp-dir
|
||||
kube::etcd::start
|
||||
|
||||
ETCD_HOST=${ETCD_HOST:-127.0.0.1}
|
||||
@ -533,6 +535,7 @@ __EOF__
|
||||
|
||||
kube::test::describe_object_assert nodes "127.0.0.1" "Name:" "Labels:" "CreationTimestamp:" "Conditions:" "Addresses:" "Capacity:" "Pods:"
|
||||
|
||||
|
||||
###########
|
||||
# Minions #
|
||||
###########
|
||||
@ -548,6 +551,7 @@ __EOF__
|
||||
kube::test::describe_object_assert minions "127.0.0.1" "Name:" "Conditions:" "Addresses:" "Capacity:" "Pods:"
|
||||
fi
|
||||
|
||||
|
||||
#####################
|
||||
# Retrieve multiple #
|
||||
#####################
|
||||
@ -555,6 +559,19 @@ __EOF__
|
||||
kube::log::status "Testing kubectl(${version}:multiget)"
|
||||
kube::test::get_object_assert 'nodes/127.0.0.1 service/kubernetes' "{{range.items}}{{.$id_field}}:{{end}}" '127.0.0.1:kubernetes:'
|
||||
|
||||
|
||||
###########
|
||||
# Swagger #
|
||||
###########
|
||||
|
||||
if [[ -n "${version}" ]]; then
|
||||
# Verify schema
|
||||
file="${KUBE_TEMP}/schema-${version}.json"
|
||||
curl -s "http://127.0.0.1:${API_PORT}/swaggerapi/api/${version}" > "${file}"
|
||||
[[ "$(grep "list of returned" "${file}")" ]]
|
||||
[[ "$(grep "list of pods" "${file}")" ]]
|
||||
fi
|
||||
|
||||
kube::test::clear_all
|
||||
done
|
||||
|
||||
|
@ -19,6 +19,8 @@ package api
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
@ -28,12 +30,48 @@ import (
|
||||
var Codec = runtime.CodecFor(Scheme, "")
|
||||
|
||||
func init() {
|
||||
Scheme.AddDefaultingFuncs(
|
||||
func(obj *ListOptions) {
|
||||
obj.LabelSelector = labels.Everything()
|
||||
obj.FieldSelector = fields.Everything()
|
||||
},
|
||||
)
|
||||
Scheme.AddConversionFuncs(
|
||||
func(in *util.Time, out *util.Time, s conversion.Scope) error {
|
||||
// Cannot deep copy these, because time.Time has unexported fields.
|
||||
*out = *in
|
||||
return nil
|
||||
},
|
||||
func(in *string, out *labels.Selector, s conversion.Scope) error {
|
||||
selector, err := labels.Parse(*in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*out = selector
|
||||
return nil
|
||||
},
|
||||
func(in *string, out *fields.Selector, s conversion.Scope) error {
|
||||
selector, err := fields.ParseSelector(*in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*out = selector
|
||||
return nil
|
||||
},
|
||||
func(in *labels.Selector, out *string, s conversion.Scope) error {
|
||||
if *in == nil {
|
||||
return nil
|
||||
}
|
||||
*out = (*in).String()
|
||||
return nil
|
||||
},
|
||||
func(in *fields.Selector, out *string, s conversion.Scope) error {
|
||||
if *in == nil {
|
||||
return nil
|
||||
}
|
||||
*out = (*in).String()
|
||||
return nil
|
||||
},
|
||||
func(in *resource.Quantity, out *resource.Quantity, s conversion.Scope) error {
|
||||
// Cannot deep copy these, because inf.Dec has unexported fields.
|
||||
*out = *in.Copy()
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
@ -63,6 +65,12 @@ var Semantic = conversion.EqualitiesOrDie(
|
||||
func(a, b util.Time) bool {
|
||||
return a.UTC() == b.UTC()
|
||||
},
|
||||
func(a, b labels.Selector) bool {
|
||||
return a.String() == b.String()
|
||||
},
|
||||
func(a, b fields.Selector) bool {
|
||||
return a.String() == b.String()
|
||||
},
|
||||
)
|
||||
|
||||
var standardResources = util.NewStringSet(
|
||||
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: move everything in this file to pkg/api/rest
|
||||
package meta
|
||||
|
||||
import (
|
||||
|
@ -38,10 +38,18 @@ func (fakeCodec) DecodeInto([]byte, runtime.Object) error {
|
||||
|
||||
type fakeConvertor struct{}
|
||||
|
||||
func (fakeConvertor) Convert(in, out interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fakeConvertor) ConvertToVersion(in runtime.Object, _ string) (runtime.Object, error) {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (fakeConvertor) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
|
||||
return label, value, nil
|
||||
}
|
||||
|
||||
var validCodec = fakeCodec{}
|
||||
var validAccessor = resourceAccessor{}
|
||||
var validConvertor = fakeConvertor{}
|
||||
|
@ -52,11 +52,12 @@ func init() {
|
||||
&NamespaceList{},
|
||||
&Secret{},
|
||||
&SecretList{},
|
||||
&DeleteOptions{},
|
||||
&PersistentVolume{},
|
||||
&PersistentVolumeList{},
|
||||
&PersistentVolumeClaim{},
|
||||
&PersistentVolumeClaimList{},
|
||||
&DeleteOptions{},
|
||||
&ListOptions{},
|
||||
)
|
||||
// Legacy names are supported
|
||||
Scheme.AddKnownTypeWithName("", "Minion", &Node{})
|
||||
@ -90,8 +91,9 @@ func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
func (*Secret) IsAnAPIObject() {}
|
||||
func (*SecretList) IsAnAPIObject() {}
|
||||
func (*DeleteOptions) IsAnAPIObject() {}
|
||||
func (*PersistentVolume) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeList) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
||||
func (*DeleteOptions) IsAnAPIObject() {}
|
||||
func (*ListOptions) IsAnAPIObject() {}
|
||||
|
@ -129,7 +129,7 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
|
||||
var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList")
|
||||
var nonInternalRoundTrippableTypes = util.NewStringSet("List")
|
||||
var nonInternalRoundTrippableTypes = util.NewStringSet("List", "ListOptions")
|
||||
|
||||
func TestRoundTripTypes(t *testing.T) {
|
||||
// api.Scheme.Log(t)
|
||||
|
@ -24,6 +24,8 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
@ -88,6 +90,11 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
|
||||
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
|
||||
j.SelfLink = c.RandString()
|
||||
},
|
||||
func(j *api.ListOptions, c fuzz.Continue) {
|
||||
// TODO: add some parsing
|
||||
j.LabelSelector, _ = labels.Parse("a=b")
|
||||
j.FieldSelector, _ = fields.ParseSelector("a=b")
|
||||
},
|
||||
func(j *api.PodPhase, c fuzz.Continue) {
|
||||
statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown}
|
||||
*j = statuses[c.Rand.Intn(len(statuses))]
|
||||
|
@ -18,6 +18,8 @@ package api
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
@ -1168,6 +1170,16 @@ type DeleteOptions struct {
|
||||
GracePeriodSeconds *int64 `json:"gracePeriodSeconds"`
|
||||
}
|
||||
|
||||
// ListOptions is the query options to a standard REST list call
|
||||
type ListOptions struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// A selector based on labels
|
||||
LabelSelector labels.Selector
|
||||
// A selector based on fields
|
||||
FieldSelector fields.Selector
|
||||
}
|
||||
|
||||
// Status is a return value for calls that don't return other objects.
|
||||
// TODO: this could go in apiserver, but I'm including it here so clients needn't
|
||||
// import both.
|
||||
|
@ -40,18 +40,20 @@ func PreV1Beta3(version string) bool {
|
||||
return version == "v1beta1" || version == "v1beta2"
|
||||
}
|
||||
|
||||
// TODO: remove me when watch is refactored
|
||||
func LabelSelectorQueryParam(version string) string {
|
||||
if PreV1Beta3(version) {
|
||||
return "labels"
|
||||
}
|
||||
return "label-selector"
|
||||
return "labelSelector"
|
||||
}
|
||||
|
||||
// TODO: remove me when watch is refactored
|
||||
func FieldSelectorQueryParam(version string) string {
|
||||
if PreV1Beta3(version) {
|
||||
return "fields"
|
||||
}
|
||||
return "field-selector"
|
||||
return "fieldSelector"
|
||||
}
|
||||
|
||||
// String returns available api versions as a human-friendly version string.
|
||||
|
@ -1494,7 +1494,7 @@ func init() {
|
||||
}
|
||||
|
||||
// Add field conversion funcs.
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "pods",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Pod",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "name":
|
||||
@ -1514,7 +1514,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "replicationControllers",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "ReplicationController",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "name":
|
||||
@ -1529,7 +1529,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "events",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Event",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "involvedObject.kind",
|
||||
@ -1551,7 +1551,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Namespace",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "status.phase":
|
||||
|
@ -59,11 +59,12 @@ func init() {
|
||||
&NamespaceList{},
|
||||
&Secret{},
|
||||
&SecretList{},
|
||||
&DeleteOptions{},
|
||||
&PersistentVolume{},
|
||||
&PersistentVolumeList{},
|
||||
&PersistentVolumeClaim{},
|
||||
&PersistentVolumeClaimList{},
|
||||
&DeleteOptions{},
|
||||
&ListOptions{},
|
||||
)
|
||||
// Future names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
|
||||
@ -97,8 +98,9 @@ func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
func (*Secret) IsAnAPIObject() {}
|
||||
func (*SecretList) IsAnAPIObject() {}
|
||||
func (*DeleteOptions) IsAnAPIObject() {}
|
||||
func (*PersistentVolume) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeList) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
||||
func (*DeleteOptions) IsAnAPIObject() {}
|
||||
func (*ListOptions) IsAnAPIObject() {}
|
||||
|
@ -985,6 +985,16 @@ type DeleteOptions struct {
|
||||
GracePeriodSeconds *int64 `json:"gracePeriodSeconds" description:"the duration in seconds to wait before deleting this object; defaults to a per object value if not specified; zero means delete immediately"`
|
||||
}
|
||||
|
||||
// ListOptions is the query options to a standard REST list call
|
||||
type ListOptions struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// A selector based on labels
|
||||
LabelSelector string `json:"labels" description:"a selector to restrict the list of returned objects by their labels; defaults to everything"`
|
||||
// A selector based on fields
|
||||
FieldSelector string `json:"fields" description:"a selector to restrict the list of returned objects by their fields; defaults to everything"`
|
||||
}
|
||||
|
||||
// Status is a return value for calls that don't return other objects.
|
||||
// TODO: this could go in apiserver, but I'm including it here so clients needn't
|
||||
// import both.
|
||||
|
@ -1420,7 +1420,7 @@ func init() {
|
||||
}
|
||||
|
||||
// Add field conversion funcs.
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "pods",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "Pod",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "name":
|
||||
@ -1440,7 +1440,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "replicationControllers",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "ReplicationController",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "name":
|
||||
@ -1455,7 +1455,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "events",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "Event",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "involvedObject.kind",
|
||||
@ -1477,7 +1477,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Namespace",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "status.phase":
|
||||
|
@ -59,11 +59,12 @@ func init() {
|
||||
&NamespaceList{},
|
||||
&Secret{},
|
||||
&SecretList{},
|
||||
&DeleteOptions{},
|
||||
&PersistentVolume{},
|
||||
&PersistentVolumeList{},
|
||||
&PersistentVolumeClaim{},
|
||||
&PersistentVolumeClaimList{},
|
||||
&DeleteOptions{},
|
||||
&ListOptions{},
|
||||
)
|
||||
// Future names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
|
||||
@ -102,3 +103,4 @@ func (*PersistentVolumeList) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
||||
func (*DeleteOptions) IsAnAPIObject() {}
|
||||
func (*ListOptions) IsAnAPIObject() {}
|
||||
|
@ -999,6 +999,16 @@ type DeleteOptions struct {
|
||||
GracePeriodSeconds *int64 `json:"gracePeriodSeconds" description:"the duration in seconds to wait before deleting this object; defaults to a per object value if not specified; zero means delete immediately"`
|
||||
}
|
||||
|
||||
// ListOptions is the query options to a standard REST list call
|
||||
type ListOptions struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// A selector based on labels
|
||||
LabelSelector string `json:"labels" description:"a selector to restrict the list of returned objects by their labels; defaults to everything"`
|
||||
// A selector based on fields
|
||||
FieldSelector string `json:"fields" description:"a selector to restrict the list of returned objects by their fields; defaults to everything"`
|
||||
}
|
||||
|
||||
// Status is a return value for calls that don't return other objects.
|
||||
// TODO: this could go in apiserver, but I'm including it here so clients needn't
|
||||
// import both.
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
|
||||
func init() {
|
||||
// Add field conversion funcs.
|
||||
err := newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "pods",
|
||||
err := newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "Pod",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "name",
|
||||
@ -39,7 +39,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "replicationControllers",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "ReplicationController",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "name":
|
||||
@ -54,7 +54,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "events",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "Event",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "involvedObject.kind",
|
||||
@ -75,7 +75,7 @@ func init() {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
|
||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Namespace",
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "status.phase":
|
||||
|
@ -53,11 +53,12 @@ func init() {
|
||||
&NamespaceList{},
|
||||
&Secret{},
|
||||
&SecretList{},
|
||||
&DeleteOptions{},
|
||||
&PersistentVolume{},
|
||||
&PersistentVolumeList{},
|
||||
&PersistentVolumeClaim{},
|
||||
&PersistentVolumeClaimList{},
|
||||
&DeleteOptions{},
|
||||
&ListOptions{},
|
||||
)
|
||||
// Legacy names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
|
||||
@ -96,3 +97,4 @@ func (*PersistentVolumeList) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
||||
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
||||
func (*DeleteOptions) IsAnAPIObject() {}
|
||||
func (*ListOptions) IsAnAPIObject() {}
|
||||
|
@ -1158,6 +1158,16 @@ type DeleteOptions struct {
|
||||
GracePeriodSeconds *int64 `json:"gracePeriodSeconds" description:"the duration in seconds to wait before deleting this object; defaults to a per object value if not specified; zero means delete immediately"`
|
||||
}
|
||||
|
||||
// ListOptions is the query options to a standard REST list call
|
||||
type ListOptions struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// A selector based on labels
|
||||
LabelSelector string `json:"labelSelector" description:"a selector to restrict the list of returned objects by their labels; defaults to everything"`
|
||||
// A selector based on fields
|
||||
FieldSelector string `json:"fieldSelector" description:"a selector to restrict the list of returned objects by their fields; defaults to everything"`
|
||||
}
|
||||
|
||||
// Status is a return value for calls that don't return other objects.
|
||||
type Status struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
@ -60,10 +61,12 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
|
||||
|
||||
// Initialize the custom handlers.
|
||||
watchHandler := (&WatchHandler{
|
||||
storage: a.group.Storage,
|
||||
codec: a.group.Codec,
|
||||
linker: a.group.Linker,
|
||||
info: a.info,
|
||||
storage: a.group.Storage,
|
||||
mapper: a.group.Mapper,
|
||||
convertor: a.group.Convertor,
|
||||
codec: a.group.Codec,
|
||||
linker: a.group.Linker,
|
||||
info: a.info,
|
||||
})
|
||||
redirectHandler := (&RedirectHandler{a.group.Storage, a.group.Codec, a.group.Context, a.info})
|
||||
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.Storage, a.group.Codec, a.group.Context, a.info})
|
||||
@ -99,6 +102,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
admit := a.group.Admit
|
||||
context := a.group.Context
|
||||
|
||||
serverVersion := a.group.ServerVersion
|
||||
if len(serverVersion) == 0 {
|
||||
serverVersion = a.group.Version
|
||||
}
|
||||
|
||||
var resource, subresource string
|
||||
switch parts := strings.Split(path, "/"); len(parts) {
|
||||
case 2:
|
||||
@ -152,10 +160,15 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
storageMeta = defaultStorageMetadata{}
|
||||
}
|
||||
|
||||
versionedListOptions, err := a.group.Creater.New(serverVersion, "ListOptions")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var versionedDeleterObject runtime.Object
|
||||
switch {
|
||||
case isGracefulDeleter:
|
||||
object, err := a.group.Creater.New(a.group.Version, "DeleteOptions")
|
||||
object, err := a.group.Creater.New(serverVersion, "DeleteOptions")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -288,11 +301,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
// test/integration/auth_test.go is currently the most comprehensive status code test
|
||||
|
||||
reqScope := RequestScope{
|
||||
ContextFunc: ctxFn,
|
||||
Codec: mapping.Codec,
|
||||
APIVersion: a.group.Version,
|
||||
Resource: resource,
|
||||
Kind: kind,
|
||||
ContextFunc: ctxFn,
|
||||
Creater: a.group.Creater,
|
||||
Convertor: a.group.Convertor,
|
||||
Codec: mapping.Codec,
|
||||
APIVersion: a.group.Version,
|
||||
ServerAPIVersion: serverVersion,
|
||||
Resource: resource,
|
||||
Kind: kind,
|
||||
}
|
||||
for _, action := range actions {
|
||||
reqScope.Namer = action.Namer
|
||||
@ -314,6 +330,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Operation("list" + kind).
|
||||
Produces("application/json").
|
||||
Writes(versionedList)
|
||||
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "PUT": // Update a resource.
|
||||
@ -651,6 +670,39 @@ func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
|
||||
}
|
||||
}
|
||||
|
||||
func addObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj runtime.Object) error {
|
||||
sv, err := conversion.EnforcePtr(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st := sv.Type()
|
||||
switch st.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
name := st.Field(i).Name
|
||||
sf, ok := st.FieldByName(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch sf.Type.Kind() {
|
||||
case reflect.Interface, reflect.Struct:
|
||||
default:
|
||||
jsonTag := sf.Tag.Get("json")
|
||||
if len(jsonTag) == 0 {
|
||||
continue
|
||||
}
|
||||
jsonName := strings.SplitN(jsonTag, ",", 2)[0]
|
||||
if len(jsonName) == 0 {
|
||||
continue
|
||||
}
|
||||
desc := sf.Tag.Get("description")
|
||||
route.Param(ws.QueryParameter(jsonName, desc).DataType(sf.Type.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// defaultStorageMetadata provides default answers to rest.StorageMetadata.
|
||||
type defaultStorageMetadata struct{}
|
||||
|
||||
|
@ -102,12 +102,19 @@ type APIGroupVersion struct {
|
||||
Root string
|
||||
Version string
|
||||
|
||||
// ServerVersion controls the Kubernetes APIVersion used for common objects in the apiserver
|
||||
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
|
||||
// define a version "v1beta1" but want to use the Kubernetes "v1beta3" internal objects. If
|
||||
// empty, defaults to Version.
|
||||
ServerVersion string
|
||||
|
||||
Mapper meta.RESTMapper
|
||||
|
||||
Codec runtime.Codec
|
||||
Typer runtime.ObjectTyper
|
||||
Creater runtime.ObjectCreater
|
||||
Linker runtime.SelfLinker
|
||||
Codec runtime.Codec
|
||||
Typer runtime.ObjectTyper
|
||||
Creater runtime.ObjectCreater
|
||||
Convertor runtime.ObjectConvertor
|
||||
Linker runtime.SelfLinker
|
||||
|
||||
Admit admission.Interface
|
||||
Context api.RequestContextMapper
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
@ -96,11 +97,10 @@ func init() {
|
||||
// api.Status is returned in errors
|
||||
|
||||
// "internal" version
|
||||
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{},
|
||||
&api.Status{})
|
||||
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{}, &api.Status{}, &api.ListOptions{})
|
||||
// "version" version
|
||||
// TODO: Use versioned api objects?
|
||||
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{}, &api.DeleteOptions{}, &api.Status{})
|
||||
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{}, &v1beta1.DeleteOptions{}, &v1beta1.Status{}, &v1beta1.ListOptions{})
|
||||
|
||||
nsMapper := newMapper()
|
||||
legacyNsMapper := newMapper()
|
||||
@ -156,10 +156,11 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
|
||||
Root: "/api",
|
||||
Version: testVersion,
|
||||
|
||||
Creater: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Codec: codec,
|
||||
Linker: selfLinker,
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Codec: codec,
|
||||
Linker: selfLinker,
|
||||
|
||||
Admit: admissionControl,
|
||||
Context: requestContextMapper,
|
||||
@ -244,6 +245,8 @@ func (storage *SimpleRESTStorage) List(ctx api.Context, label labels.Selector, f
|
||||
result := &SimpleList{
|
||||
Items: storage.list,
|
||||
}
|
||||
storage.requestedLabelSelector = label
|
||||
storage.requestedFieldSelector = field
|
||||
return result, storage.errors["list"]
|
||||
}
|
||||
|
||||
@ -522,15 +525,60 @@ func TestList(t *testing.T) {
|
||||
namespace string
|
||||
selfLink string
|
||||
legacy bool
|
||||
label string
|
||||
field string
|
||||
}{
|
||||
{"/api/version/simple", "", "/api/version/simple?namespace=", true},
|
||||
{"/api/version/simple?namespace=other", "other", "/api/version/simple?namespace=other", true},
|
||||
{
|
||||
url: "/api/version/simple",
|
||||
namespace: "",
|
||||
selfLink: "/api/version/simple?namespace=",
|
||||
legacy: true,
|
||||
},
|
||||
{
|
||||
url: "/api/version/simple?namespace=other",
|
||||
namespace: "other",
|
||||
selfLink: "/api/version/simple?namespace=other",
|
||||
legacy: true,
|
||||
},
|
||||
{
|
||||
url: "/api/version/simple?namespace=other&labels=a%3Db&fields=c%3Dd",
|
||||
namespace: "other",
|
||||
selfLink: "/api/version/simple?namespace=other",
|
||||
legacy: true,
|
||||
label: "a=b",
|
||||
field: "c=d",
|
||||
},
|
||||
// list items across all namespaces
|
||||
{"/api/version/simple?namespace=", "", "/api/version/simple?namespace=", true},
|
||||
{"/api/version/namespaces/default/simple", "default", "/api/version/namespaces/default/simple", false},
|
||||
{"/api/version/namespaces/other/simple", "other", "/api/version/namespaces/other/simple", false},
|
||||
{
|
||||
url: "/api/version/simple?namespace=",
|
||||
namespace: "",
|
||||
selfLink: "/api/version/simple?namespace=",
|
||||
legacy: true,
|
||||
},
|
||||
// list items in a namespace, v1beta3+
|
||||
{
|
||||
url: "/api/version/namespaces/default/simple",
|
||||
namespace: "default",
|
||||
selfLink: "/api/version/namespaces/default/simple",
|
||||
},
|
||||
{
|
||||
url: "/api/version/namespaces/other/simple",
|
||||
namespace: "other",
|
||||
selfLink: "/api/version/namespaces/other/simple",
|
||||
},
|
||||
{
|
||||
url: "/api/version/namespaces/other/simple?labels=a%3Db&fields=c%3Dd",
|
||||
namespace: "other",
|
||||
selfLink: "/api/version/namespaces/other/simple",
|
||||
label: "a=b",
|
||||
field: "c=d",
|
||||
},
|
||||
// list items across all namespaces
|
||||
{"/api/version/simple", "", "/api/version/simple", false},
|
||||
{
|
||||
url: "/api/version/simple",
|
||||
namespace: "",
|
||||
selfLink: "/api/version/simple",
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
storage := map[string]rest.Storage{}
|
||||
@ -567,6 +615,12 @@ func TestList(t *testing.T) {
|
||||
} else if simpleStorage.actualNamespace != testCase.namespace {
|
||||
t.Errorf("%d: unexpected resource namespace: %s", i, simpleStorage.actualNamespace)
|
||||
}
|
||||
if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label {
|
||||
t.Errorf("%d: unexpected label selector: %v", i, simpleStorage.requestedLabelSelector)
|
||||
}
|
||||
if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field {
|
||||
t.Errorf("%d: unexpected field selector: %v", i, simpleStorage.requestedFieldSelector)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,6 @@ package apiserver
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
gpath "path"
|
||||
"time"
|
||||
|
||||
@ -27,8 +26,6 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
@ -64,9 +61,15 @@ type RequestScope struct {
|
||||
Namer ScopeNamer
|
||||
ContextFunc
|
||||
runtime.Codec
|
||||
Creater runtime.ObjectCreater
|
||||
Convertor runtime.ObjectConvertor
|
||||
|
||||
Resource string
|
||||
Kind string
|
||||
APIVersion string
|
||||
|
||||
// The version of apiserver resources to use
|
||||
ServerAPIVersion string
|
||||
}
|
||||
|
||||
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
|
||||
@ -94,24 +97,6 @@ func GetResource(r rest.Getter, scope RequestScope) restful.RouteFunction {
|
||||
}
|
||||
}
|
||||
|
||||
func parseSelectorQueryParams(query url.Values, version, apiResource string) (label labels.Selector, field fields.Selector, err error) {
|
||||
labelString := query.Get(api.LabelSelectorQueryParam(version))
|
||||
label, err = labels.Parse(labelString)
|
||||
if err != nil {
|
||||
return nil, nil, errors.NewBadRequest(fmt.Sprintf("The 'labels' selector parameter (%s) could not be parsed: %v", labelString, err))
|
||||
}
|
||||
|
||||
convertToInternalVersionFunc := func(label, value string) (newLabel, newValue string, err error) {
|
||||
return api.Scheme.ConvertFieldLabel(version, apiResource, label, value)
|
||||
}
|
||||
fieldString := query.Get(api.FieldSelectorQueryParam(version))
|
||||
field, err = fields.ParseAndTransformSelector(fieldString, convertToInternalVersionFunc)
|
||||
if err != nil {
|
||||
return nil, nil, errors.NewBadRequest(fmt.Sprintf("The 'fields' selector parameter (%s) could not be parsed: %v", fieldString, err))
|
||||
}
|
||||
return label, field, nil
|
||||
}
|
||||
|
||||
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object.
|
||||
func ListResource(r rest.Lister, scope RequestScope) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
@ -125,13 +110,38 @@ func ListResource(r rest.Lister, scope RequestScope) restful.RouteFunction {
|
||||
ctx := scope.ContextFunc(req)
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
|
||||
label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), scope.APIVersion, scope.Resource)
|
||||
// TODO: extract me into a method
|
||||
query := req.Request.URL.Query()
|
||||
versioned, err := scope.Creater.New(scope.ServerAPIVersion, "ListOptions")
|
||||
if err != nil {
|
||||
// programmer error
|
||||
errorJSON(err, scope.Codec, w)
|
||||
return
|
||||
}
|
||||
if err := scope.Convertor.Convert(&query, versioned); err != nil {
|
||||
// bad request
|
||||
errorJSON(err, scope.Codec, w)
|
||||
return
|
||||
}
|
||||
out, err := scope.Convertor.ConvertToVersion(versioned, "")
|
||||
if err != nil {
|
||||
// programmer error
|
||||
errorJSON(err, scope.Codec, w)
|
||||
return
|
||||
}
|
||||
opts := *out.(*api.ListOptions)
|
||||
|
||||
// transform fields
|
||||
fn := func(label, value string) (newLabel, newValue string, err error) {
|
||||
return scope.Convertor.ConvertFieldLabel(scope.APIVersion, scope.Kind, label, value)
|
||||
}
|
||||
if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil {
|
||||
// invalid field
|
||||
errorJSON(err, scope.Codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := r.List(ctx, label, field)
|
||||
result, err := r.List(ctx, opts.LabelSelector, opts.FieldSelector)
|
||||
if err != nil {
|
||||
errorJSON(err, scope.Codec, w)
|
||||
return
|
||||
|
@ -19,6 +19,7 @@ package apiserver
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -26,8 +27,11 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
|
||||
@ -36,11 +40,14 @@ import (
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
// TODO: convert me to resthandler custom verb
|
||||
type WatchHandler struct {
|
||||
storage map[string]rest.Storage
|
||||
codec runtime.Codec
|
||||
linker runtime.SelfLinker
|
||||
info *APIRequestInfoResolver
|
||||
storage map[string]rest.Storage
|
||||
mapper meta.RESTMapper
|
||||
convertor runtime.ObjectConvertor
|
||||
codec runtime.Codec
|
||||
linker runtime.SelfLinker
|
||||
info *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
|
||||
@ -96,8 +103,24 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
httpCode = errorJSON(errors.NewMethodNotSupported(requestInfo.Resource, "watch"), h.codec, w)
|
||||
return
|
||||
}
|
||||
kind := requestInfo.Kind
|
||||
if len(kind) == 0 {
|
||||
if _, kind, err = h.mapper.VersionAndKindForResource(apiResource); err != nil {
|
||||
glog.Errorf("No kind found for %s: %v", apiResource, err)
|
||||
}
|
||||
}
|
||||
|
||||
label, field, err := parseSelectorQueryParams(req.URL.Query(), requestInfo.APIVersion, apiResource)
|
||||
scope := RequestScope{
|
||||
Convertor: h.convertor,
|
||||
Kind: kind,
|
||||
Resource: apiResource,
|
||||
APIVersion: requestInfo.APIVersion,
|
||||
// TODO: this must be parameterized per version, and is incorrect for implementors
|
||||
// outside of Kubernetes. Fix by refactoring watch under resthandler as a custome
|
||||
// resource.
|
||||
ServerAPIVersion: requestInfo.APIVersion,
|
||||
}
|
||||
label, field, err := parseSelectorQueryParams(req.URL.Query(), scope)
|
||||
if err != nil {
|
||||
httpCode = errorJSON(err, h.codec, w)
|
||||
return
|
||||
@ -125,6 +148,26 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove when watcher is refactored to fit under api_installer
|
||||
func parseSelectorQueryParams(query url.Values, scope RequestScope) (label labels.Selector, field fields.Selector, err error) {
|
||||
labelString := query.Get(api.LabelSelectorQueryParam(scope.ServerAPIVersion))
|
||||
label, err = labels.Parse(labelString)
|
||||
if err != nil {
|
||||
return nil, nil, errors.NewBadRequest(fmt.Sprintf("The 'labels' selector parameter (%s) could not be parsed: %v", labelString, err))
|
||||
}
|
||||
|
||||
fn := func(label, value string) (newLabel, newValue string, err error) {
|
||||
return scope.Convertor.ConvertFieldLabel(scope.APIVersion, scope.Kind, label, value)
|
||||
}
|
||||
fieldString := query.Get(api.FieldSelectorQueryParam(scope.ServerAPIVersion))
|
||||
field, err = fields.ParseAndTransformSelector(fieldString, fn)
|
||||
if err != nil {
|
||||
return nil, nil, errors.NewBadRequest(fmt.Sprintf("The 'fields' selector parameter (%s) could not be parsed: %v", fieldString, err))
|
||||
}
|
||||
glog.Infof("Found %#v %#v from %v in scope %#v", label, field, query, scope)
|
||||
return label, field, nil
|
||||
}
|
||||
|
||||
// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.
|
||||
type WatchServer struct {
|
||||
watching watch.Interface
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
@ -48,16 +49,24 @@ var watchTestTable = []struct {
|
||||
{watch.Deleted, &Simple{ObjectMeta: api.ObjectMeta{Name: "bar"}}},
|
||||
}
|
||||
|
||||
func init() {
|
||||
mapper.(*meta.DefaultRESTMapper).Add(meta.RESTScopeNamespaceLegacy, "Simple", testVersion, false)
|
||||
api.Scheme.AddFieldLabelConversionFunc(testVersion, "Simple",
|
||||
func(label, value string) (string, string, error) {
|
||||
return label, value, nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestWatchWebsocket(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
_ = rest.Watcher(simpleStorage) // Give compile error if this doesn't work.
|
||||
handler := handle(map[string]rest.Storage{"foo": simpleStorage})
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Scheme = "ws" // Required by websocket, though the server never sees it.
|
||||
dest.Path = "/api/version/watch/foo"
|
||||
dest.Path = "/api/version/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
ws, err := websocket.Dial(dest.String(), "", "http://localhost")
|
||||
@ -103,13 +112,13 @@ func TestWatchWebsocket(t *testing.T) {
|
||||
|
||||
func TestWatchHTTP(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"foo": simpleStorage})
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Path = "/api/version/watch/foo"
|
||||
dest.Path = "/api/version/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
request, err := http.NewRequest("GET", dest.String(), nil)
|
||||
@ -163,17 +172,13 @@ func TestWatchHTTP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWatchParamParsing(t *testing.T) {
|
||||
api.Scheme.AddFieldLabelConversionFunc(testVersion, "foo",
|
||||
func(label, value string) (string, string, error) {
|
||||
return label, value, nil
|
||||
})
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"foo": simpleStorage})
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Path = "/api/" + testVersion + "/watch/foo"
|
||||
dest.Path = "/api/" + testVersion + "/watch/simples"
|
||||
|
||||
table := []struct {
|
||||
rawQuery string
|
||||
@ -238,14 +243,14 @@ func TestWatchParamParsing(t *testing.T) {
|
||||
|
||||
func TestWatchProtocolSelection(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"foo": simpleStorage})
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
defer server.CloseClientConnections()
|
||||
client := http.Client{}
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Path = "/api/version/watch/foo"
|
||||
dest.Path = "/api/version/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
table := []struct {
|
||||
|
@ -572,9 +572,10 @@ func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
|
||||
|
||||
Mapper: latest.RESTMapper,
|
||||
|
||||
Creater: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Linker: latest.SelfLinker,
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Linker: latest.SelfLinker,
|
||||
|
||||
Admit: m.admissionControl,
|
||||
Context: m.requestContextMapper,
|
||||
|
@ -35,7 +35,9 @@ type Codec interface {
|
||||
|
||||
// ObjectConvertor converts an object to a different version.
|
||||
type ObjectConvertor interface {
|
||||
Convert(in, out interface{}) error
|
||||
ConvertToVersion(in Object, outVersion string) (out Object, err error)
|
||||
ConvertFieldLabel(version, kind, label, value string) (string, string, error)
|
||||
}
|
||||
|
||||
// ObjectTyper contains methods for extracting the APIVersion and Kind
|
||||
|
@ -18,6 +18,7 @@ package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||
@ -224,6 +225,9 @@ func NewScheme() *Scheme {
|
||||
if err := s.raw.RegisterInputDefaults(&map[string][]string{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := s.raw.RegisterInputDefaults(&url.Values{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@ -294,13 +298,13 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
|
||||
}
|
||||
|
||||
// AddFieldLabelConversionFunc adds a conversion function to convert field selectors
|
||||
// of the given api resource from the given version to internal version representation.
|
||||
func (s *Scheme) AddFieldLabelConversionFunc(version, apiResource string, conversionFunc FieldLabelConversionFunc) error {
|
||||
// of the given kind from the given version to internal version representation.
|
||||
func (s *Scheme) AddFieldLabelConversionFunc(version, kind string, conversionFunc FieldLabelConversionFunc) error {
|
||||
if s.fieldLabelConversionFuncs[version] == nil {
|
||||
s.fieldLabelConversionFuncs[version] = map[string]FieldLabelConversionFunc{}
|
||||
}
|
||||
|
||||
s.fieldLabelConversionFuncs[version][apiResource] = conversionFunc
|
||||
s.fieldLabelConversionFuncs[version][kind] = conversionFunc
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -326,15 +330,15 @@ func (s *Scheme) Convert(in, out interface{}) error {
|
||||
return s.raw.Convert(in, out)
|
||||
}
|
||||
|
||||
// Converts the given field label and value for an apiResource field selector from
|
||||
// Converts the given field label and value for an kind field selector from
|
||||
// versioned representation to an unversioned one.
|
||||
func (s *Scheme) ConvertFieldLabel(version, apiResource, label, value string) (string, string, error) {
|
||||
func (s *Scheme) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
|
||||
if s.fieldLabelConversionFuncs[version] == nil {
|
||||
return "", "", fmt.Errorf("No conversion function found for version: %s", version)
|
||||
}
|
||||
conversionFunc, ok := s.fieldLabelConversionFuncs[version][apiResource]
|
||||
conversionFunc, ok := s.fieldLabelConversionFuncs[version][kind]
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("No conversion function found for version %s and api resource %s", version, apiResource)
|
||||
return "", "", fmt.Errorf("No conversion function found for version %s and kind %s", version, kind)
|
||||
}
|
||||
return conversionFunc(label, value)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user