From 8d49905c48ed079aacf6ab84e6f11188938aa1cf Mon Sep 17 00:00:00 2001 From: Nail Islamov Date: Mon, 8 May 2017 11:17:02 +1000 Subject: [PATCH] ThirdPartyResource client-go example: added TPR controller example, code cleanup and integration test Kubernetes-commit: a6c97715ed7e24581dd9a02a256d57e56b70ac44 --- examples/third-party-resources/BUILD | 25 +-- examples/third-party-resources/README.md | 1 + .../third-party-resources/apis/tpr/v1/BUILD | 34 ++++ .../apis/tpr/v1/register.go | 56 ++++++ .../apis/tpr/v1/types.go | 53 ++++++ .../{ => apis/tpr/v1}/types_test.go | 2 +- examples/third-party-resources/client/BUILD | 29 ++++ .../third-party-resources/client/client.go | 45 +++++ examples/third-party-resources/client/tpr.go | 76 +++++++++ .../third-party-resources/controller/BUILD | 22 +++ .../controller/controller.go | 126 ++++++++++++++ examples/third-party-resources/main.go | 159 +++++++----------- examples/third-party-resources/types.go | 92 ---------- 13 files changed, 509 insertions(+), 211 deletions(-) create mode 100644 examples/third-party-resources/apis/tpr/v1/BUILD create mode 100644 examples/third-party-resources/apis/tpr/v1/register.go create mode 100644 examples/third-party-resources/apis/tpr/v1/types.go rename examples/third-party-resources/{ => apis/tpr/v1}/types_test.go (98%) create mode 100644 examples/third-party-resources/client/BUILD create mode 100644 examples/third-party-resources/client/client.go create mode 100644 examples/third-party-resources/client/tpr.go create mode 100644 examples/third-party-resources/controller/BUILD create mode 100644 examples/third-party-resources/controller/controller.go delete mode 100644 examples/third-party-resources/types.go diff --git a/examples/third-party-resources/BUILD b/examples/third-party-resources/BUILD index a3b7023e..bd800471 100644 --- a/examples/third-party-resources/BUILD +++ b/examples/third-party-resources/BUILD @@ -6,7 +6,6 @@ load( "@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", - "go_test", ) go_binary( @@ -15,34 +14,18 @@ go_binary( tags = ["automanaged"], ) -go_test( - name = "go_default_test", - srcs = ["types_test.go"], - library = ":go_default_library", - tags = ["automanaged"], - deps = [ - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) - go_library( name = "go_default_library", - srcs = [ - "main.go", - "types.go", - ], + srcs = ["main.go"], tags = ["automanaged"], deps = [ "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/client-go/examples/third-party-resources/apis/tpr/v1:go_default_library", + "//vendor/k8s.io/client-go/examples/third-party-resources/client:go_default_library", + "//vendor/k8s.io/client-go/examples/third-party-resources/controller:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", - "//vendor/k8s.io/client-go/pkg/apis/extensions/v1beta1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", ], diff --git a/examples/third-party-resources/README.md b/examples/third-party-resources/README.md index 68c25a0c..4a225b6b 100644 --- a/examples/third-party-resources/README.md +++ b/examples/third-party-resources/README.md @@ -4,6 +4,7 @@ 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) +* How to setup a controller on Resource handling create/update/delete events ## Running diff --git a/examples/third-party-resources/apis/tpr/v1/BUILD b/examples/third-party-resources/apis/tpr/v1/BUILD new file mode 100644 index 00000000..8db010a1 --- /dev/null +++ b/examples/third-party-resources/apis/tpr/v1/BUILD @@ -0,0 +1,34 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["types_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "register.go", + "types.go", + ], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) diff --git a/examples/third-party-resources/apis/tpr/v1/register.go b/examples/third-party-resources/apis/tpr/v1/register.go new file mode 100644 index 00000000..2dd196a4 --- /dev/null +++ b/examples/third-party-resources/apis/tpr/v1/register.go @@ -0,0 +1,56 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// GroupName is the group name use in this package +const GroupName = "tpr.client-go.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + SchemeBuilder.Register(addKnownTypes) +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Example{}, + &ExampleList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/examples/third-party-resources/apis/tpr/v1/types.go b/examples/third-party-resources/apis/tpr/v1/types.go new file mode 100644 index 00000000..32fea5a5 --- /dev/null +++ b/examples/third-party-resources/apis/tpr/v1/types.go @@ -0,0 +1,53 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ExampleResourcePlural = "examples" + +type Example struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec ExampleSpec `json:"spec"` + Status ExampleStatus `json:"status,omitempty"` +} + +type ExampleSpec struct { + Foo string `json:"foo"` + Bar bool `json:"bar"` +} + +type ExampleStatus struct { + State ExampleState `json:"state,omitempty"` + Message string `json:"message,omitempty"` +} + +type ExampleState string + +const ( + ExampleStateCreated ExampleState = "Created" + ExampleStateProcessed ExampleState = "Processed" +) + +type ExampleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []Example `json:"items"` +} diff --git a/examples/third-party-resources/types_test.go b/examples/third-party-resources/apis/tpr/v1/types_test.go similarity index 98% rename from examples/third-party-resources/types_test.go rename to examples/third-party-resources/apis/tpr/v1/types_test.go index f3f7bb1e..ebe0bd48 100644 --- a/examples/third-party-resources/types_test.go +++ b/examples/third-party-resources/apis/tpr/v1/types_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/examples/third-party-resources/client/BUILD b/examples/third-party-resources/client/BUILD new file mode 100644 index 00000000..016a1aac --- /dev/null +++ b/examples/third-party-resources/client/BUILD @@ -0,0 +1,29 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "client.go", + "tpr.go", + ], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/examples/third-party-resources/apis/tpr/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + "//vendor/k8s.io/client-go/pkg/apis/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + ], +) diff --git a/examples/third-party-resources/client/client.go b/examples/third-party-resources/client/client.go new file mode 100644 index 00000000..82128abd --- /dev/null +++ b/examples/third-party-resources/client/client.go @@ -0,0 +1,45 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 client + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/rest" + + tprv1 "k8s.io/client-go/examples/third-party-resources/apis/tpr/v1" +) + +func NewClient(cfg *rest.Config) (*rest.RESTClient, *runtime.Scheme, error) { + scheme := runtime.NewScheme() + if err := tprv1.AddToScheme(scheme); err != nil { + return nil, nil, err + } + + config := *cfg + config.GroupVersion = &tprv1.SchemeGroupVersion + config.APIPath = "/apis" + config.ContentType = runtime.ContentTypeJSON + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)} + + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, nil, err + } + + return client, scheme, nil +} diff --git a/examples/third-party-resources/client/tpr.go b/examples/third-party-resources/client/tpr.go new file mode 100644 index 00000000..6a1c0564 --- /dev/null +++ b/examples/third-party-resources/client/tpr.go @@ -0,0 +1,76 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 client + +import ( + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + tprv1 "k8s.io/client-go/examples/third-party-resources/apis/tpr/v1" + "k8s.io/client-go/kubernetes" + apiv1 "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/pkg/apis/extensions/v1beta1" + "k8s.io/client-go/rest" + // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). + // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" +) + +func CreateTPR(clientset kubernetes.Interface) error { + tpr := &v1beta1.ThirdPartyResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example." + tprv1.GroupName, + }, + Versions: []v1beta1.APIVersion{ + {Name: tprv1.SchemeGroupVersion.Version}, + }, + Description: "An Example ThirdPartyResource", + } + _, err := clientset.ExtensionsV1beta1().ThirdPartyResources().Create(tpr) + return err +} + +func WaitForExampleResource(exampleClient *rest.RESTClient) error { + return wait.Poll(100*time.Millisecond, 60*time.Second, func() (bool, error) { + _, err := exampleClient.Get().Namespace(apiv1.NamespaceDefault).Resource(tprv1.ExampleResourcePlural).DoRaw() + if err == nil { + return true, nil + } + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + }) +} + +func WaitForExampleInstanceProcessed(exampleClient *rest.RESTClient, name string) error { + return wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) { + var example tprv1.Example + err := exampleClient.Get(). + Resource(tprv1.ExampleResourcePlural). + Namespace(apiv1.NamespaceDefault). + Name(name). + Do().Into(&example) + + if err == nil && example.Status.State == tprv1.ExampleStateProcessed { + return true, nil + } + + return false, err + }) +} diff --git a/examples/third-party-resources/controller/BUILD b/examples/third-party-resources/controller/BUILD new file mode 100644 index 00000000..e34f7e2f --- /dev/null +++ b/examples/third-party-resources/controller/BUILD @@ -0,0 +1,22 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["controller.go"], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/client-go/examples/third-party-resources/apis/tpr/v1:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) diff --git a/examples/third-party-resources/controller/controller.go b/examples/third-party-resources/controller/controller.go new file mode 100644 index 00000000..19955d3f --- /dev/null +++ b/examples/third-party-resources/controller/controller.go @@ -0,0 +1,126 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 controller + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + apiv1 "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + + tprv1 "k8s.io/client-go/examples/third-party-resources/apis/tpr/v1" +) + +// Watcher is an example of watching on resource create/update/delete events +type ExampleController struct { + ExampleClient *rest.RESTClient + ExampleScheme *runtime.Scheme +} + +// Run starts an Example resource controller +func (c *ExampleController) Run(ctx context.Context) error { + fmt.Print("Watch Example objects\n") + + // Watch Example objects + _, err := c.watchExamples(ctx) + if err != nil { + fmt.Printf("Failed to register watch for Example resource: %v\n", err) + return err + } + + <-ctx.Done() + return ctx.Err() +} + +func (c *ExampleController) watchExamples(ctx context.Context) (cache.Controller, error) { + source := cache.NewListWatchFromClient( + c.ExampleClient, + tprv1.ExampleResourcePlural, + apiv1.NamespaceAll, + fields.Everything()) + + _, controller := cache.NewInformer( + source, + + // The object type. + &tprv1.Example{}, + + // resyncPeriod + // Every resyncPeriod, all resources in the cache will retrigger events. + // Set to 0 to disable the resync. + 0, + + // Your custom resource event handlers. + cache.ResourceEventHandlerFuncs{ + AddFunc: c.onAdd, + UpdateFunc: c.onUpdate, + DeleteFunc: c.onDelete, + }) + + go controller.Run(ctx.Done()) + return controller, nil +} + +func (c *ExampleController) onAdd(obj interface{}) { + example := obj.(*tprv1.Example) + fmt.Printf("[CONTROLLER] OnAdd %s\n", example.ObjectMeta.SelfLink) + + // NEVER modify objects from the store. It's a read-only, local cache. + // You can use exampleScheme.Copy() to make a deep copy of original object and modify this copy + // Or create a copy manually for better performance + copyObj, err := c.ExampleScheme.Copy(example) + if err != nil { + fmt.Printf("ERROR creating a deep copy of example object: %v\n", err) + return + } + + exampleCopy := copyObj.(*tprv1.Example) + exampleCopy.Status = tprv1.ExampleStatus{ + State: tprv1.ExampleStateProcessed, + Message: "Successfully processed by controller", + } + + err = c.ExampleClient.Put(). + Name(example.ObjectMeta.Name). + Namespace(example.ObjectMeta.Namespace). + Resource(tprv1.ExampleResourcePlural). + Body(exampleCopy). + Do(). + Error() + + if err != nil { + fmt.Printf("ERROR updating status: %v\n", err) + } else { + fmt.Printf("UPDATED status: %#v\n", exampleCopy) + } +} + +func (c *ExampleController) onUpdate(oldObj, newObj interface{}) { + oldExample := oldObj.(*tprv1.Example) + newExample := newObj.(*tprv1.Example) + fmt.Printf("[CONTROLLER] OnUpdate oldObj: %s\n", oldExample.ObjectMeta.SelfLink) + fmt.Printf("[CONTROLLER] OnUpdate newObj: %s\n", newExample.ObjectMeta.SelfLink) +} + +func (c *ExampleController) onDelete(obj interface{}) { + example := obj.(*tprv1.Example) + fmt.Printf("[CONTROLLER] OnDelete %s\n", example.ObjectMeta.SelfLink) +} diff --git a/examples/third-party-resources/main.go b/examples/third-party-resources/main.go index 3ae3c525..e7ad48b3 100644 --- a/examples/third-party-resources/main.go +++ b/examples/third-party-resources/main.go @@ -18,22 +18,23 @@ limitations under the License. package main import ( + "context" "flag" "fmt" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/pkg/api/v1" - "k8s.io/client-go/pkg/apis/extensions/v1beta1" + apiv1 "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + + tprv1 "k8s.io/client-go/examples/third-party-resources/apis/tpr/v1" + exampleclient "k8s.io/client-go/examples/third-party-resources/client" + examplecontroller "k8s.io/client-go/examples/third-party-resources/controller" ) func main() { @@ -52,83 +53,71 @@ func main() { } // initialize third party resource if it does not exist - tpr, err := clientset.ExtensionsV1beta1().ThirdPartyResources().Get("example.k8s.io", metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - tpr := &v1beta1.ThirdPartyResource{ - ObjectMeta: metav1.ObjectMeta{ - Name: "example.k8s.io", - }, - Versions: []v1beta1.APIVersion{ - {Name: "v1"}, - }, - Description: "An Example ThirdPartyResource", - } - - result, err := clientset.ExtensionsV1beta1().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) + err = exampleclient.CreateTPR(clientset) + if err != nil && !apierrors.IsAlreadyExists(err) { + panic(err) } // 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) + exampleClient, exampleScheme, err := exampleclient.NewClient(config) if err != nil { panic(err) } - var example Example - - err = tprclient.Get(). - Resource("examples"). - Namespace(v1.NamespaceDefault). - Name("example1"). - Do().Into(&example) - + // wait until TPR gets processed + err = exampleclient.WaitForExampleResource(exampleClient) if err != nil { - if errors.IsNotFound(err) { - // Create an instance of our TPR - example := &Example{ - Metadata: metav1.ObjectMeta{ - Name: "example1", - }, - Spec: ExampleSpec{ - Foo: "hello", - Bar: true, - }, - } - - var result Example - err = tprclient.Post(). - Resource("examples"). - Namespace(v1.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) + panic(err) } + // start a controller on instances of our TPR + controller := examplecontroller.ExampleController{ + ExampleClient: exampleClient, + ExampleScheme: exampleScheme, + } + + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + go controller.Run(ctx) + + // Create an instance of our TPR + example := &tprv1.Example{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example1", + }, + Spec: tprv1.ExampleSpec{ + Foo: "hello", + Bar: true, + }, + Status: tprv1.ExampleStatus{ + State: tprv1.ExampleStateCreated, + Message: "Created, not processed yet", + }, + } + var result tprv1.Example + err = exampleClient.Post(). + Resource(tprv1.ExampleResourcePlural). + Namespace(apiv1.NamespaceDefault). + Body(example). + Do().Into(&result) + if err == nil { + fmt.Printf("CREATED: %#v\n", result) + } else if apierrors.IsAlreadyExists(err) { + fmt.Printf("ALREADY EXISTS: %#v\n", result) + } else { + panic(err) + } + + // Poll until Example object is handled by controller and gets status updated to "Processed" + err = exampleclient.WaitForExampleInstanceProcessed(exampleClient, "example1") + if err != nil { + panic(err) + } + fmt.Print("PROCESSED\n") + // Fetch a list of our TPRs - exampleList := ExampleList{} - err = tprclient.Get().Resource("examples").Do().Into(&exampleList) + exampleList := tprv1.ExampleList{} + err = exampleClient.Get().Resource(tprv1.ExampleResourcePlural).Do().Into(&exampleList) if err != nil { panic(err) } @@ -141,27 +130,3 @@ func buildConfig(kubeconfig string) (*rest.Config, error) { } return rest.InClusterConfig() } - -func configureClient(config *rest.Config) { - groupversion := schema.GroupVersion{ - Group: "k8s.io", - Version: "v1", - } - - config.GroupVersion = &groupversion - config.APIPath = "/apis" - config.ContentType = runtime.ContentTypeJSON - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} - - schemeBuilder := runtime.NewSchemeBuilder( - func(scheme *runtime.Scheme) error { - scheme.AddKnownTypes( - groupversion, - &Example{}, - &ExampleList{}, - ) - return nil - }) - metav1.AddToGroupVersion(scheme.Scheme, groupversion) - schemeBuilder.AddToScheme(scheme.Scheme) -} diff --git a/examples/third-party-resources/types.go b/examples/third-party-resources/types.go deleted file mode 100644 index 345d5a3a..00000000 --- a/examples/third-party-resources/types.go +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -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 main - -import ( - "encoding/json" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type ExampleSpec struct { - Foo string `json:"foo"` - Bar bool `json:"bar"` -} - -type Example struct { - metav1.TypeMeta `json:",inline"` - Metadata metav1.ObjectMeta `json:"metadata"` - - Spec ExampleSpec `json:"spec"` -} - -type ExampleList struct { - metav1.TypeMeta `json:",inline"` - Metadata metav1.ListMeta `json:"metadata"` - - Items []Example `json:"items"` -} - -// Required to satisfy Object interface -func (e *Example) GetObjectKind() schema.ObjectKind { - return &e.TypeMeta -} - -// Required to satisfy ObjectMetaAccessor interface -func (e *Example) GetObjectMeta() metav1.Object { - return &e.Metadata -} - -// Required to satisfy Object interface -func (el *ExampleList) GetObjectKind() schema.ObjectKind { - return &el.TypeMeta -} - -// Required to satisfy ListMetaAccessor interface -func (el *ExampleList) GetListMeta() metav1.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 -}