From 6fb6707824978360a595915b9a556fdae9127e33 Mon Sep 17 00:00:00 2001 From: Will Farrington Date: Fri, 4 Nov 2016 10:41:20 -0400 Subject: [PATCH 1/3] Add an example for standard CRUD operations with ThirdPartyResources --- examples/third-party-resources/main.go | 155 ++++++++++++++++++++++++ examples/third-party-resources/types.go | 77 ++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 examples/third-party-resources/main.go create mode 100644 examples/third-party-resources/types.go diff --git a/examples/third-party-resources/main.go b/examples/third-party-resources/main.go new file mode 100644 index 0000000000..9421e2f153 --- /dev/null +++ b/examples/third-party-resources/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "flag" + "fmt" + + "k8s.io/client-go/1.5/kubernetes" + "k8s.io/client-go/1.5/pkg/api" + "k8s.io/client-go/1.5/pkg/api/errors" + "k8s.io/client-go/1.5/pkg/api/unversioned" + "k8s.io/client-go/1.5/pkg/api/v1" + "k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1" + "k8s.io/client-go/1.5/pkg/runtime" + "k8s.io/client-go/1.5/pkg/runtime/serializer" + "k8s.io/client-go/1.5/rest" + "k8s.io/client-go/1.5/tools/clientcmd" + + // Only required to authenticate against GKE clusters + _ "k8s.io/client-go/1.5/plugin/pkg/client/auth/gcp" +) + +var ( + config *rest.Config +) + +func main() { + kubeconfig := flag.String("kubeconfig", "", "Path to a kube config. Only required if out-of-cluster.") + flag.Parse() + + // Create the client config. Use kubeconfig if given, otherwise assume in-cluster. + config, err := buildConfig(*kubeconfig) + if err != nil { + panic(err) + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + panic(err) + } + + // initialize third party resource if it does not exist + tpr, err := clientset.Extensions().ThirdPartyResources().Get("example.k8s.io") + if err != nil { + if errors.IsNotFound(err) { + tpr := &v1beta1.ThirdPartyResource{ + ObjectMeta: v1.ObjectMeta{ + Name: "example.k8s.io", + }, + Versions: []v1beta1.APIVersion{ + {Name: "v1"}, + }, + Description: "An Example ThirdPartyResource", + } + + result, err := clientset.Extensions().ThirdPartyResources().Create(tpr) + if err != nil { + panic(err) + } + fmt.Printf("CREATED: %#v\nFROM: %#v\n", result, tpr) + } else { + panic(err) + } + } else { + fmt.Printf("SKIPPING: already exists %#v\n", tpr) + } + + // make a new config for our extension's API group, using the first config as a baseline + var tprconfig *rest.Config + tprconfig = config + configureClient(tprconfig) + + tprclient, err := rest.RESTClientFor(tprconfig) + if err != nil { + panic(err) + } + + var example Example + + err = tprclient.Get(). + Resource("examples"). + Namespace(api.NamespaceDefault). + Name("example1"). + Do().Into(&example) + + if err != nil { + if errors.IsNotFound(err) { + // Create an instance of our TPR + example := &Example{ + Metadata: api.ObjectMeta{ + Name: "example1", + }, + Spec: ExampleSpec{ + Foo: "hello", + Bar: true, + }, + } + + var result Example + err = tprclient.Post(). + Resource("examples"). + Namespace(api.NamespaceDefault). + Body(example). + Do().Into(&result) + + if err != nil { + panic(err) + } + fmt.Printf("CREATED: %#v\n", result) + } else { + panic(err) + } + } else { + fmt.Printf("GET: %#v\n", example) + } + + // Fetch a list of our TPRs + exampleList := ExampleList{} + err = tprclient.Get().Resource("examples").Do().Into(&exampleList) + if err != nil { + panic(err) + } + fmt.Printf("LIST: %#v\n", exampleList) +} + +func buildConfig(kubeconfig string) (*rest.Config, error) { + if kubeconfig != "" { + return clientcmd.BuildConfigFromFlags("", kubeconfig) + } + return rest.InClusterConfig() +} + +func configureClient(config *rest.Config) { + groupversion := unversioned.GroupVersion{ + Group: "k8s.io", + Version: "v1", + } + + config.GroupVersion = &groupversion + config.APIPath = "/apis" + config.ContentType = runtime.ContentTypeJSON + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: api.Codecs} + + schemeBuilder := runtime.NewSchemeBuilder( + func(scheme *runtime.Scheme) error { + scheme.AddKnownTypes( + groupversion, + &Example{}, + &ExampleList{}, + &api.ListOptions{}, + &api.DeleteOptions{}, + ) + return nil + }) + schemeBuilder.AddToScheme(api.Scheme) +} diff --git a/examples/third-party-resources/types.go b/examples/third-party-resources/types.go new file mode 100644 index 0000000000..c6dd2e1d01 --- /dev/null +++ b/examples/third-party-resources/types.go @@ -0,0 +1,77 @@ +package main + +import ( + "encoding/json" + + "k8s.io/client-go/1.5/pkg/api" + "k8s.io/client-go/1.5/pkg/api/meta" + "k8s.io/client-go/1.5/pkg/api/unversioned" +) + +type ExampleSpec struct { + Foo string `json:"foo"` + Bar bool `json:"bar"` +} + +type Example struct { + unversioned.TypeMeta `json:",inline"` + Metadata api.ObjectMeta `json:"metadata"` + + Spec ExampleSpec `json:"spec"` +} + +type ExampleList struct { + unversioned.TypeMeta `json:",inline"` + Metadata unversioned.ListMeta `json:"metadata"` + + Items []Example `json:"items"` +} + +// Required to satisfy Object interface +func (e *Example) GetObjectKind() unversioned.ObjectKind { + return &e.TypeMeta +} + +// Required to satisfy ObjectMetaAccessor interface +func (e *Example) GetObjectMeta() meta.Object { + return &e.Metadata +} + +// Required to satisfy Object interface +func (el *ExampleList) GetObjectKind() unversioned.ObjectKind { + return &el.TypeMeta +} + +// Required to satisfy ListMetaAccessor interface +func (el *ExampleList) GetListMeta() unversioned.List { + return &el.Metadata +} + +// The code below is used only to work around a known problem with third-party +// resources and ugorji. If/when these issues are resolved, the code below +// should no longer be required. + +type ExampleListCopy ExampleList +type ExampleCopy Example + +func (e *Example) UnmarshalJSON(data []byte) error { + tmp := ExampleCopy{} + err := json.Unmarshal(data, &tmp) + if err != nil { + return err + } + tmp2 := Example(tmp) + *e = tmp2 + return nil +} + +func (el *ExampleList) UnmarshalJSON(data []byte) error { + tmp := ExampleListCopy{} + err := json.Unmarshal(data, &tmp) + if err != nil { + return err + } + tmp2 := ExampleList(tmp) + *el = tmp2 + return nil +} From d6263b114f5b03bc009e049cac1faec2f2e58601 Mon Sep 17 00:00:00 2001 From: Will Farrington Date: Fri, 4 Nov 2016 15:21:23 -0400 Subject: [PATCH 2/3] Add README --- examples/third-party-resources/README.md | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 examples/third-party-resources/README.md diff --git a/examples/third-party-resources/README.md b/examples/third-party-resources/README.md new file mode 100644 index 0000000000..68c25a0c91 --- /dev/null +++ b/examples/third-party-resources/README.md @@ -0,0 +1,43 @@ +# Third Party Resources Example + +This particular example demonstrates how to perform basic operations such as: + +* How to register a new ThirdPartyResource (custom Resource type) +* How to create/get/list instances of your new Resource type (update/delete/etc work as well but are not demonstrated) + +## Running + +``` +# assumes you have a working kubeconfig, not required if operating in-cluster +go run *.go -kubeconfig=$HOME/.kube/config +``` + +## Use Cases + +ThirdPartyResources can be used to implement custom Resource types for your Kubernetes cluster. +These act like most other Resources in Kubernetes, and may be `kubectl apply`'d, etc. + +Some example use cases: + +* Provisioning/Management of external datastores/databases (eg. CloudSQL/RDS instances) +* Higher level abstractions around Kubernetes primitives (eg. a single Resource to define an etcd cluster, backed by a Service and a ReplicationController) + +## Defining types + +Each instance of your ThirdPartyResource has an attached Spec, which should be defined via a `struct{}` to provide data format validation. +In practice, this Spec is arbitrary key-value data that specifies the configuration/behavior of your Resource. + +For example, if you were implementing a ThirdPartyResource for a Database, you might provide a DatabaseSpec like the following: + +``` go +type DatabaseSpec struct { + Databases []string `json:"databases"` + Users []User `json:"users"` + Version string `json:"version"` +} + +type User struct { + Name string `json:"name"` + Password string `json:"password"` +} +``` \ No newline at end of file From cf262eae7dee39cab1d8c49fba760d74678f9e97 Mon Sep 17 00:00:00 2001 From: Will Farrington Date: Fri, 4 Nov 2016 15:29:34 -0400 Subject: [PATCH 3/3] CLA bump