...

Source file src/edge-infra.dev/test/f2/x/ktest/kustomization/manifests.go

Documentation: edge-infra.dev/test/f2/x/ktest/kustomization

     1  package kustomization
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	"sigs.k8s.io/kustomize/api/filters/filtersutil"
     8  	"sigs.k8s.io/kustomize/api/filters/fsslice"
     9  	"sigs.k8s.io/kustomize/api/filters/namespace"
    10  	"sigs.k8s.io/kustomize/api/filters/refvar"
    11  	"sigs.k8s.io/kustomize/api/filters/suffix"
    12  	"sigs.k8s.io/kustomize/api/types"
    13  	"sigs.k8s.io/kustomize/kyaml/kio"
    14  	"sigs.k8s.io/kustomize/kyaml/resid"
    15  	"sigs.k8s.io/kustomize/kyaml/yaml"
    16  
    17  	"edge-infra.dev/pkg/k8s/decoder"
    18  	"edge-infra.dev/pkg/k8s/unstructured"
    19  )
    20  
    21  // ProcessManifests applies a set of mutations to the supplied manifests which
    22  // allows the manifests to be safely deployed to a shared test cluster by using
    23  // the unique test id and namespace isolation. This includes:
    24  //   - mutating the namespace to test specific namespace
    25  //   - mutating ClusterRole and ClusterRoleBinding names to include the test uid
    26  //   - replacing the string `$(TEST_NAMESPACE)` from any config map or env var with the unique test namespace
    27  //   - removing priority class from deployments
    28  //
    29  // See test/f2/examples/embed/kustomization/kustomization_test.go
    30  // for example usage.
    31  func ProcessManifests(uid string, manifests []byte, namespace string) ([]*unstructured.Unstructured, error) {
    32  	buf := bytes.Buffer{}
    33  
    34  	filters := append(
    35  		processName(uid),
    36  		processNamespace(namespace),
    37  	)
    38  	filters = append(filters, processNamespaceVar(namespace)...)
    39  	filters = append(filters, processPriorityClass()...)
    40  
    41  	err := kio.Pipeline{
    42  		Inputs:  []kio.Reader{&kio.ByteReader{Reader: bytes.NewReader(manifests)}},
    43  		Outputs: []kio.Writer{kio.ByteWriter{Writer: &buf}},
    44  		Filters: filters,
    45  	}.Execute()
    46  
    47  	if err != nil {
    48  		return nil, fmt.Errorf("failed to update manifests: %w", err)
    49  	}
    50  
    51  	return decoder.DecodeYAML(buf.Bytes())
    52  }
    53  
    54  // processNamespace returns a kio.Filter which can be used to transform the
    55  // namespace on all objects to the provided namespace
    56  func processNamespace(ns string) kio.Filter {
    57  	fss := types.FsSlice{
    58  		{
    59  			Path:               "metadata/namespace",
    60  			CreateIfNotPresent: true,
    61  		},
    62  	}
    63  
    64  	return namespace.Filter{
    65  		Namespace:              ns,
    66  		FsSlice:                fss,
    67  		SetRoleBindingSubjects: namespace.AllServiceAccountSubjects,
    68  	}
    69  }
    70  
    71  // processName is used to create a set of kio.Filter which can update the names
    72  // of any cluster scoped objects to include a unique id suffix to avoid
    73  // collisions during parallel test runs. It also updates all references to those
    74  // objects in any other objects, such as the ClusterRole reference within the
    75  // ClusterRoleBinding.
    76  func processName(uid string) []kio.Filter {
    77  	filters := []kio.Filter{}
    78  
    79  	fsSlice := types.FsSlice{
    80  		{
    81  			Path: "metadata/name",
    82  			Gvk: resid.Gvk{
    83  				Group:   "rbac.authorization.k8s.io",
    84  				Version: "v1",
    85  				Kind:    "ClusterRole",
    86  			},
    87  		},
    88  		{
    89  			Path: "metadata/name",
    90  			Gvk: resid.Gvk{
    91  				Group:   "rbac.authorization.k8s.io",
    92  				Version: "v1",
    93  				Kind:    "ClusterRoleBinding",
    94  			},
    95  		},
    96  		// It may be possible to use sigs.k8s.io/kustomize/api/filters/nameref
    97  		// instead of using this, but I wasn't able to work out that package
    98  		{
    99  			Path: "roleRef/name",
   100  			Gvk: resid.Gvk{
   101  				Group:   "rbac.authorization.k8s.io",
   102  				Version: "v1",
   103  				Kind:    "ClusterRoleBinding",
   104  			},
   105  		},
   106  	}
   107  
   108  	for _, fieldSpec := range fsSlice {
   109  		filters = append(filters, suffix.Filter{
   110  			// TODO: Unique names should include the namespace as well as the uid
   111  			Suffix:    fmt.Sprintf("-%s", uid),
   112  			FieldSpec: fieldSpec,
   113  		})
   114  	}
   115  
   116  	return filters
   117  }
   118  
   119  // processNamespaceVar can be used to inject the test namespace into any
   120  // environment variables or config map's by replacing any `TEST_NAMESPACE`
   121  // string from these locations
   122  func processNamespaceVar(ns string) []kio.Filter {
   123  	envVarName := "TEST_NAMESPACE"
   124  	return []kio.Filter{
   125  		refvar.Filter{
   126  			FieldSpec: types.FieldSpec{Path: "spec/template/spec/containers/env/value"},
   127  			MappingFunc: refvar.MakePrimitiveReplacer(map[string]int{}, map[string]interface{}{
   128  				envVarName: ns,
   129  			}),
   130  		},
   131  		refvar.Filter{
   132  			FieldSpec: types.FieldSpec{Path: "data"},
   133  			MappingFunc: refvar.MakePrimitiveReplacer(map[string]int{}, map[string]interface{}{
   134  				envVarName: ns,
   135  			}),
   136  		},
   137  	}
   138  }
   139  
   140  // Removes any priority class from Daemonsets and Deployments - kind and gke
   141  // clusters for integration tests won't have any priority classes defined, so
   142  // will fail to deploy if this is set.
   143  func processPriorityClass() []kio.Filter {
   144  	return []kio.Filter{kio.FilterAll(fsslice.Filter{
   145  		CreateKind: yaml.ScalarNode,
   146  		SetValue:   filtersutil.SetScalar(""),
   147  		FsSlice: []types.FieldSpec{
   148  			{Path: "spec/template/spec/priorityClassName"},
   149  		},
   150  	})}
   151  }
   152  

View as plain text