Skip to content
This repository has been archived by the owner on Nov 19, 2020. It is now read-only.

Commit

Permalink
Merge pull request #32 from ultimateboy/all-namespaces
Browse files Browse the repository at this point in the history
list/watch resources across all namespaces
  • Loading branch information
ericchiang committed Feb 28, 2017
2 parents 7d4f858 + 44d7780 commit 9fe8b1e
Show file tree
Hide file tree
Showing 6 changed files with 452 additions and 706 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,10 @@ This client supports every API group version present since 1.3.

### Namespaces

Clients are initialized with a default namespace. For in-cluster clients, this is the namespace the pod was deployed in.

```go
pods, err := client.ListPods(ctx, "") // Pods in the current namespace.
pods, err := client.ListPods(ctx, k8s.AllNamespaces) // Pods in all namespaces.
```

This can be overridden by explicitly passing a namespace.

```go
pods, err := client.ListPods(ctx, "custom-namespace") // Pods from the "custom-namespace"
```
Expand Down
15 changes: 8 additions & 7 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ import (
"github.com/golang/protobuf/proto"
)

const (
// AllNamespaces is given to list and watch operations to signify that the code should
// list or watch resources in all namespaces.
AllNamespaces = allNamespaces
// Actual definition is private in case we want to change it later.
allNamespaces = ""
)

// String returns a pointer to a string. Useful for creating API objects
// that take pointers instead of literals.
//
Expand Down Expand Up @@ -369,13 +377,6 @@ func (c *Client) client() *http.Client {
return c.Client
}

func (c *Client) namespaceFor(namespace string) string {
if namespace != "" {
return namespace
}
return c.Namespace
}

// The following methods hold the logic for interacting with the Kubernetes API. Generated
// clients are thin wrappers on top of these methods.
//
Expand Down
132 changes: 131 additions & 1 deletion client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

const skipMsg = `
warning: this package's test run using the default context of your "kubeclt" command,
warning: this package's test run using the default context of your "kubectl" command,
and will create resources on your cluster (mostly configmaps).
If you wish to continue set the following environment variable:
Expand Down Expand Up @@ -247,3 +247,133 @@ func TestWatch(t *testing.T) {
}
}
}

// TestWatchNamespace ensures that creating a configmap in a non-default namespace is not returned while watching the default namespace
func TestWatchNamespace(t *testing.T) {
client := newTestClient(t).CoreV1()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

defaultWatch, err := client.WatchConfigMaps(ctx, "default")
if err != nil {
t.Fatal(err)
}
defer defaultWatch.Close()

allWatch, err := client.WatchConfigMaps(ctx, AllNamespaces)
if err != nil {
t.Fatal(err)
}
defer allWatch.Close()

nonDefaultNamespaceName := newName()
defaultName := newName()
name := newName()
labelVal := newName()

// Create a configmap in the default namespace so the "default" watch has something to return
defaultCM := &v1.ConfigMap{
Metadata: &v1.ObjectMeta{
Name: String(defaultName),
Namespace: String("default"),
Labels: map[string]string{
"testLabel": labelVal,
},
},
Data: map[string]string{
"foo": "bar",
},
}
defaultGot, err := client.CreateConfigMap(ctx, defaultCM)
if err != nil {
t.Fatalf("create config map: %v", err)
}

// Create a non-default Namespace
ns := &v1.Namespace{
Metadata: &v1.ObjectMeta{
Name: String(nonDefaultNamespaceName),
},
}
if _, err := client.CreateNamespace(ctx, ns); err != nil {
t.Fatalf("create non-default-namespace: %v", err)
}

// Create a configmap in the non-default namespace
nonDefaultCM := &v1.ConfigMap{
Metadata: &v1.ObjectMeta{
Name: String(name),
Namespace: String(nonDefaultNamespaceName),
Labels: map[string]string{
"testLabel": labelVal,
},
},
Data: map[string]string{
"foo": "bar",
},
}
nonDefaultGot, err := client.CreateConfigMap(ctx, nonDefaultCM)
if err != nil {
t.Fatalf("create config map: %v", err)
}

// Watching the default namespace should not return the non-default namespace configmap,
// and instead return the previously created configmap in the default namespace
if _, gotFromWatch, err := defaultWatch.Next(); err != nil {
t.Errorf("failed to get next watch: %v", err)
} else {
if reflect.DeepEqual(nonDefaultGot, gotFromWatch) {
t.Errorf("config map in non-default namespace returned while watching default namespace")
}
if !reflect.DeepEqual(defaultGot, gotFromWatch) {
t.Errorf("object from add event did not match expected value")
}
}

// However, watching all-namespaces should contain both the default and non-default namespaced configmaps
if _, gotFromWatch, err := allWatch.Next(); err != nil {
t.Errorf("failed to get next watch: %v", err)
} else {
if !reflect.DeepEqual(defaultGot, gotFromWatch) {
t.Errorf("watching all namespaces did not return the expected configmap")
}
}

if _, gotFromWatch, err := allWatch.Next(); err != nil {
t.Errorf("failed to get next watch: %v", err)
} else {
if !reflect.DeepEqual(nonDefaultGot, gotFromWatch) {
t.Errorf("watching all namespaces did not return the expected configmap")
}
}

// Delete the config map in the default namespace first, then delete the non-default namespace config map.
// Only the former should be noticed by the default-watch.

if err := client.DeleteConfigMap(ctx, *defaultCM.Metadata.Name, *defaultCM.Metadata.Namespace); err != nil {
t.Fatalf("delete config map: %v", err)
}
if err := client.DeleteConfigMap(ctx, *nonDefaultCM.Metadata.Name, *nonDefaultCM.Metadata.Namespace); err != nil {
t.Fatalf("delete config map: %v", err)
}

if event, gotFromWatch, err := defaultWatch.Next(); err != nil {
t.Errorf("failed to get next watch: %v", err)
} else {
if *event.Type != EventDeleted {
t.Errorf("expected event type %q got %q", EventDeleted, *event.Type)
}

// Resource version will be different after a delete
nonDefaultGot.Metadata.ResourceVersion = String("")
gotFromWatch.Metadata.ResourceVersion = String("")

if reflect.DeepEqual(nonDefaultGot, gotFromWatch) {
t.Errorf("should not have received event from non-default namespace while watching default namespace")
}
}

if err := client.DeleteNamespace(ctx, nonDefaultNamespaceName); err != nil {
t.Fatalf("delete namespace: %v", err)
}
}
32 changes: 6 additions & 26 deletions gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (c *{{ $.Name }}) Create{{ $r.Name }}(ctx context.Context, obj *{{ $.Import
}
if {{ $r.Namespaced }} {
if ns = c.client.namespaceFor(ns); ns == "" {
if ns == "" {
return nil, fmt.Errorf("no resource namespace provided")
}
md.Namespace = &ns
Expand Down Expand Up @@ -144,7 +144,7 @@ func (c *{{ $.Name }}) Update{{ $r.Name }}(ctx context.Context, obj *{{ $.Import
}
if {{ $r.Namespaced }} {
if ns = c.client.namespaceFor(ns); ns == "" {
if ns == "" {
return nil, fmt.Errorf("no resource namespace provided")
}
md.Namespace = &ns
Expand All @@ -162,25 +162,15 @@ func (c *{{ $.Name }}) Delete{{ $r.Name }}(ctx context.Context, name string{{ if
if name == "" {
return fmt.Errorf("create: no name for given object")
}
{{ if $r.Namespaced -}}
ns := c.client.namespaceFor(namespace)
{{ else -}}
ns := ""
{{ end }}
url := c.client.urlFor("{{ $.APIGroup }}", "{{ $.APIVersion }}", ns, "{{ $r.Pluralized }}", name)
url := c.client.urlFor("{{ $.APIGroup }}", "{{ $.APIVersion }}", {{ if $r.Namespaced }}namespace{{ else }}AllNamespaces{{ end }}, "{{ $r.Pluralized }}", name)
return c.client.delete(ctx, pbCodec, url)
}
func (c *{{ $.Name }}) Get{{ $r.Name }}(ctx context.Context, name{{ if $r.Namespaced }}, namespace{{ end }} string) (*{{ $.ImportName }}.{{ $r.Name }}, error) {
if name == "" {
return nil, fmt.Errorf("create: no name for given object")
}
{{ if $r.Namespaced -}}
ns := c.client.namespaceFor(namespace)
{{ else -}}
ns := ""
{{ end }}
url := c.client.urlFor("{{ $.APIGroup }}", "{{ $.APIVersion }}", ns, "{{ $r.Pluralized }}", name)
url := c.client.urlFor("{{ $.APIGroup }}", "{{ $.APIVersion }}", {{ if $r.Namespaced }}namespace{{ else }}AllNamespaces{{ end }}, "{{ $r.Pluralized }}", name)
resp := new({{ $.ImportName }}.{{ $r.Name }})
if err := c.client.get(ctx, pbCodec, url, resp); err != nil {
return nil, err
Expand Down Expand Up @@ -211,12 +201,7 @@ func (w *{{ $.Name }}{{ $r.Name }}Watcher) Close() error {
}
func (c *{{ $.Name }}) Watch{{ $r.Name | pluralize }}(ctx context.Context{{ if $r.Namespaced }}, namespace string{{ end }}, options ...Option) (*{{ $.Name }}{{ $r.Name }}Watcher, error) {
{{ if $r.Namespaced -}}
ns := c.client.namespaceFor(namespace)
{{ else -}}
ns := ""
{{- end }}
url := c.client.urlFor("{{ $.APIGroup }}", "{{ $.APIVersion }}", ns, "{{ $r.Pluralized }}", "", options...)
url := c.client.urlFor("{{ $.APIGroup }}", "{{ $.APIVersion }}", {{ if $r.Namespaced }}namespace{{ else }}AllNamespaces{{ end }}, "{{ $r.Pluralized }}", "", options...)
watcher, err := c.client.watch(ctx, url)
if err != nil {
return nil, err
Expand All @@ -225,12 +210,7 @@ func (c *{{ $.Name }}) Watch{{ $r.Name | pluralize }}(ctx context.Context{{ if $
}
func (c *{{ $.Name }}) List{{ $r.Name | pluralize }}(ctx context.Context{{ if $r.Namespaced }}, namespace string{{ end }}, options ...Option) (*{{ $.ImportName }}.{{ $r.Name }}List, error) {
{{ if $r.Namespaced -}}
ns := c.client.namespaceFor(namespace)
{{ else -}}
ns := ""
{{- end }}
url := c.client.urlFor("{{ $.APIGroup }}", "{{ $.APIVersion }}", ns, "{{ $r.Pluralized }}", "", options...)
url := c.client.urlFor("{{ $.APIGroup }}", "{{ $.APIVersion }}", {{ if $r.Namespaced }}namespace{{ else }}AllNamespaces{{ end }}, "{{ $r.Pluralized }}", "", options...)
resp := new({{ $.ImportName }}.{{ $r.Name }}List)
if err := c.client.get(ctx, pbCodec, url, resp); err != nil {
return nil, err
Expand Down
25 changes: 10 additions & 15 deletions tprs.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,46 +115,41 @@ type object interface {
}

func (t *ThirdPartyResources) Create(ctx context.Context, resource, namespace string, req, resp interface{}) error {
ns := t.c.namespaceFor(namespace)
if err := checkResource(t.apiGroup, t.apiVersion, resource, ns, "not required"); err != nil {
if err := checkResource(t.apiGroup, t.apiVersion, resource, namespace, "not required"); err != nil {
return err
}
url := t.c.urlFor(t.apiGroup, t.apiVersion, ns, resource, "")
url := t.c.urlFor(t.apiGroup, t.apiVersion, namespace, resource, "")
return t.c.create(ctx, jsonCodec, "POST", url, req, resp)
}

func (t *ThirdPartyResources) Update(ctx context.Context, resource, namespace, name string, req, resp interface{}) error {
ns := t.c.namespaceFor(namespace)
if err := checkResource(t.apiGroup, t.apiVersion, resource, ns, "not required"); err != nil {
if err := checkResource(t.apiGroup, t.apiVersion, resource, namespace, "not required"); err != nil {
return err
}
url := t.c.urlFor(t.apiGroup, t.apiVersion, ns, resource, name)
url := t.c.urlFor(t.apiGroup, t.apiVersion, namespace, resource, name)
return t.c.create(ctx, jsonCodec, "PUT", url, req, resp)
}

func (t *ThirdPartyResources) Get(ctx context.Context, resource, namespace, name string, resp interface{}) error {
ns := t.c.namespaceFor(namespace)
if err := checkResource(t.apiGroup, t.apiVersion, resource, ns, name); err != nil {
if err := checkResource(t.apiGroup, t.apiVersion, resource, namespace, name); err != nil {
return err
}
url := t.c.urlFor(t.apiGroup, t.apiVersion, ns, resource, name)
url := t.c.urlFor(t.apiGroup, t.apiVersion, namespace, resource, name)
return t.c.get(ctx, jsonCodec, url, resp)
}

func (t *ThirdPartyResources) Delete(ctx context.Context, resource, namespace, name string) error {
ns := t.c.namespaceFor(namespace)
if err := checkResource(t.apiGroup, t.apiVersion, resource, ns, name); err != nil {
if err := checkResource(t.apiGroup, t.apiVersion, resource, namespace, name); err != nil {
return err
}
url := t.c.urlFor(t.apiGroup, t.apiVersion, ns, resource, name)
url := t.c.urlFor(t.apiGroup, t.apiVersion, namespace, resource, name)
return t.c.delete(ctx, jsonCodec, url)
}

func (t *ThirdPartyResources) List(ctx context.Context, resource, namespace string, resp interface{}) error {
ns := t.c.namespaceFor(namespace)
if err := checkResource(t.apiGroup, t.apiVersion, resource, ns, "name not required"); err != nil {
if err := checkResource(t.apiGroup, t.apiVersion, resource, namespace, "name not required"); err != nil {
return err
}
url := t.c.urlFor(t.apiGroup, t.apiVersion, ns, resource, "")
url := t.c.urlFor(t.apiGroup, t.apiVersion, namespace, resource, "")
return t.c.get(ctx, jsonCodec, url, resp)
}

0 comments on commit 9fe8b1e

Please sign in to comment.