Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thirdpartyresources examples #29

Merged
merged 3 commits into from
Nov 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions examples/third-party-resources/README.md
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Member

@caesarxuchao caesarxuchao Nov 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a hyperlink for ThirdPartyResources?

These act like most other Resources in Kubernetes, and may be `kubectl apply`'d, etc.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean you can kubectl apply ThirdpartyResource, or instances of ThirdpartyResource? If it's the former case, I feel it doesn't need to be emphasized.


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"`
}
```
155 changes: 155 additions & 0 deletions examples/third-party-resources/main.go
Original file line number Diff line number Diff line change
@@ -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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to use the api.Codecs and api.Scheme, or can we create our own instances of them (e.g., code)? I'm not sure if adding a groupversion to the exisiting scheme will have any side effects.


schemeBuilder := runtime.NewSchemeBuilder(
func(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(
groupversion,
&Example{},
&ExampleList{},
&api.ListOptions{},
&api.DeleteOptions{},
)
return nil
})
schemeBuilder.AddToScheme(api.Scheme)
}
77 changes: 77 additions & 0 deletions examples/third-party-resources/types.go
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you link to the issue: ugorji/go#178

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And could you say if the issue is resolved, you can just define the UnmarshalText(data []byte) error for Example and ExampleList, and don't need to define ExampleListCopy and ExampleCopy.


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
}