Skip to content
This repository has been archived by the owner on Aug 17, 2023. It is now read-only.

Turn the remove-namespace kustomize function into a proper function. #388

Merged
merged 1 commit into from
Aug 4, 2020
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: 4 additions & 0 deletions kustomize-fns/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CONTEXT=kubeflow-releasing
# Build the image using skaffold
build:
skaffold build --kube-context=$(CONTEXT) --file-output=latest_image.json
2 changes: 2 additions & 0 deletions kustomize-fns/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"bytes"
"fmt"
ip "github.com/kubeflow/kfctl/kustomize-fns/image-prefix"
rn "github.com/kubeflow/kfctl/kustomize-fns/remove-namespace"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"io"
Expand Down Expand Up @@ -109,6 +110,7 @@ func (d *Dispatcher) run(input io.Reader) error {
// dispatchTable maps configFunction Kinds to implementations
var dispatchTable = map[string]func() kio.Filter{
ip.Kind: ip.Filter,
rn.Kind: rn.Filter,
}

func (d *Dispatcher) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) {
Expand Down
174 changes: 174 additions & 0 deletions kustomize-fns/remove-namespace/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

// Package main implements an injection function for resource reservations and
// is run with `kustomize config run -- DIR/`.

// TODO(https://github.com/kubeflow/gcp-blueprints/issues/27): We should make this configurable and follow
// the model of image-prefix. This means instead of using a separate main.go we should use kustomize-fns/main.go
// and just register this function in the dispatcher.
package remove_namespace

import (
"fmt"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
"strings"
)

type groupKind struct {
Group string
Kind string
}

const (
Kind = "RemoveNamespace"
APIVersion = "kubeflow.org/v1alpha1"
)

var (

// List of clusterResources to match and remove namespace from.
// TODO(jlewi): How could we make this configurable? I think you want to put the relevant data
// into the YAML files that get processed by the transform. We could define an annotation and then
// the transform could look for any resource with this annotation and remove the namespace if it exists.
clusterKinds = []groupKind{
{
Kind: "Profile",
Group: "kubeflow.org",
},
{
Kind: "ClusterRbacConfig",
Group: "rbac.istio.io",
},
{
Kind: "ClusterIssuer",
Group: "cert-manager.io",
},
{
Kind: "CompositeController",
Group: "metacontroller.k8s.io",
},
}
)

// Filter returns a new RemoveNamespaceFunction
func Filter() kio.Filter {
return &RemoveNamespaceFunction{}
}

// RemoveNamespaceFunction implements the RemoveNamespace Function
type RemoveNamespaceFunction struct {
// Kind is the API name. Must be RemoveNamespace.
Kind string `yaml:"kind"`

// APIVersion is the API version. Must be examples.kpt.dev/v1alpha1
APIVersion string `yaml:"apiVersion"`

// Metadata defines instance metadata.
Metadata Metadata `yaml:"metadata"`

// Spec defines the desired declarative configuration.
Spec Spec `yaml:"spec"`
}

type Metadata struct {
// Name is the name of the RemoveNamespace Resources
Name string `yaml:"name"`

// Namespace is the namespace of the RemoveNamespace Resources
Namespace string `yaml:"namespace"`

// Labels are labels applied to the RemoveNamespace Resources
Labels map[string]string `yaml:"labels"`

// Annotations are annotations applied to the RemoveNamespace Resources
Annotations map[string]string `yaml:"annotations"`
}

type Spec struct {
ClusterKinds []*ClusterKind `yaml:"clusterKinds"`
}

type ClusterKind struct {
Kind string `yaml:"kind"`
Group string `yaml:"group"`
}

func (f *RemoveNamespaceFunction) init() error {
if f.Metadata.Name == "" {
return fmt.Errorf("must specify ImagePrefix name")
}

if f.Metadata.Labels == nil {
f.Metadata.Labels = map[string]string{}
}

if f.Spec.ClusterKinds == nil {
f.Spec.ClusterKinds = []*ClusterKind{}
}
return nil
}


// Filter looks for resources of the specified kind and removes them
func (f *RemoveNamespaceFunction) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) {
if err := f.init(); err != nil {
return nil, err
}

errors := []error{}

for _, r := range inputs {
if err := f.removeNamespace(r); err !=nil {
errors = append(errors, err)
}
}
return inputs, utilerrors.NewAggregate(errors)
}

func getGroup(apiVersion string) string {
pieces := strings.Split(apiVersion, "/")

if len(pieces) == 1 {
return apiVersion
}

// Strip the last piece as the version
pieces = pieces[0 : len(pieces)-1]
return strings.Join(pieces, "/")
}

// remove namespace from cluster resources.
func (f *RemoveNamespaceFunction) removeNamespace(r *yaml.RNode) error {

// check for the tshirt-size annotations
meta, err := r.GetMeta()
if err != nil {
return err
}

// TODO(jlewi): Does kustomize provide built in functions for filtering to a list of kinds?
isMatch := false
for _, c := range f.Spec.ClusterKinds {
group := getGroup(meta.APIVersion)

if group == c.Group && meta.Kind == c.Kind {
isMatch = true
break
}
}

// Skip this object because it is not an allowed kind.
if !isMatch {
return nil
}

return r.PipeE(
yaml.LookupCreate(yaml.ScalarNode, "metadata"),
yaml.FieldClearer{
Name: "namespace",
},
)
}
143 changes: 143 additions & 0 deletions kustomize-fns/remove-namespace/function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package remove_namespace

import (
"bytes"
"github.com/kubeflow/kfctl/kustomize-fns/utils"
"github.com/pkg/errors"
"io/ioutil"
"os"
"path"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
"testing"
)

func readYaml(path string) ([]*yaml.RNode, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "Error reading path %v", path)
}

input := bytes.NewReader(data)
reader := kio.ByteReader{
Reader: input,
// We need to disable adding reader annotations because
// we want to run some checks about whether annotations are set and
// adding those annotations interferes with that.
OmitReaderAnnotations: true,
}

nodes, err := reader.Read()

if err != nil {
return nil, errors.Wrapf(err, "Error unmarshaling %v", path)
}

return nodes, nil
}

func writeYaml(nodes []*yaml.RNode) ([]byte, error) {
var b bytes.Buffer
writer := kio.ByteWriter{
Writer: &b,
}

if err := writer.Write(nodes); err != nil {
return []byte{}, err
}

return b.Bytes(), nil
}

func Test_remove_namespace(t *testing.T) {

type testCase struct {
InputFile string
ExpectedFile string
}

cwd, err := os.Getwd()

if err != nil {
t.Fatalf("Error getting current directory; error %v", err)
}

testDir := path.Join(cwd, "test_data")

cases := []testCase{
{
InputFile: path.Join(testDir, "in_cluster_resource.yaml"),
ExpectedFile: path.Join(testDir, "out_cluster_resource.yaml"),
},
{
InputFile: path.Join(testDir, "ns_resource.yaml"),
ExpectedFile: path.Join(testDir, "ns_resource.yaml"),
},
}

f := &RemoveNamespaceFunction {
Spec: Spec{
ClusterKinds: []*ClusterKind {
{
Kind: "Profile",
Group: "kubeflow.org",
},
{
Kind: "ClusterRbacConfig",
Group: "rbac.istio.io",
},
},
},
}

for _, c := range cases {
nodes, err := readYaml(c.InputFile)

if err != nil {
t.Errorf("Error reading YAML: %v", err)
}

if len(nodes) != 1 {
t.Errorf("Expected 1 node in file %v", c.InputFile)
}
node := nodes[0]

err = f.removeNamespace(node)
if err != nil {
t.Errorf("prefixImage failed; error %v", err)
continue
}

b, err := writeYaml([]*yaml.RNode{node})

if err != nil {
t.Errorf("Error writing yaml; error %v", err)
continue
}

actual := string(b)


// read the expected yaml and then rewrites using kio.ByteWriter.
// We do this because ByteWriter makes some formatting decisions and we
// we want to apply the same formatting to the expected values

eNode, err := readYaml(c.ExpectedFile)

if err != nil {
t.Errorf("Could not read expected file %v; error %v", c.ExpectedFile, err)
}

eBytes, err := writeYaml(eNode)

if err != nil {
t.Errorf("Could not format expected file %v; error %v", c.ExpectedFile, err)
}

expected := string(eBytes)

if actual != expected {
utils.PrintDiff(actual, expected)
}
}
}
5 changes: 0 additions & 5 deletions kustomize-fns/remove-namespace/go.mod

This file was deleted.

Loading