Merge pull request #5763 from smarterclayton/get_input_parameters_versioned
Expose versioned query parameters and make watch an operation on List
This commit is contained in:
@@ -43,6 +43,16 @@ kube::util::wait_for_url() {
|
|||||||
return 1
|
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
|
# 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
|
# 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.
|
# 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
|
[[ -n ${PROXY_PID-} ]] && kill ${PROXY_PID} 1>&2 2>/dev/null
|
||||||
|
|
||||||
kube::etcd::cleanup
|
kube::etcd::cleanup
|
||||||
|
rm -rf "${KUBE_TEMP}"
|
||||||
|
|
||||||
kube::log::status "Clean up complete"
|
kube::log::status "Clean up complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
trap cleanup EXIT SIGINT
|
trap cleanup EXIT SIGINT
|
||||||
|
|
||||||
|
kube::util::ensure-temp-dir
|
||||||
kube::etcd::start
|
kube::etcd::start
|
||||||
|
|
||||||
ETCD_HOST=${ETCD_HOST:-127.0.0.1}
|
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:"
|
kube::test::describe_object_assert nodes "127.0.0.1" "Name:" "Labels:" "CreationTimestamp:" "Conditions:" "Addresses:" "Capacity:" "Pods:"
|
||||||
|
|
||||||
|
|
||||||
###########
|
###########
|
||||||
# Minions #
|
# Minions #
|
||||||
###########
|
###########
|
||||||
@@ -548,6 +551,7 @@ __EOF__
|
|||||||
kube::test::describe_object_assert minions "127.0.0.1" "Name:" "Conditions:" "Addresses:" "Capacity:" "Pods:"
|
kube::test::describe_object_assert minions "127.0.0.1" "Name:" "Conditions:" "Addresses:" "Capacity:" "Pods:"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
#####################
|
#####################
|
||||||
# Retrieve multiple #
|
# Retrieve multiple #
|
||||||
#####################
|
#####################
|
||||||
@@ -555,6 +559,20 @@ __EOF__
|
|||||||
kube::log::status "Testing kubectl(${version}:multiget)"
|
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:'
|
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}")" ]]
|
||||||
|
[[ "$(grep "watch for changes to the described resources" "${file}")" ]]
|
||||||
|
fi
|
||||||
|
|
||||||
kube::test::clear_all
|
kube::test::clear_all
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@@ -19,6 +19,8 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
"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/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
)
|
)
|
||||||
@@ -28,12 +30,48 @@ import (
|
|||||||
var Codec = runtime.CodecFor(Scheme, "")
|
var Codec = runtime.CodecFor(Scheme, "")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Scheme.AddDefaultingFuncs(
|
||||||
|
func(obj *ListOptions) {
|
||||||
|
obj.LabelSelector = labels.Everything()
|
||||||
|
obj.FieldSelector = fields.Everything()
|
||||||
|
},
|
||||||
|
)
|
||||||
Scheme.AddConversionFuncs(
|
Scheme.AddConversionFuncs(
|
||||||
func(in *util.Time, out *util.Time, s conversion.Scope) error {
|
func(in *util.Time, out *util.Time, s conversion.Scope) error {
|
||||||
// Cannot deep copy these, because time.Time has unexported fields.
|
// Cannot deep copy these, because time.Time has unexported fields.
|
||||||
*out = *in
|
*out = *in
|
||||||
return nil
|
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 {
|
func(in *resource.Quantity, out *resource.Quantity, s conversion.Scope) error {
|
||||||
// Cannot deep copy these, because inf.Dec has unexported fields.
|
// Cannot deep copy these, because inf.Dec has unexported fields.
|
||||||
*out = *in.Copy()
|
*out = *in.Copy()
|
||||||
|
@@ -21,6 +21,8 @@ import (
|
|||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
"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/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
@@ -63,6 +65,12 @@ var Semantic = conversion.EqualitiesOrDie(
|
|||||||
func(a, b util.Time) bool {
|
func(a, b util.Time) bool {
|
||||||
return a.UTC() == b.UTC()
|
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(
|
var standardResources = util.NewStringSet(
|
||||||
|
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO: move everything in this file to pkg/api/rest
|
||||||
package meta
|
package meta
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@@ -38,10 +38,18 @@ func (fakeCodec) DecodeInto([]byte, runtime.Object) error {
|
|||||||
|
|
||||||
type fakeConvertor struct{}
|
type fakeConvertor struct{}
|
||||||
|
|
||||||
|
func (fakeConvertor) Convert(in, out interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (fakeConvertor) ConvertToVersion(in runtime.Object, _ string) (runtime.Object, error) {
|
func (fakeConvertor) ConvertToVersion(in runtime.Object, _ string) (runtime.Object, error) {
|
||||||
return in, nil
|
return in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fakeConvertor) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
|
||||||
|
return label, value, nil
|
||||||
|
}
|
||||||
|
|
||||||
var validCodec = fakeCodec{}
|
var validCodec = fakeCodec{}
|
||||||
var validAccessor = resourceAccessor{}
|
var validAccessor = resourceAccessor{}
|
||||||
var validConvertor = fakeConvertor{}
|
var validConvertor = fakeConvertor{}
|
||||||
|
@@ -52,11 +52,12 @@ func init() {
|
|||||||
&NamespaceList{},
|
&NamespaceList{},
|
||||||
&Secret{},
|
&Secret{},
|
||||||
&SecretList{},
|
&SecretList{},
|
||||||
&DeleteOptions{},
|
|
||||||
&PersistentVolume{},
|
&PersistentVolume{},
|
||||||
&PersistentVolumeList{},
|
&PersistentVolumeList{},
|
||||||
&PersistentVolumeClaim{},
|
&PersistentVolumeClaim{},
|
||||||
&PersistentVolumeClaimList{},
|
&PersistentVolumeClaimList{},
|
||||||
|
&DeleteOptions{},
|
||||||
|
&ListOptions{},
|
||||||
)
|
)
|
||||||
// Legacy names are supported
|
// Legacy names are supported
|
||||||
Scheme.AddKnownTypeWithName("", "Minion", &Node{})
|
Scheme.AddKnownTypeWithName("", "Minion", &Node{})
|
||||||
@@ -90,8 +91,9 @@ func (*Namespace) IsAnAPIObject() {}
|
|||||||
func (*NamespaceList) IsAnAPIObject() {}
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
func (*Secret) IsAnAPIObject() {}
|
func (*Secret) IsAnAPIObject() {}
|
||||||
func (*SecretList) IsAnAPIObject() {}
|
func (*SecretList) IsAnAPIObject() {}
|
||||||
func (*DeleteOptions) IsAnAPIObject() {}
|
|
||||||
func (*PersistentVolume) IsAnAPIObject() {}
|
func (*PersistentVolume) IsAnAPIObject() {}
|
||||||
func (*PersistentVolumeList) IsAnAPIObject() {}
|
func (*PersistentVolumeList) IsAnAPIObject() {}
|
||||||
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
||||||
func (*PersistentVolumeClaimList) 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 nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList")
|
||||||
var nonInternalRoundTrippableTypes = util.NewStringSet("List")
|
var nonInternalRoundTrippableTypes = util.NewStringSet("List", "ListOptions")
|
||||||
|
|
||||||
func TestRoundTripTypes(t *testing.T) {
|
func TestRoundTripTypes(t *testing.T) {
|
||||||
// api.Scheme.Log(t)
|
// api.Scheme.Log(t)
|
||||||
|
@@ -23,6 +23,8 @@ import (
|
|||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
"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/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
@@ -87,6 +89,11 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
|
|||||||
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
|
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
|
||||||
j.SelfLink = c.RandString()
|
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) {
|
func(j *api.PodPhase, c fuzz.Continue) {
|
||||||
statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown}
|
statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown}
|
||||||
*j = statuses[c.Rand.Intn(len(statuses))]
|
*j = statuses[c.Rand.Intn(len(statuses))]
|
||||||
|
@@ -18,6 +18,8 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
"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/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
@@ -1201,6 +1203,21 @@ type DeleteOptions struct {
|
|||||||
GracePeriodSeconds *int64 `json:"gracePeriodSeconds"`
|
GracePeriodSeconds *int64 `json:"gracePeriodSeconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListOptions is the query options to a standard REST list call, and has future support for
|
||||||
|
// watch calls.
|
||||||
|
type ListOptions struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// A selector based on labels
|
||||||
|
LabelSelector labels.Selector
|
||||||
|
// A selector based on fields
|
||||||
|
FieldSelector fields.Selector
|
||||||
|
// If true, watch for changes to this list
|
||||||
|
Watch bool
|
||||||
|
// The resource version to watch (no effect on list yet)
|
||||||
|
ResourceVersion string
|
||||||
|
}
|
||||||
|
|
||||||
// Status is a return value for calls that don't return other objects.
|
// 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
|
// TODO: this could go in apiserver, but I'm including it here so clients needn't
|
||||||
// import both.
|
// import both.
|
||||||
|
@@ -40,18 +40,20 @@ func PreV1Beta3(version string) bool {
|
|||||||
return version == "v1beta1" || version == "v1beta2"
|
return version == "v1beta1" || version == "v1beta2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove me when watch is refactored
|
||||||
func LabelSelectorQueryParam(version string) string {
|
func LabelSelectorQueryParam(version string) string {
|
||||||
if PreV1Beta3(version) {
|
if PreV1Beta3(version) {
|
||||||
return "labels"
|
return "labels"
|
||||||
}
|
}
|
||||||
return "label-selector"
|
return "labelSelector"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove me when watch is refactored
|
||||||
func FieldSelectorQueryParam(version string) string {
|
func FieldSelectorQueryParam(version string) string {
|
||||||
if PreV1Beta3(version) {
|
if PreV1Beta3(version) {
|
||||||
return "fields"
|
return "fields"
|
||||||
}
|
}
|
||||||
return "field-selector"
|
return "fieldSelector"
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns available api versions as a human-friendly version string.
|
// String returns available api versions as a human-friendly version string.
|
||||||
|
@@ -1493,7 +1493,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add field conversion funcs.
|
// Add field conversion funcs.
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "pods",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Pod",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "name":
|
case "name":
|
||||||
@@ -1513,7 +1513,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "replicationControllers",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "ReplicationController",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "name":
|
case "name":
|
||||||
@@ -1528,7 +1528,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "events",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Event",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "involvedObject.kind",
|
case "involvedObject.kind",
|
||||||
@@ -1550,7 +1550,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Namespace",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "status.phase":
|
case "status.phase":
|
||||||
|
@@ -59,11 +59,12 @@ func init() {
|
|||||||
&NamespaceList{},
|
&NamespaceList{},
|
||||||
&Secret{},
|
&Secret{},
|
||||||
&SecretList{},
|
&SecretList{},
|
||||||
&DeleteOptions{},
|
|
||||||
&PersistentVolume{},
|
&PersistentVolume{},
|
||||||
&PersistentVolumeList{},
|
&PersistentVolumeList{},
|
||||||
&PersistentVolumeClaim{},
|
&PersistentVolumeClaim{},
|
||||||
&PersistentVolumeClaimList{},
|
&PersistentVolumeClaimList{},
|
||||||
|
&DeleteOptions{},
|
||||||
|
&ListOptions{},
|
||||||
)
|
)
|
||||||
// Future names are supported
|
// Future names are supported
|
||||||
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
|
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
|
||||||
@@ -97,8 +98,9 @@ func (*Namespace) IsAnAPIObject() {}
|
|||||||
func (*NamespaceList) IsAnAPIObject() {}
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
func (*Secret) IsAnAPIObject() {}
|
func (*Secret) IsAnAPIObject() {}
|
||||||
func (*SecretList) IsAnAPIObject() {}
|
func (*SecretList) IsAnAPIObject() {}
|
||||||
func (*DeleteOptions) IsAnAPIObject() {}
|
|
||||||
func (*PersistentVolume) IsAnAPIObject() {}
|
func (*PersistentVolume) IsAnAPIObject() {}
|
||||||
func (*PersistentVolumeList) IsAnAPIObject() {}
|
func (*PersistentVolumeList) IsAnAPIObject() {}
|
||||||
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
||||||
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
||||||
|
func (*DeleteOptions) IsAnAPIObject() {}
|
||||||
|
func (*ListOptions) IsAnAPIObject() {}
|
||||||
|
@@ -1034,6 +1034,20 @@ 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"`
|
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"`
|
||||||
|
// If true, watch for changes to the selected resources
|
||||||
|
Watch bool `json:"watch" description:"watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion"`
|
||||||
|
// The desired resource version to watch
|
||||||
|
ResourceVersion string `json:"resourceVersion" description:"when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history"`
|
||||||
|
}
|
||||||
|
|
||||||
// Status is a return value for calls that don't return other objects.
|
// 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
|
// TODO: this could go in apiserver, but I'm including it here so clients needn't
|
||||||
// import both.
|
// import both.
|
||||||
|
@@ -1419,7 +1419,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add field conversion funcs.
|
// Add field conversion funcs.
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "pods",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "Pod",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "name":
|
case "name":
|
||||||
@@ -1439,7 +1439,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "replicationControllers",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "ReplicationController",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "name":
|
case "name":
|
||||||
@@ -1454,7 +1454,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "events",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "Event",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "involvedObject.kind",
|
case "involvedObject.kind",
|
||||||
@@ -1476,7 +1476,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Namespace",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "status.phase":
|
case "status.phase":
|
||||||
|
@@ -59,11 +59,12 @@ func init() {
|
|||||||
&NamespaceList{},
|
&NamespaceList{},
|
||||||
&Secret{},
|
&Secret{},
|
||||||
&SecretList{},
|
&SecretList{},
|
||||||
&DeleteOptions{},
|
|
||||||
&PersistentVolume{},
|
&PersistentVolume{},
|
||||||
&PersistentVolumeList{},
|
&PersistentVolumeList{},
|
||||||
&PersistentVolumeClaim{},
|
&PersistentVolumeClaim{},
|
||||||
&PersistentVolumeClaimList{},
|
&PersistentVolumeClaimList{},
|
||||||
|
&DeleteOptions{},
|
||||||
|
&ListOptions{},
|
||||||
)
|
)
|
||||||
// Future names are supported
|
// Future names are supported
|
||||||
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
|
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
|
||||||
@@ -102,3 +103,4 @@ func (*PersistentVolumeList) IsAnAPIObject() {}
|
|||||||
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
||||||
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
||||||
func (*DeleteOptions) IsAnAPIObject() {}
|
func (*DeleteOptions) IsAnAPIObject() {}
|
||||||
|
func (*ListOptions) IsAnAPIObject() {}
|
||||||
|
@@ -1048,6 +1048,20 @@ 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"`
|
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"`
|
||||||
|
// If true, watch for changes to the selected resources
|
||||||
|
Watch bool `json:"watch" description:"watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion"`
|
||||||
|
// The desired resource version to watch
|
||||||
|
ResourceVersion string `json:"resourceVersion" description:"when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history"`
|
||||||
|
}
|
||||||
|
|
||||||
// Status is a return value for calls that don't return other objects.
|
// 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
|
// TODO: this could go in apiserver, but I'm including it here so clients needn't
|
||||||
// import both.
|
// import both.
|
||||||
|
@@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Add field conversion funcs.
|
// Add field conversion funcs.
|
||||||
err := newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "pods",
|
err := newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "Pod",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "name",
|
case "name",
|
||||||
@@ -39,7 +39,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "replicationControllers",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "ReplicationController",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "name":
|
case "name":
|
||||||
@@ -54,7 +54,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "events",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "Event",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "involvedObject.kind",
|
case "involvedObject.kind",
|
||||||
@@ -75,7 +75,7 @@ func init() {
|
|||||||
// If one of the conversion functions is malformed, detect it immediately.
|
// If one of the conversion functions is malformed, detect it immediately.
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
|
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Namespace",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "status.phase":
|
case "status.phase":
|
||||||
|
@@ -53,11 +53,12 @@ func init() {
|
|||||||
&NamespaceList{},
|
&NamespaceList{},
|
||||||
&Secret{},
|
&Secret{},
|
||||||
&SecretList{},
|
&SecretList{},
|
||||||
&DeleteOptions{},
|
|
||||||
&PersistentVolume{},
|
&PersistentVolume{},
|
||||||
&PersistentVolumeList{},
|
&PersistentVolumeList{},
|
||||||
&PersistentVolumeClaim{},
|
&PersistentVolumeClaim{},
|
||||||
&PersistentVolumeClaimList{},
|
&PersistentVolumeClaimList{},
|
||||||
|
&DeleteOptions{},
|
||||||
|
&ListOptions{},
|
||||||
)
|
)
|
||||||
// Legacy names are supported
|
// Legacy names are supported
|
||||||
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
|
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
|
||||||
@@ -96,3 +97,4 @@ func (*PersistentVolumeList) IsAnAPIObject() {}
|
|||||||
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
func (*PersistentVolumeClaim) IsAnAPIObject() {}
|
||||||
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
func (*PersistentVolumeClaimList) IsAnAPIObject() {}
|
||||||
func (*DeleteOptions) IsAnAPIObject() {}
|
func (*DeleteOptions) IsAnAPIObject() {}
|
||||||
|
func (*ListOptions) IsAnAPIObject() {}
|
||||||
|
@@ -1190,6 +1190,20 @@ 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"`
|
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"`
|
||||||
|
// If true, watch for changes to the selected resources
|
||||||
|
Watch bool `json:"watch" description:"watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion"`
|
||||||
|
// The desired resource version to watch
|
||||||
|
ResourceVersion string `json:"resourceVersion" description:"when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history"`
|
||||||
|
}
|
||||||
|
|
||||||
// Status is a return value for calls that don't return other objects.
|
// Status is a return value for calls that don't return other objects.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
@@ -29,7 +29,9 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
)
|
)
|
||||||
@@ -58,13 +60,6 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
|
|||||||
// Create the WebService.
|
// Create the WebService.
|
||||||
ws = a.newWebService()
|
ws = a.newWebService()
|
||||||
|
|
||||||
// Initialize the custom handlers.
|
|
||||||
watchHandler := (&WatchHandler{
|
|
||||||
storage: a.group.Storage,
|
|
||||||
codec: a.group.Codec,
|
|
||||||
linker: a.group.Linker,
|
|
||||||
info: a.info,
|
|
||||||
})
|
|
||||||
redirectHandler := (&RedirectHandler{a.group.Storage, a.group.Codec, a.group.Context, 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})
|
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.Storage, a.group.Codec, a.group.Context, a.info})
|
||||||
|
|
||||||
@@ -77,7 +72,7 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
|
|||||||
}
|
}
|
||||||
sort.Strings(paths)
|
sort.Strings(paths)
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
if err := a.registerResourceHandlers(path, a.group.Storage[path], ws, watchHandler, redirectHandler, proxyHandler); err != nil {
|
if err := a.registerResourceHandlers(path, a.group.Storage[path], ws, redirectHandler, proxyHandler); err != nil {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,10 +90,15 @@ func (a *APIInstaller) newWebService() *restful.WebService {
|
|||||||
return ws
|
return ws
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, watchHandler, redirectHandler, proxyHandler http.Handler) error {
|
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, redirectHandler, proxyHandler http.Handler) error {
|
||||||
admit := a.group.Admit
|
admit := a.group.Admit
|
||||||
context := a.group.Context
|
context := a.group.Context
|
||||||
|
|
||||||
|
serverVersion := a.group.ServerVersion
|
||||||
|
if len(serverVersion) == 0 {
|
||||||
|
serverVersion = a.group.Version
|
||||||
|
}
|
||||||
|
|
||||||
var resource, subresource string
|
var resource, subresource string
|
||||||
switch parts := strings.Split(path, "/"); len(parts) {
|
switch parts := strings.Split(path, "/"); len(parts) {
|
||||||
case 2:
|
case 2:
|
||||||
@@ -121,17 +121,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
}
|
}
|
||||||
versionedObject := indirectArbitraryPointer(versionedPtr)
|
versionedObject := indirectArbitraryPointer(versionedPtr)
|
||||||
|
|
||||||
var versionedList interface{}
|
|
||||||
if lister, ok := storage.(rest.Lister); ok {
|
|
||||||
list := lister.NewList()
|
|
||||||
_, listKind, err := a.group.Typer.ObjectVersionAndKind(list)
|
|
||||||
versionedListPtr, err := a.group.Creater.New(a.group.Version, listKind)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
versionedList = indirectArbitraryPointer(versionedListPtr)
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping, err := a.group.Mapper.RESTMapping(kind, a.group.Version)
|
mapping, err := a.group.Mapper.RESTMapping(kind, a.group.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -145,17 +134,33 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
|
gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
|
||||||
updater, isUpdater := storage.(rest.Updater)
|
updater, isUpdater := storage.(rest.Updater)
|
||||||
patcher, isPatcher := storage.(rest.Patcher)
|
patcher, isPatcher := storage.(rest.Patcher)
|
||||||
_, isWatcher := storage.(rest.Watcher)
|
watcher, isWatcher := storage.(rest.Watcher)
|
||||||
_, isRedirector := storage.(rest.Redirector)
|
_, isRedirector := storage.(rest.Redirector)
|
||||||
storageMeta, isMetadata := storage.(rest.StorageMetadata)
|
storageMeta, isMetadata := storage.(rest.StorageMetadata)
|
||||||
if !isMetadata {
|
if !isMetadata {
|
||||||
storageMeta = defaultStorageMetadata{}
|
storageMeta = defaultStorageMetadata{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var versionedList interface{}
|
||||||
|
if isLister {
|
||||||
|
list := lister.NewList()
|
||||||
|
_, listKind, err := a.group.Typer.ObjectVersionAndKind(list)
|
||||||
|
versionedListPtr, err := a.group.Creater.New(a.group.Version, listKind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
versionedList = indirectArbitraryPointer(versionedListPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
versionedListOptions, err := a.group.Creater.New(serverVersion, "ListOptions")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var versionedDeleterObject runtime.Object
|
var versionedDeleterObject runtime.Object
|
||||||
switch {
|
switch {
|
||||||
case isGracefulDeleter:
|
case isGracefulDeleter:
|
||||||
object, err := a.group.Creater.New(a.group.Version, "DeleteOptions")
|
object, err := a.group.Creater.New(serverVersion, "DeleteOptions")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -288,11 +293,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
// test/integration/auth_test.go is currently the most comprehensive status code test
|
// test/integration/auth_test.go is currently the most comprehensive status code test
|
||||||
|
|
||||||
reqScope := RequestScope{
|
reqScope := RequestScope{
|
||||||
ContextFunc: ctxFn,
|
ContextFunc: ctxFn,
|
||||||
Codec: mapping.Codec,
|
Creater: a.group.Creater,
|
||||||
APIVersion: a.group.Version,
|
Convertor: a.group.Convertor,
|
||||||
Resource: resource,
|
Codec: mapping.Codec,
|
||||||
Kind: kind,
|
APIVersion: a.group.Version,
|
||||||
|
ServerAPIVersion: serverVersion,
|
||||||
|
Resource: resource,
|
||||||
|
Kind: kind,
|
||||||
}
|
}
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
reqScope.Namer = action.Namer
|
reqScope.Namer = action.Namer
|
||||||
@@ -308,12 +316,15 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "LIST": // List all resources of a kind.
|
case "LIST": // List all resources of a kind.
|
||||||
route := ws.GET(action.Path).To(ListResource(lister, reqScope)).
|
route := ws.GET(action.Path).To(ListResource(lister, watcher, reqScope, false)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("list objects of kind " + kind).
|
Doc("list objects of kind " + kind).
|
||||||
Operation("list" + kind).
|
Operation("list" + kind).
|
||||||
Produces("application/json").
|
Produces("application/json").
|
||||||
Writes(versionedList)
|
Writes(versionedList)
|
||||||
|
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "PUT": // Update a resource.
|
case "PUT": // Update a resource.
|
||||||
@@ -356,22 +367,30 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
}
|
}
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
|
// TODO: deprecated
|
||||||
case "WATCH": // Watch a resource.
|
case "WATCH": // Watch a resource.
|
||||||
route := ws.GET(action.Path).To(routeFunction(watchHandler)).
|
route := ws.GET(action.Path).To(ListResource(lister, watcher, reqScope, true)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("watch a particular " + kind).
|
Doc("watch changes to an object of kind " + kind).
|
||||||
Operation("watch" + kind).
|
Operation("watch" + kind).
|
||||||
Produces("application/json").
|
Produces("application/json").
|
||||||
Writes(versionedObject)
|
Writes(watchjson.NewWatchEvent())
|
||||||
|
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
|
// TODO: deprecated
|
||||||
case "WATCHLIST": // Watch all resources of a kind.
|
case "WATCHLIST": // Watch all resources of a kind.
|
||||||
route := ws.GET(action.Path).To(routeFunction(watchHandler)).
|
route := ws.GET(action.Path).To(ListResource(lister, watcher, reqScope, true)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("watch a list of " + kind).
|
Doc("watch individual changes to a list of " + kind).
|
||||||
Operation("watch" + kind + "list").
|
Operation("watch" + kind + "list").
|
||||||
Produces("application/json").
|
Produces("application/json").
|
||||||
Writes(versionedList)
|
Writes(watchjson.NewWatchEvent())
|
||||||
|
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "REDIRECT": // Get the redirect URL for a resource.
|
case "REDIRECT": // Get the redirect URL for a resource.
|
||||||
@@ -651,6 +670,45 @@ func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route.
|
||||||
|
// The object must be a pointer to a struct; only fields at the top level of the struct that are not
|
||||||
|
// themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard
|
||||||
|
// Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is
|
||||||
|
// the JSON field name. If a description struct tag is set on the field, that description is used on the
|
||||||
|
// query parameter. In essence, it converts a standard JSON top level object into a query param schema.
|
||||||
|
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.
|
// defaultStorageMetadata provides default answers to rest.StorageMetadata.
|
||||||
type defaultStorageMetadata struct{}
|
type defaultStorageMetadata struct{}
|
||||||
|
|
||||||
|
@@ -102,12 +102,19 @@ type APIGroupVersion struct {
|
|||||||
Root string
|
Root string
|
||||||
Version 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
|
Mapper meta.RESTMapper
|
||||||
|
|
||||||
Codec runtime.Codec
|
Codec runtime.Codec
|
||||||
Typer runtime.ObjectTyper
|
Typer runtime.ObjectTyper
|
||||||
Creater runtime.ObjectCreater
|
Creater runtime.ObjectCreater
|
||||||
Linker runtime.SelfLinker
|
Convertor runtime.ObjectConvertor
|
||||||
|
Linker runtime.SelfLinker
|
||||||
|
|
||||||
Admit admission.Interface
|
Admit admission.Interface
|
||||||
Context api.RequestContextMapper
|
Context api.RequestContextMapper
|
||||||
@@ -197,7 +204,11 @@ func APIVersionHandler(versions ...string) restful.RouteFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write renders a returned runtime.Object to the response as a stream or an encoded object.
|
// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object
|
||||||
|
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
|
||||||
|
// response. The Accept header and current API version will be passed in, and the output will be copied
|
||||||
|
// directly to the response body. If content type is returned it is used, otherwise the content type will
|
||||||
|
// be "application/octet-stream". All other objects are sent to standard JSON serialization.
|
||||||
func write(statusCode int, apiVersion string, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
func write(statusCode int, apiVersion string, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
||||||
if stream, ok := object.(rest.ResourceStreamer); ok {
|
if stream, ok := object.(rest.ResourceStreamer); ok {
|
||||||
out, contentType, err := stream.InputStream(apiVersion, req.Header.Get("Accept"))
|
out, contentType, err := stream.InputStream(apiVersion, req.Header.Get("Accept"))
|
||||||
|
@@ -37,6 +37,8 @@ import (
|
|||||||
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
@@ -53,11 +55,21 @@ func convert(obj runtime.Object) (runtime.Object, error) {
|
|||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This creates a fake API version, similar to api/latest.go
|
// This creates a fake API version, similar to api/latest.go for a v1beta1 equivalent api. It is distinct
|
||||||
|
// from the Kubernetes API versions to allow clients to properly distinguish the two.
|
||||||
const testVersion = "version"
|
const testVersion = "version"
|
||||||
|
|
||||||
var versions = []string{testVersion}
|
// The equivalent of the Kubernetes v1beta3 API.
|
||||||
var codec = runtime.CodecFor(api.Scheme, testVersion)
|
const testVersion2 = "version2"
|
||||||
|
|
||||||
|
var versions = []string{testVersion, testVersion2}
|
||||||
|
var legacyCodec = runtime.CodecFor(api.Scheme, testVersion)
|
||||||
|
var codec = runtime.CodecFor(api.Scheme, testVersion2)
|
||||||
|
|
||||||
|
// these codecs reflect ListOptions/DeleteOptions coming from the serverAPIversion
|
||||||
|
var versionServerCodec = runtime.CodecFor(api.Scheme, "v1beta1")
|
||||||
|
var version2ServerCodec = runtime.CodecFor(api.Scheme, "v1beta3")
|
||||||
|
|
||||||
var accessor = meta.NewAccessor()
|
var accessor = meta.NewAccessor()
|
||||||
var versioner runtime.ResourceVersioner = accessor
|
var versioner runtime.ResourceVersioner = accessor
|
||||||
var selfLinker runtime.SelfLinker = accessor
|
var selfLinker runtime.SelfLinker = accessor
|
||||||
@@ -68,6 +80,12 @@ var requestContextMapper api.RequestContextMapper
|
|||||||
func interfacesFor(version string) (*meta.VersionInterfaces, error) {
|
func interfacesFor(version string) (*meta.VersionInterfaces, error) {
|
||||||
switch version {
|
switch version {
|
||||||
case testVersion:
|
case testVersion:
|
||||||
|
return &meta.VersionInterfaces{
|
||||||
|
Codec: legacyCodec,
|
||||||
|
ObjectConvertor: api.Scheme,
|
||||||
|
MetadataAccessor: accessor,
|
||||||
|
}, nil
|
||||||
|
case testVersion2:
|
||||||
return &meta.VersionInterfaces{
|
return &meta.VersionInterfaces{
|
||||||
Codec: codec,
|
Codec: codec,
|
||||||
ObjectConvertor: api.Scheme,
|
ObjectConvertor: api.Scheme,
|
||||||
@@ -96,11 +114,13 @@ func init() {
|
|||||||
// api.Status is returned in errors
|
// api.Status is returned in errors
|
||||||
|
|
||||||
// "internal" version
|
// "internal" version
|
||||||
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{},
|
api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{}, &api.Status{}, &api.ListOptions{})
|
||||||
&api.Status{})
|
|
||||||
// "version" version
|
// "version" version
|
||||||
// TODO: Use versioned api objects?
|
// TODO: Use versioned api objects?
|
||||||
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{}, &api.DeleteOptions{}, &api.Status{})
|
api.Scheme.AddKnownTypes(testVersion, &Simple{}, &SimpleList{}, &v1beta1.Status{})
|
||||||
|
// "version2" version
|
||||||
|
// TODO: Use versioned api objects?
|
||||||
|
api.Scheme.AddKnownTypes(testVersion2, &Simple{}, &SimpleList{}, &v1beta3.Status{})
|
||||||
|
|
||||||
nsMapper := newMapper()
|
nsMapper := newMapper()
|
||||||
legacyNsMapper := newMapper()
|
legacyNsMapper := newMapper()
|
||||||
@@ -118,6 +138,18 @@ func init() {
|
|||||||
namespaceMapper = nsMapper
|
namespaceMapper = nsMapper
|
||||||
admissionControl = admit.NewAlwaysAdmit()
|
admissionControl = admit.NewAlwaysAdmit()
|
||||||
requestContextMapper = api.NewRequestContextMapper()
|
requestContextMapper = api.NewRequestContextMapper()
|
||||||
|
|
||||||
|
//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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
api.Scheme.AddFieldLabelConversionFunc(testVersion2, "Simple",
|
||||||
|
func(label, value string) (string, string, error) {
|
||||||
|
return label, value, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultAPIServer exposes nested objects for testability.
|
// defaultAPIServer exposes nested objects for testability.
|
||||||
@@ -129,45 +161,61 @@ type defaultAPIServer struct {
|
|||||||
|
|
||||||
// uses the default settings
|
// uses the default settings
|
||||||
func handle(storage map[string]rest.Storage) http.Handler {
|
func handle(storage map[string]rest.Storage) http.Handler {
|
||||||
return handleInternal(storage, admissionControl, mapper, selfLinker)
|
return handleInternal(true, storage, admissionControl, selfLinker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// uses the default settings for a v1beta3 compatible api
|
||||||
|
func handleNew(storage map[string]rest.Storage) http.Handler {
|
||||||
|
return handleInternal(false, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests with a deny admission controller
|
// tests with a deny admission controller
|
||||||
func handleDeny(storage map[string]rest.Storage) http.Handler {
|
func handleDeny(storage map[string]rest.Storage) http.Handler {
|
||||||
return handleInternal(storage, deny.NewAlwaysDeny(), mapper, selfLinker)
|
return handleInternal(true, storage, deny.NewAlwaysDeny(), selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests using the new namespace scope mechanism
|
// tests using the new namespace scope mechanism
|
||||||
func handleNamespaced(storage map[string]rest.Storage) http.Handler {
|
func handleNamespaced(storage map[string]rest.Storage) http.Handler {
|
||||||
return handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
|
return handleInternal(false, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests using a custom self linker
|
// tests using a custom self linker
|
||||||
func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker) http.Handler {
|
func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker) http.Handler {
|
||||||
return handleInternal(storage, admissionControl, mapper, selfLinker)
|
return handleInternal(true, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInternal(storage map[string]rest.Storage, admissionControl admission.Interface, mapper meta.RESTMapper, selfLinker runtime.SelfLinker) http.Handler {
|
func handleInternal(legacy bool, storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler {
|
||||||
group := &APIGroupVersion{
|
group := &APIGroupVersion{
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
|
|
||||||
Mapper: mapper,
|
Root: "/api",
|
||||||
|
|
||||||
Root: "/api",
|
Creater: api.Scheme,
|
||||||
Version: testVersion,
|
Convertor: api.Scheme,
|
||||||
|
Typer: api.Scheme,
|
||||||
Creater: api.Scheme,
|
Linker: selfLinker,
|
||||||
Typer: api.Scheme,
|
|
||||||
Codec: codec,
|
|
||||||
Linker: selfLinker,
|
|
||||||
|
|
||||||
Admit: admissionControl,
|
Admit: admissionControl,
|
||||||
Context: requestContextMapper,
|
Context: requestContextMapper,
|
||||||
}
|
}
|
||||||
|
if legacy {
|
||||||
|
group.Version = testVersion
|
||||||
|
group.ServerVersion = "v1beta1"
|
||||||
|
group.Codec = legacyCodec
|
||||||
|
group.Mapper = legacyNamespaceMapper
|
||||||
|
} else {
|
||||||
|
group.Version = testVersion2
|
||||||
|
group.ServerVersion = "v1beta3"
|
||||||
|
group.Codec = codec
|
||||||
|
group.Mapper = namespaceMapper
|
||||||
|
}
|
||||||
|
|
||||||
container := restful.NewContainer()
|
container := restful.NewContainer()
|
||||||
container.Router(restful.CurlyRouter{})
|
container.Router(restful.CurlyRouter{})
|
||||||
mux := container.ServeMux
|
mux := container.ServeMux
|
||||||
group.InstallREST(container)
|
if err := group.InstallREST(container); err != nil {
|
||||||
|
panic(fmt.Sprintf("unable to install container %s: %v", group.Version, err))
|
||||||
|
}
|
||||||
ws := new(restful.WebService)
|
ws := new(restful.WebService)
|
||||||
InstallSupport(mux, ws)
|
InstallSupport(mux, ws)
|
||||||
container.Add(ws)
|
container.Add(ws)
|
||||||
@@ -244,6 +292,8 @@ func (storage *SimpleRESTStorage) List(ctx api.Context, label labels.Selector, f
|
|||||||
result := &SimpleList{
|
result := &SimpleList{
|
||||||
Items: storage.list,
|
Items: storage.list,
|
||||||
}
|
}
|
||||||
|
storage.requestedLabelSelector = label
|
||||||
|
storage.requestedFieldSelector = field
|
||||||
return result, storage.errors["list"]
|
return result, storage.errors["list"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,15 +572,60 @@ func TestList(t *testing.T) {
|
|||||||
namespace string
|
namespace string
|
||||||
selfLink string
|
selfLink string
|
||||||
legacy bool
|
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
|
// 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},
|
url: "/api/version/simple?namespace=",
|
||||||
{"/api/version/namespaces/other/simple", "other", "/api/version/namespaces/other/simple", false},
|
namespace: "",
|
||||||
|
selfLink: "/api/version/simple?namespace=",
|
||||||
|
legacy: true,
|
||||||
|
},
|
||||||
|
// list items in a namespace, v1beta3+
|
||||||
|
{
|
||||||
|
url: "/api/version2/namespaces/default/simple",
|
||||||
|
namespace: "default",
|
||||||
|
selfLink: "/api/version2/namespaces/default/simple",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/api/version2/namespaces/other/simple",
|
||||||
|
namespace: "other",
|
||||||
|
selfLink: "/api/version2/namespaces/other/simple",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/api/version2/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd",
|
||||||
|
namespace: "other",
|
||||||
|
selfLink: "/api/version2/namespaces/other/simple",
|
||||||
|
label: "a=b",
|
||||||
|
field: "c=d",
|
||||||
|
},
|
||||||
// list items across all namespaces
|
// list items across all namespaces
|
||||||
{"/api/version/simple", "", "/api/version/simple", false},
|
{
|
||||||
|
url: "/api/version2/simple",
|
||||||
|
namespace: "",
|
||||||
|
selfLink: "/api/version2/simple",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
storage := map[string]rest.Storage{}
|
storage := map[string]rest.Storage{}
|
||||||
@@ -545,7 +640,7 @@ func TestList(t *testing.T) {
|
|||||||
if testCase.legacy {
|
if testCase.legacy {
|
||||||
handler = handleLinker(storage, selfLinker)
|
handler = handleLinker(storage, selfLinker)
|
||||||
} else {
|
} else {
|
||||||
handler = handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
|
handler = handleInternal(false, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
@@ -557,6 +652,9 @@ func TestList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
t.Errorf("%d: unexpected status: %d, Expected: %d, %#v", i, resp.StatusCode, http.StatusOK, resp)
|
t.Errorf("%d: unexpected status: %d, Expected: %d, %#v", i, resp.StatusCode, http.StatusOK, resp)
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
t.Logf("%d: body: %s", string(body))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
// TODO: future, restore get links
|
// TODO: future, restore get links
|
||||||
if !selfLinker.called {
|
if !selfLinker.called {
|
||||||
@@ -567,6 +665,12 @@ func TestList(t *testing.T) {
|
|||||||
} else if simpleStorage.actualNamespace != testCase.namespace {
|
} else if simpleStorage.actualNamespace != testCase.namespace {
|
||||||
t.Errorf("%d: unexpected resource namespace: %s", i, simpleStorage.actualNamespace)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,16 +925,16 @@ func TestGetNamespaceSelfLink(t *testing.T) {
|
|||||||
}
|
}
|
||||||
selfLinker := &setTestSelfLinker{
|
selfLinker := &setTestSelfLinker{
|
||||||
t: t,
|
t: t,
|
||||||
expectedSet: "/api/version/namespaces/foo/simple/id",
|
expectedSet: "/api/version2/namespaces/foo/simple/id",
|
||||||
name: "id",
|
name: "id",
|
||||||
namespace: "foo",
|
namespace: "foo",
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := handleInternal(storage, admissionControl, namespaceMapper, selfLinker)
|
handler := handleInternal(false, storage, admissionControl, selfLinker)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
resp, err := http.Get(server.URL + "/api/version/namespaces/foo/simple/id")
|
resp, err := http.Get(server.URL + "/api/version2/namespaces/foo/simple/id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -905,7 +1009,7 @@ func TestDeleteWithOptions(t *testing.T) {
|
|||||||
item := &api.DeleteOptions{
|
item := &api.DeleteOptions{
|
||||||
GracePeriodSeconds: &grace,
|
GracePeriodSeconds: &grace,
|
||||||
}
|
}
|
||||||
body, err := codec.Encode(item)
|
body, err := versionServerCodec.Encode(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -966,7 +1070,7 @@ func TestLegacyDeleteIgnoresOptions(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
item := api.NewDeleteOptions(300)
|
item := api.NewDeleteOptions(300)
|
||||||
body, err := codec.Encode(item)
|
body, err := versionServerCodec.Encode(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1575,7 +1679,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
|
|||||||
namespace: "other",
|
namespace: "other",
|
||||||
expectedSet: "/api/version/foo/bar?namespace=other",
|
expectedSet: "/api/version/foo/bar?namespace=other",
|
||||||
}
|
}
|
||||||
handler := handleInternal(map[string]rest.Storage{"foo": &storage}, deny.NewAlwaysDeny(), mapper, selfLinker)
|
handler := handleInternal(true, map[string]rest.Storage{"foo": &storage}, deny.NewAlwaysDeny(), selfLinker)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
|
@@ -294,7 +294,7 @@ func TestProxy(t *testing.T) {
|
|||||||
server *httptest.Server
|
server *httptest.Server
|
||||||
proxyTestPattern string
|
proxyTestPattern string
|
||||||
}{
|
}{
|
||||||
{namespaceServer, "/api/version/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
|
{namespaceServer, "/api/version2/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
|
||||||
{legacyNamespaceServer, "/api/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
|
{legacyNamespaceServer, "/api/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@ func TestProxyUpgrade(t *testing.T) {
|
|||||||
server := httptest.NewServer(namespaceHandler)
|
server := httptest.NewServer(namespaceHandler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/api/version/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
|
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/api/version2/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("websocket dial err: %s", err)
|
t.Fatalf("websocket dial err: %s", err)
|
||||||
}
|
}
|
||||||
|
@@ -105,7 +105,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
|
|||||||
for _, item := range table {
|
for _, item := range table {
|
||||||
simpleStorage.errors["resourceLocation"] = item.err
|
simpleStorage.errors["resourceLocation"] = item.err
|
||||||
simpleStorage.resourceLocation = &url.URL{Host: item.id}
|
simpleStorage.resourceLocation = &url.URL{Host: item.id}
|
||||||
resp, err := client.Get(server.URL + "/api/version/redirect/namespaces/other/foo/" + item.id)
|
resp, err := client.Get(server.URL + "/api/version2/redirect/namespaces/other/foo/" + item.id)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
t.Fatalf("Unexpected nil resp")
|
t.Fatalf("Unexpected nil resp")
|
||||||
}
|
}
|
||||||
|
@@ -27,8 +27,6 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"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/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
@@ -64,9 +62,15 @@ type RequestScope struct {
|
|||||||
Namer ScopeNamer
|
Namer ScopeNamer
|
||||||
ContextFunc
|
ContextFunc
|
||||||
runtime.Codec
|
runtime.Codec
|
||||||
|
Creater runtime.ObjectCreater
|
||||||
|
Convertor runtime.ObjectConvertor
|
||||||
|
|
||||||
Resource string
|
Resource string
|
||||||
Kind string
|
Kind string
|
||||||
APIVersion 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.
|
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
|
||||||
@@ -94,26 +98,8 @@ 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.
|
// 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 {
|
func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool) restful.RouteFunction {
|
||||||
return func(req *restful.Request, res *restful.Response) {
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
w := res.ResponseWriter
|
w := res.ResponseWriter
|
||||||
|
|
||||||
@@ -125,13 +111,35 @@ func ListResource(r rest.Lister, scope RequestScope) restful.RouteFunction {
|
|||||||
ctx := scope.ContextFunc(req)
|
ctx := scope.ContextFunc(req)
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
|
||||||
label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), scope.APIVersion, scope.Resource)
|
out, err := queryToObject(req.Request.URL.Query(), scope, "ListOptions")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, scope.Codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
opts := *out.(*api.ListOptions)
|
||||||
|
|
||||||
result, err := r.List(ctx, label, field)
|
// 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 {
|
||||||
|
// TODO: allow bad request to set field causes based on query parameters
|
||||||
|
err = errors.NewBadRequest(err.Error())
|
||||||
|
errorJSON(err, scope.Codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.Watch || forceWatch) && rw != nil {
|
||||||
|
watcher, err := rw.Watch(ctx, opts.LabelSelector, opts.FieldSelector, opts.ResourceVersion)
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, scope.Codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serveWatch(watcher, scope, w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := r.List(ctx, opts.LabelSelector, opts.FieldSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, scope.Codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
@@ -409,6 +417,27 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// queryToObject converts query parameters into a structured internal object by
|
||||||
|
// kind. The caller must cast the returned object to the matching internal Kind
|
||||||
|
// to use it.
|
||||||
|
// TODO: add appropriate structured error responses
|
||||||
|
func queryToObject(query url.Values, scope RequestScope, kind string) (runtime.Object, error) {
|
||||||
|
versioned, err := scope.Creater.New(scope.ServerAPIVersion, kind)
|
||||||
|
if err != nil {
|
||||||
|
// programmer error
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := scope.Convertor.Convert(&query, versioned); err != nil {
|
||||||
|
return nil, errors.NewBadRequest(err.Error())
|
||||||
|
}
|
||||||
|
out, err := scope.Convertor.ConvertToVersion(versioned, "")
|
||||||
|
if err != nil {
|
||||||
|
// programmer error
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// resultFunc is a function that returns a rest result and can be run in a goroutine
|
// resultFunc is a function that returns a rest result and can be run in a goroutine
|
||||||
type resultFunc func() (runtime.Object, error)
|
type resultFunc func() (runtime.Object, error)
|
||||||
|
|
||||||
|
@@ -17,111 +17,38 @@ limitations under the License.
|
|||||||
package apiserver
|
package apiserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"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/httplog"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
|
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WatchHandler struct {
|
|
||||||
storage map[string]rest.Storage
|
|
||||||
codec runtime.Codec
|
|
||||||
linker runtime.SelfLinker
|
|
||||||
info *APIRequestInfoResolver
|
|
||||||
}
|
|
||||||
|
|
||||||
// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
|
|
||||||
func (h *WatchHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request) error {
|
|
||||||
name, err := h.linker.Name(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newURL := *req.URL
|
|
||||||
newURL.Path = path.Join(req.URL.Path, name)
|
|
||||||
newURL.RawQuery = ""
|
|
||||||
newURL.Fragment = ""
|
|
||||||
return h.linker.SetSelfLink(obj, newURL.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var connectionUpgradeRegex = regexp.MustCompile("(^|.*,\\s*)upgrade($|\\s*,)")
|
var connectionUpgradeRegex = regexp.MustCompile("(^|.*,\\s*)upgrade($|\\s*,)")
|
||||||
|
|
||||||
func isWebsocketRequest(req *http.Request) bool {
|
func isWebsocketRequest(req *http.Request) bool {
|
||||||
return connectionUpgradeRegex.MatchString(strings.ToLower(req.Header.Get("Connection"))) && strings.ToLower(req.Header.Get("Upgrade")) == "websocket"
|
return connectionUpgradeRegex.MatchString(strings.ToLower(req.Header.Get("Connection"))) && strings.ToLower(req.Header.Get("Upgrade")) == "websocket"
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP processes watch requests.
|
// serveWatch handles serving requests to the server
|
||||||
func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func serveWatch(watcher watch.Interface, scope RequestScope, w http.ResponseWriter, req *restful.Request) {
|
||||||
var verb string
|
watchServer := &WatchServer{watcher, scope.Codec, func(obj runtime.Object) {
|
||||||
var apiResource string
|
if err := setSelfLink(obj, req, scope.Namer); err != nil {
|
||||||
var httpCode int
|
glog.V(5).Infof("Failed to set self link for object %v: %v", reflect.TypeOf(obj), err)
|
||||||
reqStart := time.Now()
|
|
||||||
defer monitor("watch", &verb, &apiResource, &httpCode, reqStart)
|
|
||||||
|
|
||||||
if req.Method != "GET" {
|
|
||||||
httpCode = errorJSON(errors.NewBadRequest(
|
|
||||||
fmt.Sprintf("unsupported method for watch: %s", req.Method)), h.codec, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
requestInfo, err := h.info.GetAPIRequestInfo(req)
|
|
||||||
if err != nil {
|
|
||||||
httpCode = errorJSON(errors.NewBadRequest(
|
|
||||||
fmt.Sprintf("failed to find api request info: %s", err.Error())), h.codec, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
verb = requestInfo.Verb
|
|
||||||
ctx := api.WithNamespace(api.NewContext(), requestInfo.Namespace)
|
|
||||||
|
|
||||||
storage := h.storage[requestInfo.Resource]
|
|
||||||
if storage == nil {
|
|
||||||
httpCode = errorJSON(errors.NewNotFound(requestInfo.Resource, "Resource"), h.codec, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
apiResource = requestInfo.Resource
|
|
||||||
watcher, ok := storage.(rest.Watcher)
|
|
||||||
if !ok {
|
|
||||||
httpCode = errorJSON(errors.NewMethodNotSupported(requestInfo.Resource, "watch"), h.codec, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
label, field, err := parseSelectorQueryParams(req.URL.Query(), requestInfo.APIVersion, apiResource)
|
|
||||||
if err != nil {
|
|
||||||
httpCode = errorJSON(err, h.codec, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceVersion := req.URL.Query().Get("resourceVersion")
|
|
||||||
watching, err := watcher.Watch(ctx, label, field, resourceVersion)
|
|
||||||
if err != nil {
|
|
||||||
httpCode = errorJSON(err, h.codec, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
httpCode = http.StatusOK
|
|
||||||
|
|
||||||
// TODO: This is one watch per connection. We want to multiplex, so that
|
|
||||||
// multiple watches of the same thing don't create two watches downstream.
|
|
||||||
watchServer := &WatchServer{watching, h.codec, func(obj runtime.Object) {
|
|
||||||
if err := h.setSelfLinkAddName(obj, req); err != nil {
|
|
||||||
glog.Errorf("Failed to set self link for object %#v", obj)
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
if isWebsocketRequest(req) {
|
if isWebsocketRequest(req.Request) {
|
||||||
websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req)
|
websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req.Request)
|
||||||
} else {
|
} else {
|
||||||
watchServer.ServeHTTP(w, req)
|
watchServer.ServeHTTP(w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -51,13 +51,13 @@ var watchTestTable = []struct {
|
|||||||
func TestWatchWebsocket(t *testing.T) {
|
func TestWatchWebsocket(t *testing.T) {
|
||||||
simpleStorage := &SimpleRESTStorage{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
_ = rest.Watcher(simpleStorage) // Give compile error if this doesn't work.
|
_ = 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)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
dest, _ := url.Parse(server.URL)
|
dest, _ := url.Parse(server.URL)
|
||||||
dest.Scheme = "ws" // Required by websocket, though the server never sees it.
|
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 = ""
|
dest.RawQuery = ""
|
||||||
|
|
||||||
ws, err := websocket.Dial(dest.String(), "", "http://localhost")
|
ws, err := websocket.Dial(dest.String(), "", "http://localhost")
|
||||||
@@ -103,13 +103,13 @@ func TestWatchWebsocket(t *testing.T) {
|
|||||||
|
|
||||||
func TestWatchHTTP(t *testing.T) {
|
func TestWatchHTTP(t *testing.T) {
|
||||||
simpleStorage := &SimpleRESTStorage{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
handler := handle(map[string]rest.Storage{"foo": simpleStorage})
|
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
|
|
||||||
dest, _ := url.Parse(server.URL)
|
dest, _ := url.Parse(server.URL)
|
||||||
dest.Path = "/api/version/watch/foo"
|
dest.Path = "/api/version/watch/simples"
|
||||||
dest.RawQuery = ""
|
dest.RawQuery = ""
|
||||||
|
|
||||||
request, err := http.NewRequest("GET", dest.String(), nil)
|
request, err := http.NewRequest("GET", dest.String(), nil)
|
||||||
@@ -163,17 +163,13 @@ func TestWatchHTTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWatchParamParsing(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{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
handler := handle(map[string]rest.Storage{"foo": simpleStorage})
|
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
dest, _ := url.Parse(server.URL)
|
dest, _ := url.Parse(server.URL)
|
||||||
dest.Path = "/api/" + testVersion + "/watch/foo"
|
dest.Path = "/api/" + testVersion + "/watch/simples"
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
rawQuery string
|
rawQuery string
|
||||||
@@ -189,13 +185,13 @@ func TestWatchParamParsing(t *testing.T) {
|
|||||||
fieldSelector: "",
|
fieldSelector: "",
|
||||||
namespace: api.NamespaceAll,
|
namespace: api.NamespaceAll,
|
||||||
}, {
|
}, {
|
||||||
rawQuery: "namespace=default&resourceVersion=314159&" + api.FieldSelectorQueryParam(testVersion) + "=Host%3D&" + api.LabelSelectorQueryParam(testVersion) + "=name%3Dfoo",
|
rawQuery: "namespace=default&resourceVersion=314159&fields=Host%3D&labels=name%3Dfoo",
|
||||||
resourceVersion: "314159",
|
resourceVersion: "314159",
|
||||||
labelSelector: "name=foo",
|
labelSelector: "name=foo",
|
||||||
fieldSelector: "Host=",
|
fieldSelector: "Host=",
|
||||||
namespace: api.NamespaceDefault,
|
namespace: api.NamespaceDefault,
|
||||||
}, {
|
}, {
|
||||||
rawQuery: "namespace=watchother&" + api.FieldSelectorQueryParam(testVersion) + "=id%3dfoo&resourceVersion=1492",
|
rawQuery: "namespace=watchother&fields=id%3dfoo&resourceVersion=1492",
|
||||||
resourceVersion: "1492",
|
resourceVersion: "1492",
|
||||||
labelSelector: "",
|
labelSelector: "",
|
||||||
fieldSelector: "id=foo",
|
fieldSelector: "id=foo",
|
||||||
@@ -238,14 +234,14 @@ func TestWatchParamParsing(t *testing.T) {
|
|||||||
|
|
||||||
func TestWatchProtocolSelection(t *testing.T) {
|
func TestWatchProtocolSelection(t *testing.T) {
|
||||||
simpleStorage := &SimpleRESTStorage{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
handler := handle(map[string]rest.Storage{"foo": simpleStorage})
|
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
defer server.CloseClientConnections()
|
defer server.CloseClientConnections()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
|
|
||||||
dest, _ := url.Parse(server.URL)
|
dest, _ := url.Parse(server.URL)
|
||||||
dest.Path = "/api/version/watch/foo"
|
dest.Path = "/api/version/watch/simples"
|
||||||
dest.RawQuery = ""
|
dest.RawQuery = ""
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
|
@@ -572,9 +572,10 @@ func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
|
|||||||
|
|
||||||
Mapper: latest.RESTMapper,
|
Mapper: latest.RESTMapper,
|
||||||
|
|
||||||
Creater: api.Scheme,
|
Creater: api.Scheme,
|
||||||
Typer: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
Linker: latest.SelfLinker,
|
Typer: api.Scheme,
|
||||||
|
Linker: latest.SelfLinker,
|
||||||
|
|
||||||
Admit: m.admissionControl,
|
Admit: m.admissionControl,
|
||||||
Context: m.requestContextMapper,
|
Context: m.requestContextMapper,
|
||||||
|
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Defines conversions between generic types and structs to map query strings
|
||||||
|
// to struct objects.
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -40,6 +42,7 @@ func JSONKeyMapper(key string, sourceTag, destTag reflect.StructTag) (string, st
|
|||||||
var DefaultStringConversions = []interface{}{
|
var DefaultStringConversions = []interface{}{
|
||||||
convertStringSliceToString,
|
convertStringSliceToString,
|
||||||
convertStringSliceToInt,
|
convertStringSliceToInt,
|
||||||
|
convertStringSliceToBool,
|
||||||
convertStringSliceToInt64,
|
convertStringSliceToInt64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +67,19 @@ func convertStringSliceToInt(input *[]string, out *int, s conversion.Scope) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertStringSliceToBool(input *[]string, out *bool, s conversion.Scope) error {
|
||||||
|
if len(*input) == 0 {
|
||||||
|
*out = false
|
||||||
|
}
|
||||||
|
switch strings.ToLower((*input)[0]) {
|
||||||
|
case "true", "1":
|
||||||
|
*out = true
|
||||||
|
default:
|
||||||
|
*out = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func convertStringSliceToInt64(input *[]string, out *int64, s conversion.Scope) error {
|
func convertStringSliceToInt64(input *[]string, out *int64, s conversion.Scope) error {
|
||||||
if len(*input) == 0 {
|
if len(*input) == 0 {
|
||||||
*out = 0
|
*out = 0
|
||||||
|
@@ -35,7 +35,9 @@ type Codec interface {
|
|||||||
|
|
||||||
// ObjectConvertor converts an object to a different version.
|
// ObjectConvertor converts an object to a different version.
|
||||||
type ObjectConvertor interface {
|
type ObjectConvertor interface {
|
||||||
|
Convert(in, out interface{}) error
|
||||||
ConvertToVersion(in Object, outVersion string) (out Object, err 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
|
// ObjectTyper contains methods for extracting the APIVersion and Kind
|
||||||
|
@@ -18,6 +18,7 @@ package runtime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
"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 {
|
if err := s.raw.RegisterInputDefaults(&map[string][]string{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if err := s.raw.RegisterInputDefaults(&url.Values{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,13 +298,13 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddFieldLabelConversionFunc adds a conversion function to convert field selectors
|
// AddFieldLabelConversionFunc adds a conversion function to convert field selectors
|
||||||
// of the given api resource from the given version to internal version representation.
|
// of the given kind from the given version to internal version representation.
|
||||||
func (s *Scheme) AddFieldLabelConversionFunc(version, apiResource string, conversionFunc FieldLabelConversionFunc) error {
|
func (s *Scheme) AddFieldLabelConversionFunc(version, kind string, conversionFunc FieldLabelConversionFunc) error {
|
||||||
if s.fieldLabelConversionFuncs[version] == nil {
|
if s.fieldLabelConversionFuncs[version] == nil {
|
||||||
s.fieldLabelConversionFuncs[version] = map[string]FieldLabelConversionFunc{}
|
s.fieldLabelConversionFuncs[version] = map[string]FieldLabelConversionFunc{}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.fieldLabelConversionFuncs[version][apiResource] = conversionFunc
|
s.fieldLabelConversionFuncs[version][kind] = conversionFunc
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,15 +330,15 @@ func (s *Scheme) Convert(in, out interface{}) error {
|
|||||||
return s.raw.Convert(in, out)
|
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.
|
// 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 {
|
if s.fieldLabelConversionFuncs[version] == nil {
|
||||||
return "", "", fmt.Errorf("No conversion function found for version: %s", version)
|
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 {
|
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)
|
return conversionFunc(label, value)
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,11 @@ type watchEvent struct {
|
|||||||
Object runtime.RawExtension `json:"object,omitempty"`
|
Object runtime.RawExtension `json:"object,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWatchEvent returns the serialization form of watchEvent for structured schemas
|
||||||
|
func NewWatchEvent() interface{} {
|
||||||
|
return &watchEvent{}
|
||||||
|
}
|
||||||
|
|
||||||
// Object converts a watch.Event into an appropriately serializable JSON object
|
// Object converts a watch.Event into an appropriately serializable JSON object
|
||||||
func Object(codec runtime.Codec, event *watch.Event) (interface{}, error) {
|
func Object(codec runtime.Codec, event *watch.Event) (interface{}, error) {
|
||||||
obj, ok := event.Object.(runtime.Object)
|
obj, ok := event.Object.(runtime.Object)
|
||||||
|
Reference in New Issue
Block a user