mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 21:47:07 +00:00
Add support for installing custom object APIs
This commit is contained in:
parent
7bfc8b5f37
commit
b196d0f84b
@ -287,7 +287,6 @@ _kubectl_get()
|
|||||||
must_have_one_noun+=("secret")
|
must_have_one_noun+=("secret")
|
||||||
must_have_one_noun+=("service")
|
must_have_one_noun+=("service")
|
||||||
must_have_one_noun+=("serviceaccount")
|
must_have_one_noun+=("serviceaccount")
|
||||||
must_have_one_noun+=("thirdpartyresource")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_kubectl_describe()
|
_kubectl_describe()
|
||||||
@ -461,7 +460,6 @@ _kubectl_delete()
|
|||||||
must_have_one_noun+=("secret")
|
must_have_one_noun+=("secret")
|
||||||
must_have_one_noun+=("service")
|
must_have_one_noun+=("service")
|
||||||
must_have_one_noun+=("serviceaccount")
|
must_have_one_noun+=("serviceaccount")
|
||||||
must_have_one_noun+=("thirdpartyresource")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_kubectl_namespace()
|
_kubectl_namespace()
|
||||||
|
@ -1982,24 +1982,6 @@ func deepCopy_api_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *c
|
|||||||
return 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
|
|
||||||
}
|
|
||||||
if err := deepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if in.Data != nil {
|
|
||||||
out.Data = make([]uint8, len(in.Data))
|
|
||||||
for i := range in.Data {
|
|
||||||
out.Data[i] = in.Data[i]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.Data = nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deepCopy_api_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error {
|
func deepCopy_api_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error {
|
||||||
out.Kind = in.Kind
|
out.Kind = in.Kind
|
||||||
out.APIVersion = in.APIVersion
|
out.APIVersion = in.APIVersion
|
||||||
@ -2258,7 +2240,6 @@ func init() {
|
|||||||
deepCopy_api_StatusCause,
|
deepCopy_api_StatusCause,
|
||||||
deepCopy_api_StatusDetails,
|
deepCopy_api_StatusDetails,
|
||||||
deepCopy_api_TCPSocketAction,
|
deepCopy_api_TCPSocketAction,
|
||||||
deepCopy_api_ThirdPartyResourceData,
|
|
||||||
deepCopy_api_TypeMeta,
|
deepCopy_api_TypeMeta,
|
||||||
deepCopy_api_Volume,
|
deepCopy_api_Volume,
|
||||||
deepCopy_api_VolumeMount,
|
deepCopy_api_VolumeMount,
|
||||||
|
@ -2199,22 +2199,6 @@ func convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction,
|
|||||||
return 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)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if err := s.Convert(&in.Data, &out.Data, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convert_api_TypeMeta_To_v1_TypeMeta(in *api.TypeMeta, out *TypeMeta, s conversion.Scope) error {
|
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 {
|
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||||
defaulting.(func(*api.TypeMeta))(in)
|
defaulting.(func(*api.TypeMeta))(in)
|
||||||
@ -4513,22 +4497,6 @@ func convert_v1_TCPSocketAction_To_api_TCPSocketAction(in *TCPSocketAction, out
|
|||||||
return 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)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if err := s.Convert(&in.Data, &out.Data, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convert_v1_TypeMeta_To_api_TypeMeta(in *TypeMeta, out *api.TypeMeta, s conversion.Scope) error {
|
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 {
|
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||||
defaulting.(func(*TypeMeta))(in)
|
defaulting.(func(*TypeMeta))(in)
|
||||||
@ -4765,7 +4733,6 @@ func init() {
|
|||||||
convert_api_StatusDetails_To_v1_StatusDetails,
|
convert_api_StatusDetails_To_v1_StatusDetails,
|
||||||
convert_api_Status_To_v1_Status,
|
convert_api_Status_To_v1_Status,
|
||||||
convert_api_TCPSocketAction_To_v1_TCPSocketAction,
|
convert_api_TCPSocketAction_To_v1_TCPSocketAction,
|
||||||
convert_api_ThirdPartyResourceData_To_v1_ThirdPartyResourceData,
|
|
||||||
convert_api_TypeMeta_To_v1_TypeMeta,
|
convert_api_TypeMeta_To_v1_TypeMeta,
|
||||||
convert_api_VolumeMount_To_v1_VolumeMount,
|
convert_api_VolumeMount_To_v1_VolumeMount,
|
||||||
convert_api_VolumeSource_To_v1_VolumeSource,
|
convert_api_VolumeSource_To_v1_VolumeSource,
|
||||||
@ -4879,7 +4846,6 @@ func init() {
|
|||||||
convert_v1_StatusDetails_To_api_StatusDetails,
|
convert_v1_StatusDetails_To_api_StatusDetails,
|
||||||
convert_v1_Status_To_api_Status,
|
convert_v1_Status_To_api_Status,
|
||||||
convert_v1_TCPSocketAction_To_api_TCPSocketAction,
|
convert_v1_TCPSocketAction_To_api_TCPSocketAction,
|
||||||
convert_v1_ThirdPartyResourceData_To_api_ThirdPartyResourceData,
|
|
||||||
convert_v1_TypeMeta_To_api_TypeMeta,
|
convert_v1_TypeMeta_To_api_TypeMeta,
|
||||||
convert_v1_VolumeMount_To_api_VolumeMount,
|
convert_v1_VolumeMount_To_api_VolumeMount,
|
||||||
convert_v1_VolumeSource_To_api_VolumeSource,
|
convert_v1_VolumeSource_To_api_VolumeSource,
|
||||||
|
@ -1987,24 +1987,6 @@ func deepCopy_v1_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *co
|
|||||||
return 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
|
|
||||||
}
|
|
||||||
if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if in.Data != nil {
|
|
||||||
out.Data = make([]uint8, len(in.Data))
|
|
||||||
for i := range in.Data {
|
|
||||||
out.Data[i] = in.Data[i]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.Data = nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deepCopy_v1_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error {
|
func deepCopy_v1_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error {
|
||||||
out.Kind = in.Kind
|
out.Kind = in.Kind
|
||||||
out.APIVersion = in.APIVersion
|
out.APIVersion = in.APIVersion
|
||||||
@ -2260,7 +2242,6 @@ func init() {
|
|||||||
deepCopy_v1_StatusCause,
|
deepCopy_v1_StatusCause,
|
||||||
deepCopy_v1_StatusDetails,
|
deepCopy_v1_StatusDetails,
|
||||||
deepCopy_v1_TCPSocketAction,
|
deepCopy_v1_TCPSocketAction,
|
||||||
deepCopy_v1_ThirdPartyResourceData,
|
|
||||||
deepCopy_v1_TypeMeta,
|
deepCopy_v1_TypeMeta,
|
||||||
deepCopy_v1_Volume,
|
deepCopy_v1_Volume,
|
||||||
deepCopy_v1_VolumeMount,
|
deepCopy_v1_VolumeMount,
|
||||||
|
@ -248,6 +248,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
|
|
||||||
var ctxFn ContextFunc
|
var ctxFn ContextFunc
|
||||||
ctxFn = func(req *restful.Request) api.Context {
|
ctxFn = func(req *restful.Request) api.Context {
|
||||||
|
if context == nil {
|
||||||
|
return api.NewContext()
|
||||||
|
}
|
||||||
if ctx, ok := context.Get(req.Request); ok {
|
if ctx, ok := context.Get(req.Request); ok {
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
@ -309,7 +309,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if admit.Handles(admission.Create) {
|
if admit != nil && admit.Handles(admission.Create) {
|
||||||
userInfo, _ := api.UserFrom(ctx)
|
userInfo, _ := api.UserFrom(ctx)
|
||||||
|
|
||||||
err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo))
|
err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo))
|
||||||
@ -481,7 +481,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if admit.Handles(admission.Update) {
|
if admit != nil && admit.Handles(admission.Update) {
|
||||||
userInfo, _ := api.UserFrom(ctx)
|
userInfo, _ := api.UserFrom(ctx)
|
||||||
|
|
||||||
err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
|
err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
|
||||||
@ -546,7 +546,7 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if admit.Handles(admission.Delete) {
|
if admit != nil && admit.Handles(admission.Delete) {
|
||||||
userInfo, _ := api.UserFrom(ctx)
|
userInfo, _ := api.UserFrom(ctx)
|
||||||
|
|
||||||
err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo))
|
err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo))
|
||||||
|
@ -257,3 +257,15 @@ func ValidateDeployment(obj *expapi.Deployment) errs.ValidationErrorList {
|
|||||||
allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec).Prefix("spec")...)
|
allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec).Prefix("spec")...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateThirdPartyResourceDataUpdate(old, update *expapi.ThirdPartyResourceData) errs.ValidationErrorList {
|
||||||
|
return ValidateThirdPartyResourceData(update)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateThirdPartyResourceData(obj *expapi.ThirdPartyResourceData) errs.ValidationErrorList {
|
||||||
|
allErrs := errs.ValidationErrorList{}
|
||||||
|
if len(obj.Name) == 0 {
|
||||||
|
allErrs = append(allErrs, errs.NewFieldInvalid("name", obj.Name, "name must be non-empty"))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
@ -72,6 +72,7 @@ import (
|
|||||||
ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator"
|
ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator"
|
||||||
serviceaccountetcd "k8s.io/kubernetes/pkg/registry/serviceaccount/etcd"
|
serviceaccountetcd "k8s.io/kubernetes/pkg/registry/serviceaccount/etcd"
|
||||||
thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd"
|
thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
|
||||||
thirdpartyresourcedataetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd"
|
thirdpartyresourcedataetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd"
|
||||||
"k8s.io/kubernetes/pkg/storage"
|
"k8s.io/kubernetes/pkg/storage"
|
||||||
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
|
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
|
||||||
@ -771,26 +772,28 @@ func (m *Master) api_v1() *apiserver.APIGroupVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Master) InstallThirdPartyAPI(rsrc *expapi.ThirdPartyResource) error {
|
func (m *Master) InstallThirdPartyAPI(rsrc *expapi.ThirdPartyResource) error {
|
||||||
thirdparty := m.thirdpartyapi(rsrc)
|
kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
thirdparty := m.thirdpartyapi(group, strings.ToLower(kind)+"s", rsrc.Versions[0].Name)
|
||||||
if err := thirdparty.InstallREST(m.handlerContainer); err != nil {
|
if err := thirdparty.InstallREST(m.handlerContainer); err != nil {
|
||||||
glog.Fatalf("Unable to setup thirdparty api: %v", err)
|
glog.Fatalf("Unable to setup thirdparty api: %v", err)
|
||||||
}
|
}
|
||||||
thirdPartyPrefix := "/thirdparty/" + rsrc.Name + "/"
|
thirdPartyPrefix := "/thirdparty/" + group + "/"
|
||||||
apiRoot := rsrc.Name
|
|
||||||
apiserver.AddApiWebService(m.handlerContainer, thirdPartyPrefix, []string{rsrc.Versions[0].Name})
|
apiserver.AddApiWebService(m.handlerContainer, thirdPartyPrefix, []string{rsrc.Versions[0].Name})
|
||||||
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: util.NewStringSet(strings.TrimPrefix(apiRoot, "/")), RestMapper: thirdparty.Mapper}
|
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: util.NewStringSet(strings.TrimPrefix(group, "/")), RestMapper: thirdparty.Mapper}
|
||||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, thirdPartyRequestInfoResolver, []string{thirdparty.Version})
|
apiserver.InstallServiceErrorHandler(m.handlerContainer, thirdPartyRequestInfoResolver, []string{thirdparty.Version})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Master) thirdpartyapi(rsrc *expapi.ThirdPartyResource) *apiserver.APIGroupVersion {
|
func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupVersion {
|
||||||
resourceStorage := thirdpartyresourcedataetcd.NewREST(m.thirdPartyStorage)
|
resourceStorage := thirdpartyresourcedataetcd.NewREST(m.thirdPartyStorage, group, kind)
|
||||||
|
|
||||||
apiGroup := rsrc.Name
|
apiRoot := "/thirdparty/" + group + "/"
|
||||||
apiRoot := "/thirdparty/" + rsrc.Name + "/" + rsrc.Versions[0].Name
|
|
||||||
|
|
||||||
storage := map[string]rest.Storage{
|
storage := map[string]rest.Storage{
|
||||||
apiGroup: resourceStorage,
|
kind: resourceStorage,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &apiserver.APIGroupVersion{
|
return &apiserver.APIGroupVersion{
|
||||||
@ -800,11 +803,11 @@ func (m *Master) thirdpartyapi(rsrc *expapi.ThirdPartyResource) *apiserver.APIGr
|
|||||||
Convertor: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
Typer: api.Scheme,
|
Typer: api.Scheme,
|
||||||
|
|
||||||
Mapper: explatest.RESTMapper,
|
Mapper: thirdpartyresourcedata.NewMapper(explatest.RESTMapper, kind),
|
||||||
Codec: explatest.Codec,
|
Codec: explatest.Codec,
|
||||||
Linker: explatest.SelfLinker,
|
Linker: explatest.SelfLinker,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Version: rsrc.Versions[0].Name,
|
Version: version,
|
||||||
|
|
||||||
Admit: m.admissionControl,
|
Admit: m.admissionControl,
|
||||||
Context: m.requestContextMapper,
|
Context: m.requestContextMapper,
|
||||||
|
@ -17,9 +17,12 @@ limitations under the License.
|
|||||||
package master
|
package master
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
@ -80,30 +83,59 @@ func TestFindExternalAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstallThirdPartyAPI(t *testing.T) {
|
type Foo struct {
|
||||||
|
api.TypeMeta `json:",inline"`
|
||||||
|
api.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"`
|
||||||
|
|
||||||
|
SomeField string `json:"someField"`
|
||||||
|
OtherField int `json:"otherField"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FooList struct {
|
||||||
|
api.TypeMeta `json:",inline"`
|
||||||
|
api.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see http://docs.k8s.io/api-conventions.md#metadata"`
|
||||||
|
|
||||||
|
items []Foo `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func initThirdParty(t *testing.T) (*tools.FakeEtcdClient, *httptest.Server) {
|
||||||
master := &Master{}
|
master := &Master{}
|
||||||
api := &expapi.ThirdPartyResource{
|
api := &expapi.ThirdPartyResource{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
Name: "foo",
|
Name: "foo.company.com",
|
||||||
},
|
},
|
||||||
Versions: []expapi.APIVersion{
|
Versions: []expapi.APIVersion{
|
||||||
expapi.APIVersion{
|
{
|
||||||
APIGroup: "group",
|
APIGroup: "group",
|
||||||
Name: "v1",
|
Name: "v1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
master.handlerContainer = restful.NewContainer()
|
master.handlerContainer = restful.NewContainer()
|
||||||
|
|
||||||
|
fakeClient := tools.NewFakeEtcdClient(t)
|
||||||
|
fakeClient.Machines = []string{"http://machine1:4001", "http://machine2", "http://machine3:4003"}
|
||||||
|
master.thirdPartyStorage = etcdstorage.NewEtcdStorage(fakeClient, explatest.Codec, etcdtest.PathPrefix())
|
||||||
|
|
||||||
if err := master.InstallThirdPartyAPI(api); err != nil {
|
if err := master.InstallThirdPartyAPI(api); err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
server := httptest.NewServer(master.handlerContainer.ServeMux)
|
server := httptest.NewServer(master.handlerContainer.ServeMux)
|
||||||
|
return fakeClient, server
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstallThirdPartyAPIList(t *testing.T) {
|
||||||
|
fakeClient, server := initThirdParty(t)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
resp, err := http.Get(server.URL + "/thirdparty/foo/v1")
|
fakeClient.ExpectNotFoundGet(etcdtest.PathPrefix() + "/ThirdPartyResourceData/company.com/foos/default")
|
||||||
|
|
||||||
|
resp, err := http.Get(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@ -116,7 +148,216 @@ func TestInstallThirdPartyAPI(t *testing.T) {
|
|||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(data) != "foo" {
|
list := FooList{}
|
||||||
t.Errorf("unexpected response: %s", string(data))
|
if err := json.Unmarshal(data, &list); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encodeToThirdParty(name string, obj interface{}) ([]byte, error) {
|
||||||
|
serial, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
thirdPartyData := expapi.ThirdPartyResourceData{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: name},
|
||||||
|
Data: serial,
|
||||||
|
}
|
||||||
|
return latest.Codec.Encode(&thirdPartyData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeToEtcd(fakeClient *tools.FakeEtcdClient, path, name string, obj interface{}) error {
|
||||||
|
data, err := encodeToThirdParty(name, obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = fakeClient.Set(etcdtest.PathPrefix()+path, string(data), 0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeResponse(resp *http.Response, obj interface{}) error {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstallThirdPartyAPIGet(t *testing.T) {
|
||||||
|
fakeClient, server := initThirdParty(t)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
expectedObj := Foo{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
TypeMeta: api.TypeMeta{
|
||||||
|
Kind: "foo",
|
||||||
|
},
|
||||||
|
SomeField: "test field",
|
||||||
|
OtherField: 10,
|
||||||
|
}
|
||||||
|
if err := storeToEtcd(fakeClient, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
t.FailNow()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("unexpected status: %v", resp)
|
||||||
|
}
|
||||||
|
item := Foo{}
|
||||||
|
if err := decodeResponse(resp, &item); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(item, expectedObj) {
|
||||||
|
t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstallThirdPartyAPIPost(t *testing.T) {
|
||||||
|
fakeClient, server := initThirdParty(t)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
inputObj := Foo{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
TypeMeta: api.TypeMeta{
|
||||||
|
Kind: "foo",
|
||||||
|
},
|
||||||
|
SomeField: "test field",
|
||||||
|
OtherField: 10,
|
||||||
|
}
|
||||||
|
data, err := encodeToThirdParty("test", inputObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Post(server.URL+"/thirdparty/company.com/v1/namespaces/default/foos", "application/json", bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusCreated {
|
||||||
|
t.Errorf("unexpected status: %v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
item := Foo{}
|
||||||
|
if err := decodeResponse(resp, &item); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(item, inputObj) {
|
||||||
|
t.Errorf("expected:\n%v\nsaw:\n%v\n", inputObj, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
etcdResp, err := fakeClient.Get(etcdtest.PathPrefix()+"/ThirdPartyResourceData/company.com/foos/default/test", false, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := explatest.Codec.Decode([]byte(etcdResp.Node.Value))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
thirdPartyObj, ok := obj.(*expapi.ThirdPartyResourceData)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("unexpected object: %v", obj)
|
||||||
|
}
|
||||||
|
item = Foo{}
|
||||||
|
if err := json.Unmarshal(thirdPartyObj.Data, &item); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(item, inputObj) {
|
||||||
|
t.Errorf("expected:\n%v\nsaw:\n%v\n", inputObj, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstallThirdPartyAPIDelete(t *testing.T) {
|
||||||
|
fakeClient, server := initThirdParty(t)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
expectedObj := Foo{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
TypeMeta: api.TypeMeta{
|
||||||
|
Kind: "foo",
|
||||||
|
},
|
||||||
|
SomeField: "test field",
|
||||||
|
OtherField: 10,
|
||||||
|
}
|
||||||
|
if err := storeToEtcd(fakeClient, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
t.FailNow()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("unexpected status: %v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
item := Foo{}
|
||||||
|
if err := decodeResponse(resp, &item); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(item, expectedObj) {
|
||||||
|
t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = httpDelete(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("unexpected status: %v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = http.Get(server.URL + "/thirdparty/company.com/v1/namespaces/default/foos/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusNotFound {
|
||||||
|
t.Errorf("unexpected status: %v", resp)
|
||||||
|
}
|
||||||
|
expectDeletedKeys := []string{etcdtest.PathPrefix() + "/ThirdPartyResourceData/company.com/foos/default/test"}
|
||||||
|
if !reflect.DeepEqual(fakeClient.DeletedKeys, expectDeletedKeys) {
|
||||||
|
t.Errorf("unexpected deleted keys: %v", fakeClient.DeletedKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpDelete(url string) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("DELETE", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client := &http.Client{}
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
@ -101,6 +101,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
func TestDelete(t *testing.T) {
|
func TestDelete(t *testing.T) {
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
storage, fakeClient := newStorage(t)
|
storage, fakeClient := newStorage(t)
|
||||||
|
175
pkg/registry/thirdpartyresourcedata/codec.go
Normal file
175
pkg/registry/thirdpartyresourcedata/codec.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
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 thirdpartyresourcedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
|
"k8s.io/kubernetes/pkg/expapi"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type thirdPartyResourceDataMapper struct {
|
||||||
|
mapper meta.RESTMapper
|
||||||
|
kind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataMapper) GroupForResource(resource string) (string, error) {
|
||||||
|
return t.mapper.GroupForResource(resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataMapper) RESTMapping(kind string, versions ...string) (*meta.RESTMapping, error) {
|
||||||
|
mapping, err := t.mapper.RESTMapping(kind, versions...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mapping.Codec = NewCodec(mapping.Codec, t.kind)
|
||||||
|
return mapping, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataMapper) AliasesForResource(resource string) ([]string, bool) {
|
||||||
|
return t.mapper.AliasesForResource(resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataMapper) ResourceSingularizer(resource string) (singular string, err error) {
|
||||||
|
return t.mapper.ResourceSingularizer(resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
|
||||||
|
return t.mapper.VersionAndKindForResource(resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMapper(mapper meta.RESTMapper, kind string) meta.RESTMapper {
|
||||||
|
return &thirdPartyResourceDataMapper{mapper, kind}
|
||||||
|
}
|
||||||
|
|
||||||
|
type thirdPartyResourceDataCodec struct {
|
||||||
|
delegate runtime.Codec
|
||||||
|
kind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCodec(codec runtime.Codec, kind string) runtime.Codec {
|
||||||
|
return &thirdPartyResourceDataCodec{codec, kind}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataCodec) populate(objIn *expapi.ThirdPartyResourceData, data []byte) error {
|
||||||
|
var obj interface{}
|
||||||
|
if err := json.Unmarshal(data, &obj); err != nil {
|
||||||
|
fmt.Printf("Invalid JSON:\n%s\n", string(data))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mapObj, ok := obj.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected object: %#v", obj)
|
||||||
|
}
|
||||||
|
kind, ok := mapObj["kind"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected object: %#v", obj)
|
||||||
|
}
|
||||||
|
if kind == "ThirdPartyResourceData" {
|
||||||
|
return t.delegate.DecodeInto(data, objIn)
|
||||||
|
}
|
||||||
|
if kind != t.kind {
|
||||||
|
return fmt.Errorf("unexpected kind: %s, expected: %s", kind, t.kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, ok := mapObj["metadata"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected object: %#v", obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, ok := metadata["name"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected object: %#v", obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
objIn.Name = name
|
||||||
|
objIn.Data = data
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataCodec) Decode(data []byte) (runtime.Object, error) {
|
||||||
|
result := &expapi.ThirdPartyResourceData{}
|
||||||
|
if err := t.populate(result, data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataCodec) DecodeToVersion(data []byte, version string) (runtime.Object, error) {
|
||||||
|
// TODO: this is hacky, there must be a better way...
|
||||||
|
obj, err := t.Decode(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objData, err := t.delegate.Encode(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return t.delegate.DecodeToVersion(objData, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataCodec) DecodeInto(data []byte, obj runtime.Object) error {
|
||||||
|
thirdParty, ok := obj.(*expapi.ThirdPartyResourceData)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected object: %#v", obj)
|
||||||
|
}
|
||||||
|
return t.populate(thirdParty, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataCodec) DecodeIntoWithSpecifiedVersionKind(data []byte, obj runtime.Object, kind, version string) error {
|
||||||
|
thirdParty, ok := obj.(*expapi.ThirdPartyResourceData)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected object: %#v", obj)
|
||||||
|
}
|
||||||
|
if err := t.populate(thirdParty, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
thirdParty.Kind = kind
|
||||||
|
thirdParty.APIVersion = version
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = `{
|
||||||
|
"kind": "%s",
|
||||||
|
"items": [ %s ]
|
||||||
|
}`
|
||||||
|
|
||||||
|
func (t *thirdPartyResourceDataCodec) Encode(obj runtime.Object) (data []byte, err error) {
|
||||||
|
switch obj := obj.(type) {
|
||||||
|
case *expapi.ThirdPartyResourceData:
|
||||||
|
return obj.Data, nil
|
||||||
|
case *expapi.ThirdPartyResourceDataList:
|
||||||
|
// TODO: There must be a better way to do this...
|
||||||
|
buff := &bytes.Buffer{}
|
||||||
|
dataStrings := make([]string, len(obj.Items))
|
||||||
|
for ix := range obj.Items {
|
||||||
|
dataStrings[ix] = string(obj.Items[ix].Data)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buff, template, t.kind+"List", strings.Join(dataStrings, ","))
|
||||||
|
return buff.Bytes(), nil
|
||||||
|
case *api.Status:
|
||||||
|
return t.delegate.Encode(obj)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected object to encode: %#v", obj)
|
||||||
|
}
|
||||||
|
}
|
@ -34,8 +34,8 @@ type REST struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewREST returns a registry which will store ThirdPartyResourceData in the given helper
|
// NewREST returns a registry which will store ThirdPartyResourceData in the given helper
|
||||||
func NewREST(s storage.Interface) *REST {
|
func NewREST(s storage.Interface, group, kind string) *REST {
|
||||||
prefix := "/ThirdPartyResourceData"
|
prefix := "/ThirdPartyResourceData/" + group + "/" + kind
|
||||||
|
|
||||||
store := &etcdgeneric.Etcd{
|
store := &etcdgeneric.Etcd{
|
||||||
NewFunc: func() runtime.Object { return &expapi.ThirdPartyResourceData{} },
|
NewFunc: func() runtime.Object { return &expapi.ThirdPartyResourceData{} },
|
||||||
|
@ -40,41 +40,37 @@ var codec runtime.Codec
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Ensure that expapi/v1 packege is used, so that it will get initialized and register HorizontalPodAutoscaler object.
|
// Ensure that expapi/v1 packege is used, so that it will get initialized and register HorizontalPodAutoscaler object.
|
||||||
_ = v1.ThirdPartyResource{}
|
_ = v1.ThirdPartyResourceData{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, storage.Interface) {
|
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, storage.Interface) {
|
||||||
fakeEtcdClient := tools.NewFakeEtcdClient(t)
|
fakeEtcdClient := tools.NewFakeEtcdClient(t)
|
||||||
fakeEtcdClient.TestIndex = true
|
fakeEtcdClient.TestIndex = true
|
||||||
etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix())
|
etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix())
|
||||||
storage := NewREST(etcdStorage)
|
storage := NewREST(etcdStorage, "foo", "bar")
|
||||||
return storage, fakeEtcdClient, etcdStorage
|
return storage, fakeEtcdClient, etcdStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func validNewThirdPartyResource(name string) *expapi.ThirdPartyResource {
|
func validNewThirdPartyResourceData(name string) *expapi.ThirdPartyResourceData {
|
||||||
return &expapi.ThirdPartyResource{
|
return &expapi.ThirdPartyResourceData{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: api.NamespaceDefault,
|
Namespace: api.NamespaceDefault,
|
||||||
},
|
},
|
||||||
Versions: []expapi.APIVersion{
|
Data: []byte("foobarbaz"),
|
||||||
{
|
|
||||||
Name: "stable/v1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
storage, fakeEtcdClient, _ := newStorage(t)
|
storage, fakeEtcdClient, _ := newStorage(t)
|
||||||
test := resttest.New(t, storage, fakeEtcdClient.SetError)
|
test := resttest.New(t, storage, fakeEtcdClient.SetError)
|
||||||
rsrc := validNewThirdPartyResource("foo")
|
rsrc := validNewThirdPartyResourceData("foo")
|
||||||
rsrc.ObjectMeta = api.ObjectMeta{}
|
rsrc.ObjectMeta = api.ObjectMeta{}
|
||||||
test.TestCreate(
|
test.TestCreate(
|
||||||
// valid
|
// valid
|
||||||
rsrc,
|
rsrc,
|
||||||
// invalid
|
// invalid
|
||||||
&expapi.ThirdPartyResource{},
|
&expapi.ThirdPartyResourceData{},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,14 +84,14 @@ func TestUpdate(t *testing.T) {
|
|||||||
key = etcdtest.AddPrefix(key)
|
key = etcdtest.AddPrefix(key)
|
||||||
fakeEtcdClient.ExpectNotFoundGet(key)
|
fakeEtcdClient.ExpectNotFoundGet(key)
|
||||||
fakeEtcdClient.ChangeIndex = 2
|
fakeEtcdClient.ChangeIndex = 2
|
||||||
rsrc := validNewThirdPartyResource("foo")
|
rsrc := validNewThirdPartyResourceData("foo")
|
||||||
existing := validNewThirdPartyResource("exists")
|
existing := validNewThirdPartyResourceData("exists")
|
||||||
existing.Namespace = test.TestNamespace()
|
existing.Namespace = test.TestNamespace()
|
||||||
obj, err := storage.Create(test.TestContext(), existing)
|
obj, err := storage.Create(test.TestContext(), existing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create object: %v", err)
|
t.Fatalf("unable to create object: %v", err)
|
||||||
}
|
}
|
||||||
older := obj.(*expapi.ThirdPartyResource)
|
older := obj.(*expapi.ThirdPartyResourceData)
|
||||||
older.ResourceVersion = "1"
|
older.ResourceVersion = "1"
|
||||||
test.TestUpdate(
|
test.TestUpdate(
|
||||||
rsrc,
|
rsrc,
|
||||||
@ -104,37 +100,10 @@ func TestUpdate(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func TestGet(t *testing.T) {
|
||||||
storage, fakeEtcdClient, _ := newStorage(t)
|
storage, fakeEtcdClient, _ := newStorage(t)
|
||||||
test := resttest.New(t, storage, fakeEtcdClient.SetError)
|
test := resttest.New(t, storage, fakeEtcdClient.SetError)
|
||||||
rsrc := validNewThirdPartyResource("foo")
|
rsrc := validNewThirdPartyResourceData("foo")
|
||||||
test.TestGet(rsrc)
|
test.TestGet(rsrc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,10 +121,10 @@ func TestEmptyList(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if len(rsrcList.(*expapi.ThirdPartyResourceList).Items) != 0 {
|
if len(rsrcList.(*expapi.ThirdPartyResourceDataList).Items) != 0 {
|
||||||
t.Errorf("Unexpected non-zero autoscaler list: %#v", rsrcList)
|
t.Errorf("Unexpected non-zero autoscaler list: %#v", rsrcList)
|
||||||
}
|
}
|
||||||
if rsrcList.(*expapi.ThirdPartyResourceList).ResourceVersion != "1" {
|
if rsrcList.(*expapi.ThirdPartyResourceDataList).ResourceVersion != "1" {
|
||||||
t.Errorf("Unexpected resource version: %#v", rsrcList)
|
t.Errorf("Unexpected resource version: %#v", rsrcList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,12 +140,12 @@ func TestList(t *testing.T) {
|
|||||||
Node: &etcd.Node{
|
Node: &etcd.Node{
|
||||||
Nodes: []*etcd.Node{
|
Nodes: []*etcd.Node{
|
||||||
{
|
{
|
||||||
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResource{
|
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResourceData{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResource{
|
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResourceData{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -188,18 +157,18 @@ func TestList(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
rsrcList := obj.(*expapi.ThirdPartyResourceList)
|
rsrcList := obj.(*expapi.ThirdPartyResourceDataList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rsrcList.Items) != 2 {
|
if len(rsrcList.Items) != 2 {
|
||||||
t.Errorf("Unexpected ThirdPartyResource list: %#v", rsrcList)
|
t.Errorf("Unexpected ThirdPartyResourceData list: %#v", rsrcList)
|
||||||
}
|
}
|
||||||
if rsrcList.Items[0].Name != "foo" {
|
if rsrcList.Items[0].Name != "foo" {
|
||||||
t.Errorf("Unexpected ThirdPartyResource: %#v", rsrcList.Items[0])
|
t.Errorf("Unexpected ThirdPartyResourceData: %#v", rsrcList.Items[0])
|
||||||
}
|
}
|
||||||
if rsrcList.Items[1].Name != "bar" {
|
if rsrcList.Items[1].Name != "bar" {
|
||||||
t.Errorf("Unexpected ThirdPartyResource: %#v", rsrcList.Items[1])
|
t.Errorf("Unexpected ThirdPartyResourceData: %#v", rsrcList.Items[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ func (strategy) PrepareForCreate(obj runtime.Object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList {
|
func (strategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList {
|
||||||
return validation.ValidateThirdPartyResource(obj.(*expapi.ThirdPartyResource))
|
return validation.ValidateThirdPartyResourceData(obj.(*expapi.ThirdPartyResourceData))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) AllowCreateOnUpdate() bool {
|
func (strategy) AllowCreateOnUpdate() bool {
|
||||||
@ -63,7 +63,7 @@ func (strategy) PrepareForUpdate(obj, old runtime.Object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
|
func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
|
||||||
return validation.ValidateThirdPartyResourceUpdate(old.(*expapi.ThirdPartyResource), obj.(*expapi.ThirdPartyResource))
|
return validation.ValidateThirdPartyResourceDataUpdate(old.(*expapi.ThirdPartyResourceData), obj.(*expapi.ThirdPartyResourceData))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) AllowUnconditionalUpdate() bool {
|
func (strategy) AllowUnconditionalUpdate() bool {
|
||||||
@ -73,9 +73,9 @@ func (strategy) AllowUnconditionalUpdate() bool {
|
|||||||
// Matcher returns a generic matcher for a given label and field selector.
|
// Matcher returns a generic matcher for a given label and field selector.
|
||||||
func Matcher(label labels.Selector, field fields.Selector) generic.Matcher {
|
func Matcher(label labels.Selector, field fields.Selector) generic.Matcher {
|
||||||
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {
|
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {
|
||||||
sa, ok := obj.(*expapi.ThirdPartyResource)
|
sa, ok := obj.(*expapi.ThirdPartyResourceData)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("not a ThirdPartyResource")
|
return false, fmt.Errorf("not a ThirdPartyResourceData")
|
||||||
}
|
}
|
||||||
fields := SelectableFields(sa)
|
fields := SelectableFields(sa)
|
||||||
return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil
|
return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil
|
||||||
@ -83,6 +83,6 @@ func Matcher(label labels.Selector, field fields.Selector) generic.Matcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SelectableFields returns a label set that can be used for filter selection
|
// SelectableFields returns a label set that can be used for filter selection
|
||||||
func SelectableFields(obj *expapi.ThirdPartyResource) labels.Set {
|
func SelectableFields(obj *expapi.ThirdPartyResourceData) labels.Set {
|
||||||
return labels.Set{}
|
return labels.Set{}
|
||||||
}
|
}
|
||||||
|
49
pkg/registry/thirdpartyresourcedata/util.go
Normal file
49
pkg/registry/thirdpartyresourcedata/util.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
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 thirdpartyresourcedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/expapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func convertToCamelCase(input string) string {
|
||||||
|
result := ""
|
||||||
|
toUpper := true
|
||||||
|
for ix := range input {
|
||||||
|
char := input[ix]
|
||||||
|
if toUpper {
|
||||||
|
result = result + string([]byte{(char - 32)})
|
||||||
|
toUpper = false
|
||||||
|
} else if char == '-' {
|
||||||
|
toUpper = true
|
||||||
|
} else {
|
||||||
|
result = result + string([]byte{char})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractApiGroupAndKind(rsrc *expapi.ThirdPartyResource) (kind string, group string, err error) {
|
||||||
|
parts := strings.Split(rsrc.Name, ".")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return "", "", fmt.Errorf("unexpectedly short resource name: %s, expected at least <kind>.<domain>.<tld>", rsrc.Name)
|
||||||
|
}
|
||||||
|
return convertToCamelCase(parts[0]), strings.Join(parts[1:], "."), nil
|
||||||
|
}
|
66
pkg/registry/thirdpartyresourcedata/util_test.go
Normal file
66
pkg/registry/thirdpartyresourcedata/util_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
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 thirdpartyresourcedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/expapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractAPIGroupAndKind(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expectedKind string
|
||||||
|
expectedGroup string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "foo.company.com",
|
||||||
|
expectedKind: "Foo",
|
||||||
|
expectedGroup: "company.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "cron-tab.company.com",
|
||||||
|
expectedKind: "CronTab",
|
||||||
|
expectedGroup: "company.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "foo",
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
kind, group, err := ExtractApiGroupAndKind(&expapi.ThirdPartyResource{ObjectMeta: api.ObjectMeta{Name: test.input}})
|
||||||
|
if err != nil && !test.expectErr {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil && test.expectErr {
|
||||||
|
t.Errorf("unexpected non-error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if kind != test.expectedKind {
|
||||||
|
t.Errorf("expected: %s, saw: %s", test.expectedKind, kind)
|
||||||
|
}
|
||||||
|
if group != test.expectedGroup {
|
||||||
|
t.Errorf("expected: %s, saw: %s", test.expectedGroup, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user