-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||
These act like most other Resources in Kubernetes, and may be `kubectl apply`'d, etc. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean you can |
||
|
||
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"` | ||
} | ||
``` |
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} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you link to the issue: ugorji/go#178 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
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 | ||
} |
There was a problem hiding this comment.
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?