From 5d664892f2b1bc99fed986425490d68ea96ff460 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Fri, 14 Aug 2015 22:10:15 -0700 Subject: [PATCH] Add a storage implementation for thirdpartyresources. --- contrib/completions/bash/kubectl | 2 + pkg/api/deep_copy_generated.go | 50 ----- pkg/api/serialization_test.go | 2 +- pkg/api/v1/conversion_generated.go | 118 ---------- pkg/api/v1/deep_copy_generated.go | 50 ----- pkg/api/validation/validation.go | 4 + pkg/expapi/deep_copy_generated.go | 50 +++++ pkg/expapi/register.go | 4 + pkg/expapi/types.go | 32 +++ pkg/expapi/v1/conversion_generated.go | 118 ++++++++++ pkg/expapi/v1/deep_copy_generated.go | 50 +++++ pkg/expapi/v1/defaults.go | 8 + pkg/expapi/v1/register.go | 4 + pkg/expapi/v1/types.go | 32 +++ pkg/expapi/validation/validation.go | 24 ++ pkg/kubectl/resource_printer.go | 27 +++ pkg/master/master.go | 7 +- pkg/registry/thirdpartyresource/doc.go | 19 ++ pkg/registry/thirdpartyresource/etcd/etcd.go | 63 ++++++ .../thirdpartyresource/etcd/etcd_test.go | 205 ++++++++++++++++++ pkg/registry/thirdpartyresource/registry.go | 88 ++++++++ pkg/registry/thirdpartyresource/strategy.go | 88 ++++++++ 22 files changed, 824 insertions(+), 221 deletions(-) create mode 100644 pkg/registry/thirdpartyresource/doc.go create mode 100644 pkg/registry/thirdpartyresource/etcd/etcd.go create mode 100644 pkg/registry/thirdpartyresource/etcd/etcd_test.go create mode 100644 pkg/registry/thirdpartyresource/registry.go create mode 100644 pkg/registry/thirdpartyresource/strategy.go diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 46084270960..2ab585ae1c3 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -278,6 +278,7 @@ _kubectl_get() must_have_one_noun+=("secret") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("thirdpartyresource") } _kubectl_describe() @@ -449,6 +450,7 @@ _kubectl_delete() must_have_one_noun+=("secret") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("thirdpartyresource") } _kubectl_namespace() diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index 51b5a15358a..a84d50522fa 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -30,12 +30,6 @@ import ( inf "speter.net/go/exp/math/dec/inf" ) -func deepCopy_api_APIVersion(in APIVersion, out *APIVersion, c *conversion.Cloner) error { - out.Name = in.Name - out.APIGroup = in.APIGroup - return nil -} - func deepCopy_api_AWSElasticBlockStoreVolumeSource(in AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, c *conversion.Cloner) error { out.VolumeID = in.VolumeID out.FSType = in.FSType @@ -2027,27 +2021,6 @@ func deepCopy_api_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *c return nil } -func deepCopy_api_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { - if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - if err := deepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { - return err - } - out.Description = in.Description - if in.Versions != nil { - out.Versions = make([]APIVersion, len(in.Versions)) - for i := range in.Versions { - if err := deepCopy_api_APIVersion(in.Versions[i], &out.Versions[i], c); err != nil { - return err - } - } - } else { - out.Versions = nil - } - return nil -} - func deepCopy_api_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPartyResourceData, c *conversion.Cloner) error { if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err @@ -2066,26 +2039,6 @@ func deepCopy_api_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPa return nil } -func deepCopy_api_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error { - if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - if err := deepCopy_api_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ThirdPartyResource, len(in.Items)) - for i := range in.Items { - if err := deepCopy_api_ThirdPartyResource(in.Items[i], &out.Items[i], c); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - func deepCopy_api_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error { out.Kind = in.Kind out.APIVersion = in.APIVersion @@ -2233,7 +2186,6 @@ func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) erro func init() { err := Scheme.AddGeneratedDeepCopyFuncs( - deepCopy_api_APIVersion, deepCopy_api_AWSElasticBlockStoreVolumeSource, deepCopy_api_Binding, deepCopy_api_Capabilities, @@ -2349,9 +2301,7 @@ func init() { deepCopy_api_StatusCause, deepCopy_api_StatusDetails, deepCopy_api_TCPSocketAction, - deepCopy_api_ThirdPartyResource, deepCopy_api_ThirdPartyResourceData, - deepCopy_api_ThirdPartyResourceList, deepCopy_api_TypeMeta, deepCopy_api_Volume, deepCopy_api_VolumeMount, diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 6eed0666be9..67dbcc4802f 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -124,7 +124,7 @@ func TestList(t *testing.T) { roundTripSame(t, item) } -var nonRoundTrippableTypes = util.NewStringSet() +var nonRoundTrippableTypes = util.NewStringSet("ThirdPartyResource") var nonInternalRoundTrippableTypes = util.NewStringSet("List", "ListOptions", "PodExecOptions", "PodAttachOptions") var nonRoundTrippableTypesByVersion = map[string][]string{} diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index 09dcbb80e96..f1ea280ac2e 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -26,15 +26,6 @@ import ( conversion "k8s.io/kubernetes/pkg/conversion" ) -func convert_api_APIVersion_To_v1_APIVersion(in *api.APIVersion, out *APIVersion, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.APIVersion))(in) - } - out.Name = in.Name - out.APIGroup = in.APIGroup - return nil -} - func convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*api.AWSElasticBlockStoreVolumeSource))(in) @@ -2259,30 +2250,6 @@ func convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, return nil } -func convert_api_ThirdPartyResource_To_v1_ThirdPartyResource(in *api.ThirdPartyResource, out *ThirdPartyResource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ThirdPartyResource))(in) - } - if err := convert_api_TypeMeta_To_v1_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { - return err - } - if err := convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - out.Description = in.Description - if in.Versions != nil { - out.Versions = make([]APIVersion, len(in.Versions)) - for i := range in.Versions { - if err := convert_api_APIVersion_To_v1_APIVersion(&in.Versions[i], &out.Versions[i], s); err != nil { - return err - } - } - } else { - out.Versions = nil - } - return nil -} - func convert_api_ThirdPartyResourceData_To_v1_ThirdPartyResourceData(in *api.ThirdPartyResourceData, out *ThirdPartyResourceData, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*api.ThirdPartyResourceData))(in) @@ -2299,29 +2266,6 @@ func convert_api_ThirdPartyResourceData_To_v1_ThirdPartyResourceData(in *api.Thi return nil } -func convert_api_ThirdPartyResourceList_To_v1_ThirdPartyResourceList(in *api.ThirdPartyResourceList, out *ThirdPartyResourceList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ThirdPartyResourceList))(in) - } - if err := convert_api_TypeMeta_To_v1_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { - return err - } - if err := convert_api_ListMeta_To_v1_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ThirdPartyResource, len(in.Items)) - for i := range in.Items { - if err := convert_api_ThirdPartyResource_To_v1_ThirdPartyResource(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - func convert_api_TypeMeta_To_v1_TypeMeta(in *api.TypeMeta, out *TypeMeta, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*api.TypeMeta))(in) @@ -2447,15 +2391,6 @@ func convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *Volu return nil } -func convert_v1_APIVersion_To_api_APIVersion(in *APIVersion, out *api.APIVersion, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*APIVersion))(in) - } - out.Name = in.Name - out.APIGroup = in.APIGroup - return nil -} - func convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in *AWSElasticBlockStoreVolumeSource, out *api.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*AWSElasticBlockStoreVolumeSource))(in) @@ -4680,30 +4615,6 @@ func convert_v1_TCPSocketAction_To_api_TCPSocketAction(in *TCPSocketAction, out return nil } -func convert_v1_ThirdPartyResource_To_api_ThirdPartyResource(in *ThirdPartyResource, out *api.ThirdPartyResource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*ThirdPartyResource))(in) - } - if err := convert_v1_TypeMeta_To_api_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { - return err - } - if err := convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - out.Description = in.Description - if in.Versions != nil { - out.Versions = make([]api.APIVersion, len(in.Versions)) - for i := range in.Versions { - if err := convert_v1_APIVersion_To_api_APIVersion(&in.Versions[i], &out.Versions[i], s); err != nil { - return err - } - } - } else { - out.Versions = nil - } - return nil -} - func convert_v1_ThirdPartyResourceData_To_api_ThirdPartyResourceData(in *ThirdPartyResourceData, out *api.ThirdPartyResourceData, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ThirdPartyResourceData))(in) @@ -4720,29 +4631,6 @@ func convert_v1_ThirdPartyResourceData_To_api_ThirdPartyResourceData(in *ThirdPa return nil } -func convert_v1_ThirdPartyResourceList_To_api_ThirdPartyResourceList(in *ThirdPartyResourceList, out *api.ThirdPartyResourceList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*ThirdPartyResourceList))(in) - } - if err := convert_v1_TypeMeta_To_api_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { - return err - } - if err := convert_v1_ListMeta_To_api_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]api.ThirdPartyResource, len(in.Items)) - for i := range in.Items { - if err := convert_v1_ThirdPartyResource_To_api_ThirdPartyResource(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - func convert_v1_TypeMeta_To_api_TypeMeta(in *TypeMeta, out *api.TypeMeta, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*TypeMeta))(in) @@ -4870,7 +4758,6 @@ func convert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api.Volu func init() { err := api.Scheme.AddGeneratedConversionFuncs( - convert_api_APIVersion_To_v1_APIVersion, convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource, convert_api_Binding_To_v1_Binding, convert_api_Capabilities_To_v1_Capabilities, @@ -4985,13 +4872,10 @@ func init() { convert_api_Status_To_v1_Status, convert_api_TCPSocketAction_To_v1_TCPSocketAction, convert_api_ThirdPartyResourceData_To_v1_ThirdPartyResourceData, - convert_api_ThirdPartyResourceList_To_v1_ThirdPartyResourceList, - convert_api_ThirdPartyResource_To_v1_ThirdPartyResource, convert_api_TypeMeta_To_v1_TypeMeta, convert_api_VolumeMount_To_v1_VolumeMount, convert_api_VolumeSource_To_v1_VolumeSource, convert_api_Volume_To_v1_Volume, - convert_v1_APIVersion_To_api_APIVersion, convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource, convert_v1_Binding_To_api_Binding, convert_v1_Capabilities_To_api_Capabilities, @@ -5106,8 +4990,6 @@ func init() { convert_v1_Status_To_api_Status, convert_v1_TCPSocketAction_To_api_TCPSocketAction, convert_v1_ThirdPartyResourceData_To_api_ThirdPartyResourceData, - convert_v1_ThirdPartyResourceList_To_api_ThirdPartyResourceList, - convert_v1_ThirdPartyResource_To_api_ThirdPartyResource, convert_v1_TypeMeta_To_api_TypeMeta, convert_v1_VolumeMount_To_api_VolumeMount, convert_v1_VolumeSource_To_api_VolumeSource, diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index 8a1b9031747..2b7fb503d79 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -45,12 +45,6 @@ func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c return nil } -func deepCopy_v1_APIVersion(in APIVersion, out *APIVersion, c *conversion.Cloner) error { - out.Name = in.Name - out.APIGroup = in.APIGroup - return nil -} - func deepCopy_v1_AWSElasticBlockStoreVolumeSource(in AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, c *conversion.Cloner) error { out.VolumeID = in.VolumeID out.FSType = in.FSType @@ -2032,27 +2026,6 @@ func deepCopy_v1_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *co return nil } -func deepCopy_v1_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { - if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { - return err - } - out.Description = in.Description - if in.Versions != nil { - out.Versions = make([]APIVersion, len(in.Versions)) - for i := range in.Versions { - if err := deepCopy_v1_APIVersion(in.Versions[i], &out.Versions[i], c); err != nil { - return err - } - } - } else { - out.Versions = nil - } - return nil -} - func deepCopy_v1_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPartyResourceData, c *conversion.Cloner) error { if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err @@ -2071,26 +2044,6 @@ func deepCopy_v1_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPar return nil } -func deepCopy_v1_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error { - if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - if err := deepCopy_v1_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ThirdPartyResource, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_ThirdPartyResource(in.Items[i], &out.Items[i], c); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - func deepCopy_v1_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error { out.Kind = in.Kind out.APIVersion = in.APIVersion @@ -2235,7 +2188,6 @@ func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) erro func init() { err := api.Scheme.AddGeneratedDeepCopyFuncs( deepCopy_resource_Quantity, - deepCopy_v1_APIVersion, deepCopy_v1_AWSElasticBlockStoreVolumeSource, deepCopy_v1_Binding, deepCopy_v1_Capabilities, @@ -2351,9 +2303,7 @@ func init() { deepCopy_v1_StatusCause, deepCopy_v1_StatusDetails, deepCopy_v1_TCPSocketAction, - deepCopy_v1_ThirdPartyResource, deepCopy_v1_ThirdPartyResourceData, - deepCopy_v1_ThirdPartyResourceList, deepCopy_v1_TypeMeta, deepCopy_v1_Volume, deepCopy_v1_VolumeMount, diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index a1f6710dea1..f87ceff2c54 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -1823,6 +1823,10 @@ func ValidateSecurityContext(sc *api.SecurityContext) errs.ValidationErrorList { return allErrs } +func ValidateThirdPartyResourceUpdate(old, update *api.ThirdPartyResource) errs.ValidationErrorList { + return ValidateThirdPartyResource(update) +} + func ValidateThirdPartyResource(obj *api.ThirdPartyResource) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} if len(obj.Name) == 0 { diff --git a/pkg/expapi/deep_copy_generated.go b/pkg/expapi/deep_copy_generated.go index a2406f4006f..ca1613efdb1 100644 --- a/pkg/expapi/deep_copy_generated.go +++ b/pkg/expapi/deep_copy_generated.go @@ -100,6 +100,12 @@ func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c return nil } +func deepCopy_expapi_APIVersion(in APIVersion, out *APIVersion, c *conversion.Cloner) error { + out.Name = in.Name + out.APIGroup = in.APIGroup + return nil +} + func deepCopy_expapi_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err @@ -228,6 +234,47 @@ func deepCopy_expapi_SubresourceReference(in SubresourceReference, out *Subresou return nil } +func deepCopy_expapi_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { + if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := deepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + out.Description = in.Description + if in.Versions != nil { + out.Versions = make([]APIVersion, len(in.Versions)) + for i := range in.Versions { + if err := deepCopy_expapi_APIVersion(in.Versions[i], &out.Versions[i], c); err != nil { + return err + } + } + } else { + out.Versions = nil + } + return nil +} + +func deepCopy_expapi_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error { + if err := deepCopy_api_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := deepCopy_api_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]ThirdPartyResource, len(in.Items)) + for i := range in.Items { + if err := deepCopy_expapi_ThirdPartyResource(in.Items[i], &out.Items[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) error { if newVal, err := c.DeepCopy(in.Time); err != nil { return err @@ -243,6 +290,7 @@ func init() { deepCopy_api_ObjectMeta, deepCopy_api_TypeMeta, deepCopy_resource_Quantity, + deepCopy_expapi_APIVersion, deepCopy_expapi_HorizontalPodAutoscaler, deepCopy_expapi_HorizontalPodAutoscalerList, deepCopy_expapi_HorizontalPodAutoscalerSpec, @@ -253,6 +301,8 @@ func init() { deepCopy_expapi_ScaleSpec, deepCopy_expapi_ScaleStatus, deepCopy_expapi_SubresourceReference, + deepCopy_expapi_ThirdPartyResource, + deepCopy_expapi_ThirdPartyResourceList, deepCopy_util_Time, ) if err != nil { diff --git a/pkg/expapi/register.go b/pkg/expapi/register.go index 93f07ac00eb..889b17ca8bd 100644 --- a/pkg/expapi/register.go +++ b/pkg/expapi/register.go @@ -32,6 +32,8 @@ func addKnownTypes() { &HorizontalPodAutoscalerList{}, &ReplicationControllerDummy{}, &Scale{}, + &ThirdPartyResource{}, + &ThirdPartyResourceList{}, ) } @@ -39,3 +41,5 @@ func (*HorizontalPodAutoscaler) IsAnAPIObject() {} func (*HorizontalPodAutoscalerList) IsAnAPIObject() {} func (*ReplicationControllerDummy) IsAnAPIObject() {} func (*Scale) IsAnAPIObject() {} +func (*ThirdPartyResource) IsAnAPIObject() {} +func (*ThirdPartyResourceList) IsAnAPIObject() {} diff --git a/pkg/expapi/types.go b/pkg/expapi/types.go index 77954f67697..1949e5f7b6c 100644 --- a/pkg/expapi/types.go +++ b/pkg/expapi/types.go @@ -134,3 +134,35 @@ type HorizontalPodAutoscalerList struct { Items []HorizontalPodAutoscaler `json:"items"` } + +// A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource +// types to the API. It consists of one or more Versions of the api. +type ThirdPartyResource struct { + api.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"` + + Description string `json:"description,omitempty" description:"The description of this object"` + + Versions []APIVersion `json:"versions,omitempty" description:"The versions for this third party object"` +} + +type ThirdPartyResourceList struct { + api.TypeMeta `json:",inline"` + api.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see http://docs.k8s.io/api-conventions.md#metadata"` + + Items []ThirdPartyResource `json:"items" description:"items is a list of schema objects"` +} + +// An APIVersion represents a single concrete version of an object model. +type APIVersion struct { + Name string `json:"name,omitempty" description:"name of this version (e.g. 'v1')"` + APIGroup string `json:"apiGroup,omitempty" description:"The API group to add this object into, default 'experimental'"` +} + +// An internal object, used for versioned storage in etcd. Not exposed to the end user. +type ThirdPartyResourceData struct { + api.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"` + + Data []byte `json:"name,omitempty" description:"the raw JSON data for this data"` +} diff --git a/pkg/expapi/v1/conversion_generated.go b/pkg/expapi/v1/conversion_generated.go index 5f20d3f4fed..70f391e09ea 100644 --- a/pkg/expapi/v1/conversion_generated.go +++ b/pkg/expapi/v1/conversion_generated.go @@ -155,6 +155,15 @@ func convert_v1_TypeMeta_To_api_TypeMeta(in *v1.TypeMeta, out *api.TypeMeta, s c return nil } +func convert_expapi_APIVersion_To_v1_APIVersion(in *expapi.APIVersion, out *APIVersion, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*expapi.APIVersion))(in) + } + out.Name = in.Name + out.APIGroup = in.APIGroup + return nil +} + func convert_expapi_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler(in *expapi.HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*expapi.HorizontalPodAutoscaler))(in) @@ -312,6 +321,62 @@ func convert_expapi_SubresourceReference_To_v1_SubresourceReference(in *expapi.S return nil } +func convert_expapi_ThirdPartyResource_To_v1_ThirdPartyResource(in *expapi.ThirdPartyResource, out *ThirdPartyResource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*expapi.ThirdPartyResource))(in) + } + if err := convert_api_TypeMeta_To_v1_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + out.Description = in.Description + if in.Versions != nil { + out.Versions = make([]APIVersion, len(in.Versions)) + for i := range in.Versions { + if err := convert_expapi_APIVersion_To_v1_APIVersion(&in.Versions[i], &out.Versions[i], s); err != nil { + return err + } + } + } else { + out.Versions = nil + } + return nil +} + +func convert_expapi_ThirdPartyResourceList_To_v1_ThirdPartyResourceList(in *expapi.ThirdPartyResourceList, out *ThirdPartyResourceList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*expapi.ThirdPartyResourceList))(in) + } + if err := convert_api_TypeMeta_To_v1_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := convert_api_ListMeta_To_v1_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]ThirdPartyResource, len(in.Items)) + for i := range in.Items { + if err := convert_expapi_ThirdPartyResource_To_v1_ThirdPartyResource(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func convert_v1_APIVersion_To_expapi_APIVersion(in *APIVersion, out *expapi.APIVersion, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*APIVersion))(in) + } + out.Name = in.Name + out.APIGroup = in.APIGroup + return nil +} + func convert_v1_HorizontalPodAutoscaler_To_expapi_HorizontalPodAutoscaler(in *HorizontalPodAutoscaler, out *expapi.HorizontalPodAutoscaler, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*HorizontalPodAutoscaler))(in) @@ -469,11 +534,59 @@ func convert_v1_SubresourceReference_To_expapi_SubresourceReference(in *Subresou return nil } +func convert_v1_ThirdPartyResource_To_expapi_ThirdPartyResource(in *ThirdPartyResource, out *expapi.ThirdPartyResource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*ThirdPartyResource))(in) + } + if err := convert_v1_TypeMeta_To_api_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + out.Description = in.Description + if in.Versions != nil { + out.Versions = make([]expapi.APIVersion, len(in.Versions)) + for i := range in.Versions { + if err := convert_v1_APIVersion_To_expapi_APIVersion(&in.Versions[i], &out.Versions[i], s); err != nil { + return err + } + } + } else { + out.Versions = nil + } + return nil +} + +func convert_v1_ThirdPartyResourceList_To_expapi_ThirdPartyResourceList(in *ThirdPartyResourceList, out *expapi.ThirdPartyResourceList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*ThirdPartyResourceList))(in) + } + if err := convert_v1_TypeMeta_To_api_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := convert_v1_ListMeta_To_api_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]expapi.ThirdPartyResource, len(in.Items)) + for i := range in.Items { + if err := convert_v1_ThirdPartyResource_To_expapi_ThirdPartyResource(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + func init() { err := api.Scheme.AddGeneratedConversionFuncs( convert_api_ListMeta_To_v1_ListMeta, convert_api_ObjectMeta_To_v1_ObjectMeta, convert_api_TypeMeta_To_v1_TypeMeta, + convert_expapi_APIVersion_To_v1_APIVersion, convert_expapi_HorizontalPodAutoscalerList_To_v1_HorizontalPodAutoscalerList, convert_expapi_HorizontalPodAutoscalerSpec_To_v1_HorizontalPodAutoscalerSpec, convert_expapi_HorizontalPodAutoscalerStatus_To_v1_HorizontalPodAutoscalerStatus, @@ -484,6 +597,9 @@ func init() { convert_expapi_ScaleStatus_To_v1_ScaleStatus, convert_expapi_Scale_To_v1_Scale, convert_expapi_SubresourceReference_To_v1_SubresourceReference, + convert_expapi_ThirdPartyResourceList_To_v1_ThirdPartyResourceList, + convert_expapi_ThirdPartyResource_To_v1_ThirdPartyResource, + convert_v1_APIVersion_To_expapi_APIVersion, convert_v1_HorizontalPodAutoscalerList_To_expapi_HorizontalPodAutoscalerList, convert_v1_HorizontalPodAutoscalerSpec_To_expapi_HorizontalPodAutoscalerSpec, convert_v1_HorizontalPodAutoscalerStatus_To_expapi_HorizontalPodAutoscalerStatus, @@ -496,6 +612,8 @@ func init() { convert_v1_ScaleStatus_To_expapi_ScaleStatus, convert_v1_Scale_To_expapi_Scale, convert_v1_SubresourceReference_To_expapi_SubresourceReference, + convert_v1_ThirdPartyResourceList_To_expapi_ThirdPartyResourceList, + convert_v1_ThirdPartyResource_To_expapi_ThirdPartyResource, convert_v1_TypeMeta_To_api_TypeMeta, ) if err != nil { diff --git a/pkg/expapi/v1/deep_copy_generated.go b/pkg/expapi/v1/deep_copy_generated.go index 2e151292345..9350fe73617 100644 --- a/pkg/expapi/v1/deep_copy_generated.go +++ b/pkg/expapi/v1/deep_copy_generated.go @@ -101,6 +101,12 @@ func deepCopy_v1_TypeMeta(in v1.TypeMeta, out *v1.TypeMeta, c *conversion.Cloner return nil } +func deepCopy_v1_APIVersion(in APIVersion, out *APIVersion, c *conversion.Cloner) error { + out.Name = in.Name + out.APIGroup = in.APIGroup + return nil +} + func deepCopy_v1_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err @@ -229,6 +235,47 @@ func deepCopy_v1_SubresourceReference(in SubresourceReference, out *SubresourceR return nil } +func deepCopy_v1_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { + if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + out.Description = in.Description + if in.Versions != nil { + out.Versions = make([]APIVersion, len(in.Versions)) + for i := range in.Versions { + if err := deepCopy_v1_APIVersion(in.Versions[i], &out.Versions[i], c); err != nil { + return err + } + } + } else { + out.Versions = nil + } + return nil +} + +func deepCopy_v1_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error { + if err := deepCopy_v1_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := deepCopy_v1_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]ThirdPartyResource, len(in.Items)) + for i := range in.Items { + if err := deepCopy_v1_ThirdPartyResource(in.Items[i], &out.Items[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) error { if newVal, err := c.DeepCopy(in.Time); err != nil { return err @@ -244,6 +291,7 @@ func init() { deepCopy_v1_ListMeta, deepCopy_v1_ObjectMeta, deepCopy_v1_TypeMeta, + deepCopy_v1_APIVersion, deepCopy_v1_HorizontalPodAutoscaler, deepCopy_v1_HorizontalPodAutoscalerList, deepCopy_v1_HorizontalPodAutoscalerSpec, @@ -254,6 +302,8 @@ func init() { deepCopy_v1_ScaleSpec, deepCopy_v1_ScaleStatus, deepCopy_v1_SubresourceReference, + deepCopy_v1_ThirdPartyResource, + deepCopy_v1_ThirdPartyResourceList, deepCopy_util_Time, ) if err != nil { diff --git a/pkg/expapi/v1/defaults.go b/pkg/expapi/v1/defaults.go index d4a06d1bcf9..e1d02ef1a1d 100644 --- a/pkg/expapi/v1/defaults.go +++ b/pkg/expapi/v1/defaults.go @@ -16,5 +16,13 @@ limitations under the License. package v1 +import "k8s.io/kubernetes/pkg/api" + func addDefaultingFuncs() { + api.Scheme.AddDefaultingFuncs( + func(obj *APIVersion) { + if len(obj.APIGroup) == 0 { + obj.APIGroup = "experimental" + } + }) } diff --git a/pkg/expapi/v1/register.go b/pkg/expapi/v1/register.go index 1ad15196db8..efde9936153 100644 --- a/pkg/expapi/v1/register.go +++ b/pkg/expapi/v1/register.go @@ -36,6 +36,8 @@ func addKnownTypes() { &HorizontalPodAutoscalerList{}, &ReplicationControllerDummy{}, &Scale{}, + &ThirdPartyResource{}, + &ThirdPartyResourceList{}, ) } @@ -43,3 +45,5 @@ func (*HorizontalPodAutoscaler) IsAnAPIObject() {} func (*HorizontalPodAutoscalerList) IsAnAPIObject() {} func (*ReplicationControllerDummy) IsAnAPIObject() {} func (*Scale) IsAnAPIObject() {} +func (*ThirdPartyResource) IsAnAPIObject() {} +func (*ThirdPartyResourceList) IsAnAPIObject() {} diff --git a/pkg/expapi/v1/types.go b/pkg/expapi/v1/types.go index abfec1b2ced..33c6a7086f9 100644 --- a/pkg/expapi/v1/types.go +++ b/pkg/expapi/v1/types.go @@ -120,3 +120,35 @@ type HorizontalPodAutoscalerList struct { Items []HorizontalPodAutoscaler `json:"items" description:"list of horizontal pod autoscalers"` } + +// A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource +// types to the API. It consists of one or more Versions of the api. +type ThirdPartyResource struct { + v1.TypeMeta `json:",inline"` + v1.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"` + + Description string `json:"description,omitempty" description:"The description of this object"` + + Versions []APIVersion `json:"versions,omitempty" description:"The versions for this third party object"` +} + +type ThirdPartyResourceList struct { + v1.TypeMeta `json:",inline"` + v1.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see http://docs.k8s.io/api-conventions.md#metadata"` + + Items []ThirdPartyResource `json:"items" description:"items is a list of schema objects"` +} + +// An APIVersion represents a single concrete version of an object model. +type APIVersion struct { + Name string `json:"name,omitempty" description:"name of this version (e.g. 'v1')"` + APIGroup string `json:"apiGroup,omitempty" description:"The API group to add this object into, default 'experimental'"` +} + +// An internal object, used for versioned storage in etcd. Not exposed to the end user. +type ThirdPartyResourceData struct { + v1.TypeMeta `json:",inline"` + v1.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"` + + Data []byte `json:"name,omitempty" description:"the raw JSON data for this data"` +} diff --git a/pkg/expapi/validation/validation.go b/pkg/expapi/validation/validation.go index b61300e4e4f..6ae820750a2 100644 --- a/pkg/expapi/validation/validation.go +++ b/pkg/expapi/validation/validation.go @@ -20,6 +20,7 @@ import ( "k8s.io/kubernetes/pkg/api" apivalidation "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/util" errs "k8s.io/kubernetes/pkg/util/fielderrors" ) @@ -65,3 +66,26 @@ func ValidateHorizontalPodAutoscalerUpdate(newAutoscler, oldAutoscaler *expapi.H allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscler.Spec)...) return allErrs } + +func ValidateThirdPartyResourceUpdate(old, update *expapi.ThirdPartyResource) errs.ValidationErrorList { + return ValidateThirdPartyResource(update) +} + +func ValidateThirdPartyResource(obj *expapi.ThirdPartyResource) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + if len(obj.Name) == 0 { + allErrs = append(allErrs, errs.NewFieldInvalid("name", obj.Name, "name must be non-empty")) + } + versions := util.StringSet{} + for ix := range obj.Versions { + version := &obj.Versions[ix] + if len(version.Name) == 0 { + allErrs = append(allErrs, errs.NewFieldInvalid("name", version, "name can not be empty")) + } + if versions.Has(version.Name) { + allErrs = append(allErrs, errs.NewFieldDuplicate("version", version)) + } + versions.Insert(version.Name) + } + return allErrs +} diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 217b6aaa659..07337797305 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -33,6 +33,7 @@ import ( "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/conversion" + "k8s.io/kubernetes/pkg/expapi" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util/jsonpath" @@ -283,6 +284,7 @@ var serviceAccountColumns = []string{"NAME", "SECRETS", "AGE"} var persistentVolumeColumns = []string{"NAME", "LABELS", "CAPACITY", "ACCESSMODES", "STATUS", "CLAIM", "REASON", "AGE"} var persistentVolumeClaimColumns = []string{"NAME", "LABELS", "STATUS", "VOLUME", "CAPACITY", "ACCESSMODES", "AGE"} var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"} +var thirdPartyResourceColumns = []string{"NAME", "DESCRIPTION", "VERSION(S)"} var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too. // addDefaultHandlers adds print handlers for default Kubernetes types. @@ -317,6 +319,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() { h.Handler(persistentVolumeColumns, printPersistentVolumeList) h.Handler(componentStatusColumns, printComponentStatus) h.Handler(componentStatusColumns, printComponentStatusList) + h.Handler(thirdPartyResourceColumns, printThirdPartyResource) + h.Handler(thirdPartyResourceColumns, printThirdPartyResourceList) } func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error { @@ -1014,6 +1018,29 @@ func printComponentStatusList(list *api.ComponentStatusList, w io.Writer, withNa return nil } +func printThirdPartyResource(rsrc *expapi.ThirdPartyResource, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error { + versions := make([]string, len(rsrc.Versions)) + for ix := range rsrc.Versions { + version := &rsrc.Versions[ix] + versions[ix] = fmt.Sprint("%s/%s", version.APIGroup, version.Name) + } + versionsString := strings.Join(versions, ",") + if _, err := fmt.Fprintf(w, "%s\t%s\t%s", rsrc.Name, rsrc.Description, versionsString); err != nil { + return err + } + return nil +} + +func printThirdPartyResourceList(list *expapi.ThirdPartyResourceList, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error { + for _, item := range list.Items { + if err := printThirdPartyResource(&item, w, withNamespace, wide, showAll, columnLabels); err != nil { + return err + } + } + + return nil +} + func appendLabels(itemLabels map[string]string, columnLabels []string) string { var buffer bytes.Buffer diff --git a/pkg/master/master.go b/pkg/master/master.go index 8eacf783991..d90c1f19767 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -71,6 +71,7 @@ import ( serviceetcd "k8s.io/kubernetes/pkg/registry/service/etcd" ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator" serviceaccountetcd "k8s.io/kubernetes/pkg/registry/serviceaccount/etcd" + thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd" "k8s.io/kubernetes/pkg/storage" etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" "k8s.io/kubernetes/pkg/tools" @@ -779,13 +780,15 @@ func (m *Master) api_v1() *apiserver.APIGroupVersion { // expapi returns the resources and codec for the experimental api func (m *Master) expapi(c *Config) *apiserver.APIGroupVersion { - controllerStorage := expcontrolleretcd.NewStorage(c.DatabaseStorage) - autoscalerStorage := horizontalpodautoscaleretcd.NewREST(c.DatabaseStorage) + controllerStorage := expcontrolleretcd.NewStorage(c.ExpDatabaseStorage) + autoscalerStorage := horizontalpodautoscaleretcd.NewREST(c.ExpDatabaseStorage) + thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(c.ExpDatabaseStorage) storage := map[string]rest.Storage{ strings.ToLower("replicationControllers"): controllerStorage.ReplicationController, strings.ToLower("replicationControllers/scale"): controllerStorage.Scale, strings.ToLower("horizontalpodautoscalers"): autoscalerStorage, + "thirdpartyresources": thirdPartyResourceStorage, } return &apiserver.APIGroupVersion{ diff --git a/pkg/registry/thirdpartyresource/doc.go b/pkg/registry/thirdpartyresource/doc.go new file mode 100644 index 00000000000..7f52880dac5 --- /dev/null +++ b/pkg/registry/thirdpartyresource/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package thirdpartyresource provides Registry interface and its REST +// implementation for storing ThirdPartyResource api objects. +package thirdpartyresource diff --git a/pkg/registry/thirdpartyresource/etcd/etcd.go b/pkg/registry/thirdpartyresource/etcd/etcd.go new file mode 100644 index 00000000000..d72e7816a82 --- /dev/null +++ b/pkg/registry/thirdpartyresource/etcd/etcd.go @@ -0,0 +1,63 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/thirdpartyresource" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" +) + +// REST implements a RESTStorage for ThirdPartyResources against etcd +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a registry which will store ThirdPartyResource in the given helper +func NewREST(s storage.Interface) *REST { + prefix := "/thirdpartyresources" + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &expapi.ThirdPartyResource{} }, + NewListFunc: func() runtime.Object { return &expapi.ThirdPartyResourceList{} }, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*expapi.ThirdPartyResource).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return thirdpartyresource.Matcher(label, field) + }, + EndpointName: "thirdPartyResources", + CreateStrategy: thirdpartyresource.Strategy, + UpdateStrategy: thirdpartyresource.Strategy, + + Storage: s, + } + + return &REST{store} +} diff --git a/pkg/registry/thirdpartyresource/etcd/etcd_test.go b/pkg/registry/thirdpartyresource/etcd/etcd_test.go new file mode 100644 index 00000000000..97581297ac2 --- /dev/null +++ b/pkg/registry/thirdpartyresource/etcd/etcd_test.go @@ -0,0 +1,205 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest/resttest" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/expapi/v1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" + "k8s.io/kubernetes/pkg/tools" + "k8s.io/kubernetes/pkg/tools/etcdtest" + + "github.com/coreos/go-etcd/etcd" +) + +var scheme *runtime.Scheme +var codec runtime.Codec + +func init() { + // Ensure that expapi/v1 packege is used, so that it will get initialized and register HorizontalPodAutoscaler object. + _ = v1.ThirdPartyResource{} +} + +func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, storage.Interface) { + fakeEtcdClient := tools.NewFakeEtcdClient(t) + fakeEtcdClient.TestIndex = true + etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix()) + storage := NewREST(etcdStorage) + return storage, fakeEtcdClient, etcdStorage +} + +func validNewThirdPartyResource(name string) *expapi.ThirdPartyResource { + return &expapi.ThirdPartyResource{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Versions: []expapi.APIVersion{ + { + Name: "stable/v1", + }, + }, + } +} + +func TestCreate(t *testing.T) { + storage, fakeEtcdClient, _ := newStorage(t) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + rsrc := validNewThirdPartyResource("foo") + rsrc.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + rsrc, + // invalid + &expapi.ThirdPartyResource{}, + ) +} + +func TestUpdate(t *testing.T) { + storage, fakeEtcdClient, _ := newStorage(t) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + key, err := storage.KeyFunc(test.TestContext(), "foo") + if err != nil { + t.Fatal(err) + } + key = etcdtest.AddPrefix(key) + fakeEtcdClient.ExpectNotFoundGet(key) + fakeEtcdClient.ChangeIndex = 2 + rsrc := validNewThirdPartyResource("foo") + existing := validNewThirdPartyResource("exists") + existing.Namespace = test.TestNamespace() + obj, err := storage.Create(test.TestContext(), existing) + if err != nil { + t.Fatalf("unable to create object: %v", err) + } + older := obj.(*expapi.ThirdPartyResource) + older.ResourceVersion = "1" + test.TestUpdate( + rsrc, + existing, + older, + ) +} + +func TestDelete(t *testing.T) { + ctx := api.NewDefaultContext() + storage, fakeEtcdClient, _ := newStorage(t) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + rsrc := validNewThirdPartyResource("foo2") + key, _ := storage.KeyFunc(ctx, "foo2") + key = etcdtest.AddPrefix(key) + createFn := func() runtime.Object { + fakeEtcdClient.Data[key] = tools.EtcdResponseWithError{ + R: &etcd.Response{ + Node: &etcd.Node{ + Value: runtime.EncodeOrDie(testapi.Codec(), rsrc), + ModifiedIndex: 1, + }, + }, + } + return rsrc + } + gracefulSetFn := func() bool { + if fakeEtcdClient.Data[key].R.Node == nil { + return false + } + return fakeEtcdClient.Data[key].R.Node.TTL == 30 + } + test.TestDeleteNoGraceful(createFn, gracefulSetFn) +} + +func TestGet(t *testing.T) { + storage, fakeEtcdClient, _ := newStorage(t) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + rsrc := validNewThirdPartyResource("foo") + test.TestGet(rsrc) +} + +func TestEmptyList(t *testing.T) { + ctx := api.NewDefaultContext() + registry, fakeClient, _ := newStorage(t) + fakeClient.ChangeIndex = 1 + key := registry.KeyRootFunc(ctx) + key = etcdtest.AddPrefix(key) + fakeClient.Data[key] = tools.EtcdResponseWithError{ + R: &etcd.Response{}, + E: fakeClient.NewError(tools.EtcdErrorCodeNotFound), + } + rsrcList, err := registry.List(ctx, labels.Everything(), fields.Everything()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(rsrcList.(*expapi.ThirdPartyResourceList).Items) != 0 { + t.Errorf("Unexpected non-zero autoscaler list: %#v", rsrcList) + } + if rsrcList.(*expapi.ThirdPartyResourceList).ResourceVersion != "1" { + t.Errorf("Unexpected resource version: %#v", rsrcList) + } +} + +func TestList(t *testing.T) { + ctx := api.NewDefaultContext() + registry, fakeClient, _ := newStorage(t) + fakeClient.ChangeIndex = 1 + key := registry.KeyRootFunc(ctx) + key = etcdtest.AddPrefix(key) + fakeClient.Data[key] = tools.EtcdResponseWithError{ + R: &etcd.Response{ + Node: &etcd.Node{ + Nodes: []*etcd.Node{ + { + Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResource{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + }), + }, + { + Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResource{ + ObjectMeta: api.ObjectMeta{Name: "bar"}, + }), + }, + }, + }, + }, + } + obj, err := registry.List(ctx, labels.Everything(), fields.Everything()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + rsrcList := obj.(*expapi.ThirdPartyResourceList) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(rsrcList.Items) != 2 { + t.Errorf("Unexpected ThirdPartyResource list: %#v", rsrcList) + } + if rsrcList.Items[0].Name != "foo" { + t.Errorf("Unexpected ThirdPartyResource: %#v", rsrcList.Items[0]) + } + if rsrcList.Items[1].Name != "bar" { + t.Errorf("Unexpected ThirdPartyResource: %#v", rsrcList.Items[1]) + } +} diff --git a/pkg/registry/thirdpartyresource/registry.go b/pkg/registry/thirdpartyresource/registry.go new file mode 100644 index 00000000000..d30454f5c40 --- /dev/null +++ b/pkg/registry/thirdpartyresource/registry.go @@ -0,0 +1,88 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package thirdpartyresource + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface implemented by things that know how to store ThirdPartyResource objects. +type Registry interface { + // ListThirdPartyResources obtains a list of ThirdPartyResources having labels which match selector. + ListThirdPartyResources(ctx api.Context, selector labels.Selector) (*expapi.ThirdPartyResourceList, error) + // Watch for new/changed/deleted ThirdPartyResources + WatchThirdPartyResources(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) + // Get a specific ThirdPartyResource + GetThirdPartyResource(ctx api.Context, name string) (*expapi.ThirdPartyResource, error) + // Create a ThirdPartyResource based on a specification. + CreateThirdPartyResource(ctx api.Context, resource *expapi.ThirdPartyResource) (*expapi.ThirdPartyResource, error) + // Update an existing ThirdPartyResource + UpdateThirdPartyResource(ctx api.Context, resource *expapi.ThirdPartyResource) (*expapi.ThirdPartyResource, error) + // Delete an existing ThirdPartyResource + DeleteThirdPartyResource(ctx api.Context, name string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListThirdPartyResources(ctx api.Context, label labels.Selector) (*expapi.ThirdPartyResourceList, error) { + obj, err := s.List(ctx, label, fields.Everything()) + if err != nil { + return nil, err + } + return obj.(*expapi.ThirdPartyResourceList), nil +} + +func (s *storage) WatchThirdPartyResources(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) { + return s.Watch(ctx, label, field, resourceVersion) +} + +func (s *storage) GetThirdPartyResource(ctx api.Context, name string) (*expapi.ThirdPartyResource, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*expapi.ThirdPartyResource), nil +} + +func (s *storage) CreateThirdPartyResource(ctx api.Context, ThirdPartyResource *expapi.ThirdPartyResource) (*expapi.ThirdPartyResource, error) { + obj, err := s.Create(ctx, ThirdPartyResource) + return obj.(*expapi.ThirdPartyResource), err +} + +func (s *storage) UpdateThirdPartyResource(ctx api.Context, ThirdPartyResource *expapi.ThirdPartyResource) (*expapi.ThirdPartyResource, error) { + obj, _, err := s.Update(ctx, ThirdPartyResource) + return obj.(*expapi.ThirdPartyResource), err +} + +func (s *storage) DeleteThirdPartyResource(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/pkg/registry/thirdpartyresource/strategy.go b/pkg/registry/thirdpartyresource/strategy.go new file mode 100644 index 00000000000..cbdea16a107 --- /dev/null +++ b/pkg/registry/thirdpartyresource/strategy.go @@ -0,0 +1,88 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package thirdpartyresource + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/expapi/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/fielderrors" +) + +// strategy implements behavior for ThirdPartyResource objects +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating ThirdPartyResource +// objects via the REST API. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +var _ = rest.RESTCreateStrategy(Strategy) + +var _ = rest.RESTUpdateStrategy(Strategy) + +func (strategy) NamespaceScoped() bool { + return true +} + +func (strategy) PrepareForCreate(obj runtime.Object) { +} + +func (strategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList { + return validation.ValidateThirdPartyResource(obj.(*expapi.ThirdPartyResource)) +} + +func (strategy) AllowCreateOnUpdate() bool { + return false +} + +func (strategy) PrepareForUpdate(obj, old runtime.Object) { +} + +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList { + return validation.ValidateThirdPartyResourceUpdate(old.(*expapi.ThirdPartyResource), obj.(*expapi.ThirdPartyResource)) +} + +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*expapi.ThirdPartyResource) + if !ok { + return false, fmt.Errorf("not a ThirdPartyResource") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *expapi.ThirdPartyResource) labels.Set { + return labels.Set{} +}