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

Obtain scope of CRD instances from its manifest as a fallback #1876

Merged
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
4 changes: 2 additions & 2 deletions cluster/kubernetes/cached_disco_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestCachedDiscovery(t *testing.T) {
t.Fatal(err)
}

namespaced, err := namespacer.lookupNamespaced("foo/v1", "Custom")
namespaced, err := namespacer.lookupNamespaced("foo/v1", "Custom", nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -124,7 +124,7 @@ func TestCachedDiscovery(t *testing.T) {
t.Error("does not exist")
}

namespaced, err = namespacer.lookupNamespaced("foo/v1", "Custom")
namespaced, err = namespacer.lookupNamespaced("foo/v1", "Custom", nil)
if err != nil {
t.Fatal(err)
}
Expand Down
39 changes: 37 additions & 2 deletions cluster/kubernetes/manifests.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package kubernetes

import (
"gopkg.in/yaml.v2"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/weaveworks/flux"
kresource "github.com/weaveworks/flux/cluster/kubernetes/resource"
"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/resource"
)

// ResourceScopes maps resource definitions (GroupVersionKind) to whether they are namespaced or not
type ResourceScopes map[schema.GroupVersionKind]v1beta1.ResourceScope

// namespacer assigns namespaces to manifests that need it (or "" if
// the manifest should not have a namespace.
type namespacer interface {
// EffectiveNamespace gives the namespace that would be used were
// the manifest to be applied. This may be "", indicating that it
// should not have a namespace (i.e., it's a cluster-level
// resource).
EffectiveNamespace(kresource.KubeManifest) (string, error)
EffectiveNamespace(manifest kresource.KubeManifest, knownScopes ResourceScopes) (string, error)
}

// Manifests is an implementation of cluster.Manifests, particular to
Expand All @@ -26,11 +33,39 @@ type Manifests struct {
Namespacer namespacer
}

func getCRDScopes(manifests map[string]kresource.KubeManifest) ResourceScopes {
result := ResourceScopes{}
for _, km := range manifests {
if km.GetKind() == "CustomResourceDefinition" {
var crd v1beta1.CustomResourceDefinition
if err := yaml.Unmarshal(km.Bytes(), &crd); err != nil {
// The CRD can't be parsed, so we (intentionally) ignore it and
// just hope for EffectiveNamespace() to find its scope in the cluster if needed.
continue
}
crdVersions := crd.Spec.Versions
if len(crdVersions) == 0 {
crdVersions = []v1beta1.CustomResourceDefinitionVersion{{Name: crd.Spec.Version}}
}
for _, crdVersion := range crdVersions {
gvk := schema.GroupVersionKind{
Group: crd.Spec.Group,
Version: crdVersion.Name,
Kind: crd.Spec.Names.Kind,
}
result[gvk] = crd.Spec.Scope
}
}
}
return result
}

func postProcess(manifests map[string]kresource.KubeManifest, nser namespacer) (map[string]resource.Resource, error) {
knownScopes := getCRDScopes(manifests)
squaremo marked this conversation as resolved.
Show resolved Hide resolved
result := map[string]resource.Resource{}
for _, km := range manifests {
if nser != nil {
ns, err := nser.EffectiveNamespace(km)
ns, err := nser.EffectiveNamespace(km, knownScopes)
if err != nil {
return nil, err
}
Expand Down
62 changes: 62 additions & 0 deletions cluster/kubernetes/manifests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package kubernetes

import (
"io/ioutil"
"path/filepath"
"testing"

"github.com/weaveworks/flux/cluster/kubernetes/testfiles"
)

func TestKnownCRDScope(t *testing.T) {
coreClient := makeFakeClient()

nser, err := NewNamespacer(coreClient.Discovery())
if err != nil {
t.Fatal(err)
}
manifests := Manifests{nser}

dir, cleanup := testfiles.TempDir(t)
defer cleanup()
const defs = `---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: foo
spec:
group: foo.example.com
names:
kind: Foo
listKind: FooList
plural: foos
shortNames:
- foo
scope: Namespaced
version: v1beta1
versions:
- name: v1beta1
served: true
storage: true
---
apiVersion: foo.example.com/v1beta1
kind: Foo
metadata:
name: fooinstance
namespace: bar
`

if err = ioutil.WriteFile(filepath.Join(dir, "test.yaml"), []byte(defs), 0600); err != nil {
t.Fatal(err)
}

resources, err := manifests.LoadManifests(dir, []string{dir})
if err != nil {
t.Fatal(err)
}

if _, ok := resources["bar:foo/fooinstance"]; !ok {
t.Fatal("couldn't find crd instance")
}

}
25 changes: 22 additions & 3 deletions cluster/kubernetes/namespacer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package kubernetes
import (
"fmt"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/tools/clientcmd"

Expand Down Expand Up @@ -55,8 +57,8 @@ var getDefaultNamespace = func() (string, error) {
// effectiveNamespace yields the namespace that would be used for this
// resource were it applied, taking into account the kind of the
// resource, and local configuration.
func (n *namespaceViaDiscovery) EffectiveNamespace(m kresource.KubeManifest) (string, error) {
namespaced, err := n.lookupNamespaced(m.GroupVersion(), m.GetKind())
func (n *namespaceViaDiscovery) EffectiveNamespace(m kresource.KubeManifest, knownScopes ResourceScopes) (string, error) {
namespaced, err := n.lookupNamespaced(m.GroupVersion(), m.GetKind(), knownScopes)
switch {
case err != nil:
return "", err
Expand All @@ -68,7 +70,24 @@ func (n *namespaceViaDiscovery) EffectiveNamespace(m kresource.KubeManifest) (st
return m.GetNamespace(), nil
}

func (n *namespaceViaDiscovery) lookupNamespaced(groupVersion, kind string) (bool, error) {
func (n *namespaceViaDiscovery) lookupNamespaced(groupVersion string, kind string, knownScopes ResourceScopes) (bool, error) {
namespaced, clusterErr := n.lookupNamespacedInCluster(groupVersion, kind)
if clusterErr == nil || knownScopes == nil {
2opremio marked this conversation as resolved.
Show resolved Hide resolved
return namespaced, nil
}
// Not found in the cluster, let's try the known scopes
gv, err := schema.ParseGroupVersion(groupVersion)
if err != nil {
return false, clusterErr
}
scope, found := knownScopes[gv.WithKind(kind)]
if !found {
return false, clusterErr
}
return scope == v1beta1.NamespaceScoped, nil
}

func (n *namespaceViaDiscovery) lookupNamespacedInCluster(groupVersion, kind string) (bool, error) {
resourceList, err := n.disco.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return false, fmt.Errorf("error looking up API resources for %s.%s: %s", kind, groupVersion, err.Error())
Expand Down
8 changes: 7 additions & 1 deletion cluster/kubernetes/namespacer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ func makeFakeClient() *corefake.Clientset {
{Name: "namespaces", SingularName: "namespace", Namespaced: false, Kind: "Namespace", Verbs: getAndList},
},
},
{
GroupVersion: "apiextensions.k8s.io/v1beta1",
APIResources: []metav1.APIResource{
{Name: "customresourcedefinitions", SingularName: "customresourcedefinition", Namespaced: false, Kind: "CustomResourceDefinition", Verbs: getAndList},
},
},
}

coreClient := corefake.NewSimpleClientset()
Expand Down Expand Up @@ -101,7 +107,7 @@ metadata:
t.Errorf("manifest for %q not found", id)
return
}
got, err := nser.EffectiveNamespace(res)
got, err := nser.EffectiveNamespace(res, nil)
if err != nil {
t.Errorf("error getting effective namespace for %q: %s", id, err.Error())
return
Expand Down
2 changes: 1 addition & 1 deletion daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ func mustParseImageRef(ref string) image.Ref {

type anonNamespacer func(kresource.KubeManifest) string

func (fn anonNamespacer) EffectiveNamespace(m kresource.KubeManifest) (string, error) {
func (fn anonNamespacer) EffectiveNamespace(m kresource.KubeManifest, _ kubernetes.ResourceScopes) (string, error) {
return fn(m), nil
}

Expand Down
2 changes: 1 addition & 1 deletion release/releaser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

type constNamespacer string

func (ns constNamespacer) EffectiveNamespace(kresource.KubeManifest) (string, error) {
func (ns constNamespacer) EffectiveNamespace(manifest kresource.KubeManifest, _ kubernetes.ResourceScopes) (string, error) {
return string(ns), nil
}

Expand Down