github.com/mgoltzsche/khelm@v1.0.1/cmd/khelm/fn_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/mgoltzsche/khelm/internal/output"
    12  	"github.com/mgoltzsche/khelm/pkg/config"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	"sigs.k8s.io/kustomize/kyaml/yaml"
    16  )
    17  
    18  func TestKptFnCommand(t *testing.T) {
    19  	dir, err := ioutil.TempDir("", "khelm-fn-test-")
    20  	require.NoError(t, err)
    21  	defer os.RemoveAll(dir)
    22  	os.Setenv("HELM_HOME", dir)
    23  	defer os.Unsetenv("HELM_HOME")
    24  	exampleDir := filepath.Join("..", "..", "example")
    25  
    26  	inputAnnotations := map[string]interface{}{}
    27  	inputItems := []map[string]interface{}{
    28  		{
    29  			// should be preserved
    30  			"somekey": "somevalue",
    31  		},
    32  		{
    33  			// should be filtered
    34  			"apiVersion": "v1",
    35  			"kind":       "ConfigMap",
    36  			"metadata":   map[string]interface{}{"annotations": inputAnnotations},
    37  		},
    38  	}
    39  
    40  	for _, c := range []struct {
    41  		name           string
    42  		input          kptFnConfig
    43  		mustContainObj int
    44  		mustContain    []string
    45  	}{
    46  		{
    47  			"chart path only",
    48  			kptFnConfig{ChartConfig: &config.ChartConfig{
    49  				LoaderConfig: config.LoaderConfig{
    50  					Chart: filepath.Join(exampleDir, "namespace"),
    51  				},
    52  			}},
    53  			3, []string{"myconfiga"},
    54  		},
    55  		{
    56  			"latest cluster scoped remote chart",
    57  			kptFnConfig{ChartConfig: &config.ChartConfig{
    58  				LoaderConfig: config.LoaderConfig{
    59  					Repository: "https://charts.jetstack.io",
    60  					Chart:      "cert-manager",
    61  				},
    62  			}},
    63  			-1, []string{"acme.cert-manager.io"},
    64  		},
    65  		{
    66  			"remote chart with version",
    67  			kptFnConfig{ChartConfig: &config.ChartConfig{
    68  				LoaderConfig: config.LoaderConfig{
    69  					Repository: "https://charts.jetstack.io",
    70  					Chart:      "cert-manager",
    71  					Version:    "0.9.x",
    72  				},
    73  			}},
    74  			34, []string{"chart: cainjector-v0.9.1"},
    75  		},
    76  		{
    77  			"release name",
    78  			kptFnConfig{ChartConfig: &config.ChartConfig{
    79  				LoaderConfig: config.LoaderConfig{
    80  					Chart: filepath.Join(exampleDir, "release-name"),
    81  				},
    82  				RendererConfig: config.RendererConfig{
    83  					Name: "myrelease",
    84  				},
    85  			}},
    86  			1, []string{"myrelease-config"},
    87  		},
    88  		{
    89  			"valueFiles",
    90  			kptFnConfig{ChartConfig: &config.ChartConfig{
    91  				LoaderConfig: config.LoaderConfig{
    92  					Chart: filepath.Join(exampleDir, "values-inheritance", "chart"),
    93  				},
    94  				RendererConfig: config.RendererConfig{
    95  					ValueFiles: []string{filepath.Join(exampleDir, "values-inheritance", "values.yaml")},
    96  				}}},
    97  			1, []string{" valueoverwrite: overwritten by file"},
    98  		},
    99  		{
   100  			"values",
   101  			kptFnConfig{ChartConfig: &config.ChartConfig{
   102  				LoaderConfig: config.LoaderConfig{
   103  					Chart: filepath.Join(exampleDir, "values-inheritance", "chart"),
   104  				},
   105  				RendererConfig: config.RendererConfig{
   106  					Values: map[string]interface{}{
   107  						"example": map[string]string{"overrideValue": "explicitly"},
   108  					},
   109  				}}},
   110  			1, []string{" valueoverwrite: explicitly"},
   111  		},
   112  		{
   113  			"values override",
   114  			kptFnConfig{ChartConfig: &config.ChartConfig{
   115  				LoaderConfig: config.LoaderConfig{
   116  					Chart: filepath.Join(exampleDir, "values-inheritance", "chart"),
   117  				},
   118  				RendererConfig: config.RendererConfig{
   119  					ValueFiles: []string{filepath.Join(exampleDir, "values-inheritance", "values.yaml")},
   120  					Values: map[string]interface{}{
   121  						"example": map[string]string{"overrideValue": "explicitly"},
   122  					},
   123  				}}},
   124  			1, []string{" valueoverwrite: explicitly"},
   125  		},
   126  		{
   127  			"apiversions",
   128  			kptFnConfig{ChartConfig: &config.ChartConfig{
   129  				LoaderConfig: config.LoaderConfig{
   130  					Chart: filepath.Join(exampleDir, "apiversions-condition", "chart"),
   131  				},
   132  				RendererConfig: config.RendererConfig{
   133  					APIVersions: []string{"myfancyapi/v1", ""},
   134  				}}},
   135  			1, []string{"fancycr"},
   136  		},
   137  		{
   138  			"kubeversion",
   139  			kptFnConfig{ChartConfig: &config.ChartConfig{
   140  				LoaderConfig: config.LoaderConfig{
   141  					Chart: filepath.Join(exampleDir, "release-name"),
   142  				},
   143  				RendererConfig: config.RendererConfig{
   144  					KubeVersion: "1.12",
   145  				}}},
   146  			1, []string{"k8sVersion: v1.12.0"},
   147  		},
   148  		{
   149  			"expand-list",
   150  			kptFnConfig{ChartConfig: &config.ChartConfig{
   151  				LoaderConfig: config.LoaderConfig{
   152  					Chart: filepath.Join(exampleDir, "expand-list"),
   153  				},
   154  			}},
   155  			3, []string{"\n  name: myserviceaccount2\n"},
   156  		},
   157  		{
   158  			"namespace",
   159  			kptFnConfig{ChartConfig: &config.ChartConfig{
   160  				LoaderConfig: config.LoaderConfig{
   161  					Chart: filepath.Join(exampleDir, "namespace"),
   162  				},
   163  				RendererConfig: config.RendererConfig{
   164  					Namespace: "mynamespace",
   165  				},
   166  			}},
   167  			3, []string{" namespace: mynamespace\n"},
   168  		},
   169  		{
   170  			"force namespace",
   171  			kptFnConfig{ChartConfig: &config.ChartConfig{
   172  				LoaderConfig: config.LoaderConfig{
   173  					Chart: filepath.Join(exampleDir, "namespace"),
   174  				},
   175  				RendererConfig: config.RendererConfig{
   176  					ForceNamespace: "forced-namespace",
   177  				},
   178  			}},
   179  			3, []string{" namespace: forced-namespace\n"},
   180  		},
   181  		{
   182  			"exclude",
   183  			kptFnConfig{ChartConfig: &config.ChartConfig{
   184  				LoaderConfig: config.LoaderConfig{
   185  					Chart: filepath.Join(exampleDir, "namespace"),
   186  				},
   187  				RendererConfig: config.RendererConfig{
   188  					Exclude: []config.ResourceSelector{
   189  						{
   190  							APIVersion: "v1",
   191  							Kind:       "ConfigMap",
   192  							Name:       "myconfiga",
   193  						},
   194  					},
   195  				},
   196  			}},
   197  			2, []string{"myconfigb"},
   198  		},
   199  		{
   200  			"include",
   201  			kptFnConfig{ChartConfig: &config.ChartConfig{
   202  				LoaderConfig: config.LoaderConfig{
   203  					Chart: filepath.Join(exampleDir, "namespace"),
   204  				},
   205  				RendererConfig: config.RendererConfig{
   206  					Include: []config.ResourceSelector{
   207  						{
   208  							APIVersion: "v1",
   209  							Kind:       "ConfigMap",
   210  						},
   211  					},
   212  					Exclude: []config.ResourceSelector{
   213  						{
   214  							APIVersion: "v1",
   215  							Kind:       "ConfigMap",
   216  							Name:       "myconfiga",
   217  						},
   218  					},
   219  				},
   220  			}},
   221  			1, []string{"myconfigb"},
   222  		},
   223  		{
   224  			"annotate output path",
   225  			kptFnConfig{
   226  				ChartConfig: &config.ChartConfig{
   227  					LoaderConfig: config.LoaderConfig{
   228  						Chart: filepath.Join(exampleDir, "namespace"),
   229  					},
   230  				},
   231  				OutputPath: "my/output/manifest.yaml",
   232  			},
   233  			3, []string{" annotations:\n    config.kubernetes.io/index: 1\n    config.kubernetes.io/path: my/output/manifest.yaml\n"},
   234  		},
   235  		{
   236  			"annotate output path when annotations empty",
   237  			kptFnConfig{
   238  				ChartConfig: &config.ChartConfig{
   239  					LoaderConfig: config.LoaderConfig{
   240  						Chart: filepath.Join(exampleDir, "empty-annotations"),
   241  					},
   242  				},
   243  				OutputPath: "my/output/path/",
   244  			},
   245  			3, []string{
   246  				"\n    config.kubernetes.io/path: my/output/path/kustomization.yaml\n",
   247  				"\n    config.kubernetes.io/path: my/output/path/serviceaccount_sa1.yaml\n",
   248  				"\n    config.kubernetes.io/path: my/output/path/serviceaccount_sa2.yaml\n",
   249  				" myannotation: should-be-preserved\n",
   250  			},
   251  		},
   252  		{
   253  			"output kustomization",
   254  			kptFnConfig{
   255  				ChartConfig: &config.ChartConfig{
   256  					LoaderConfig: config.LoaderConfig{
   257  						Chart: filepath.Join(exampleDir, "namespace"),
   258  					},
   259  				},
   260  				OutputPath: "my/output/path/",
   261  			},
   262  			4, []string{"resources:\n- configmap_myconfiga.yaml\n- configmap_myconfigb.yaml\n"},
   263  		},
   264  	} {
   265  		t.Run(c.name, func(t *testing.T) {
   266  			c.input.Debug = true
   267  			if c.input.Name == "" {
   268  				c.input.Name = "release-name"
   269  			}
   270  			outPath := c.input.OutputPath
   271  			if outPath == "" {
   272  				if output.IsDirectory(outPath) {
   273  					outPath = path.Join(outPath, "previously-generated.yaml")
   274  				} else {
   275  					outPath = "generated-manifest.yaml"
   276  				}
   277  			}
   278  			inputAnnotations[annotationPath] = outPath
   279  			b, err := yaml.Marshal(map[string]interface{}{
   280  				"apiVersion":     "config.kubernetes.io/v1alpha1",
   281  				"kind":           "ResourceList",
   282  				"items":          inputItems,
   283  				"functionConfig": map[string]interface{}{"data": c.input},
   284  			})
   285  			require.NoError(t, err)
   286  			var out bytes.Buffer
   287  			os.Args = []string{"khelmfn"}
   288  			err = Execute(bytes.NewReader(b), &out)
   289  			require.NoError(t, err)
   290  			result := validateYAML(t, out.Bytes(), 1)
   291  			items, _ := result["items"].([]interface{})
   292  			out.Reset()
   293  			enc := yaml.NewEncoder(&out)
   294  			for _, item := range items {
   295  				err = enc.Encode(item)
   296  				require.NoError(t, err)
   297  			}
   298  			if c.mustContainObj >= 0 {
   299  				// assert n+1 resources because one provided input resource should be preserved,
   300  				// the 2nd input resource should be excluded since it was generated by this function during a previous run.
   301  				if !assert.Equal(t, c.mustContainObj+1, len(items), "amount of resources within output") {
   302  					t.Log("\n" + out.String())
   303  				}
   304  			}
   305  			for _, mustContain := range c.mustContain {
   306  				require.Contains(t, out.String(), mustContain, "output of %#v", c.input)
   307  			}
   308  		})
   309  	}
   310  }