Repository: kinvolk/lokomotive
Pull request #1342 (2021-01-22 12:09:38)

contour: Fix the hostPort regression
This change was introduced in 8d44c8513f67512527218c8a538e79a61cb7cd34 - "don't expose Envoy using hostPorts". But a later commit overrode it: 6a6a3c5d849b981559c2cc2cf3797bfd6b6c9159 - "contour: Update to v1.7.0". This commit fixes this regression.

Fixes: https://github.com/kinvolk/lokomotive/issues/1336

surajssd: Right now the way these test helper functions are written there is no easy way to write test that verify an error condition. Right now the test fails, right away because we pass test object `t`.
surajssd: @invidian PTAL all the comments have been addressed.

Patch:
diff --git a/assets/charts/components/contour/templates/03-envoy.yaml b/assets/charts/components/contour/templates/03-envoy.yaml
index 1d972716..6cb94a90 100644
--- a/assets/charts/components/contour/templates/03-envoy.yaml
+++ b/assets/charts/components/contour/templates/03-envoy.yaml
@@ -95,11 +95,9 @@ spec:
               fieldPath: metadata.name
         ports:
         - containerPort: 80
-          hostPort: 80
           name: http
           protocol: TCP
         - containerPort: 443
-          hostPort: 443
           name: https
           protocol: TCP
         readinessProbe:
diff --git a/pkg/assets/generated_assets.go b/pkg/assets/generated_assets.go
index 499c19f5..f8834df2 100644
--- a/pkg/assets/generated_assets.go
+++ b/pkg/assets/generated_assets.go
@@ -730,9 +730,9 @@ var vfsgenAssets = func() http.FileSystem {
 		"/charts/components/contour/templates/03-envoy.yaml": &vfsgen۰CompressedFileInfo{
 			name:             "03-envoy.yaml",
 			modTime:          time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC),
-			uncompressedSize: 3944,
+			uncompressedSize: 3897,
 
-			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x57\x5b\x6f\xdb\x36\x14\x7e\xf7\xaf\x20\xb2\x0e\xe8\x1e\xe4\x4b\xd3\x87\x4c\x40\x1e\xb2\xa4\xeb\x06\xb4\x8e\x51\xa7\x45\xfb\x54\xd0\xd4\xb1\xcd\x9a\xe2\x11\xc8\x23\x25\xaa\x97\xff\x3e\x50\xb2\x64\x52\xb2\x9d\x6c\xc3\xaa\x27\xe3\x5c\x3f\x7e\xe7\x42\x3a\x8a\xa2\xc1\x4f\xec\xf3\xe7\xcf\x31\x7b\x87\x1b\x4c\x91\x64\x01\xcc\x66\x20\xe4\x52\x0a\x26\xd6\x5c\xaf\x60\xb0\x91\x3a\x89\xd9\x07\x54\xf0\x9b\xd4\x89\xd4\xab\x01\xcf\xe4\x27\x30\x56\xa2\x8e\x99\x59\x70\x31\xe4\x39\xad\xd1\xc8\xef\x9c\x24\xea\xe1\xe6\xc2\x0e\x25\x8e\x8a\xc9\x20\x05\xe2\x09\x27\x1e\x0f\x18\xd3\x3c\x85\x98\x81\x2e\xb0\x8c\x32\x23\x0b\xa9\x60\x05\x49\x94\xd9\x6c\xa7\xb4\x19\x17\x10\xb3\xcc\xe0\x37\x10\x24\x50\x13\xe6\x66\x60\x50\xc1\x07\x58\xba\x08\x35\x90\x6b\x95\x5b\x02\xe3\xf0\xb4\x51\x7b\xf1\x78\x26\xdf\x1a\xcc\xb3\x13\xf8\x06\x36\x5f\xb8\x44\x36\x1e\x44\xbb\xd0\x73\x30\x85\x14\x70\x25\x04\xe6\x9a\x42\xcc\x27\x41\x3a\x22\x7d\x52\x78\x96\x59\x77\xfe\x3a\xec\x0d\x87\x14\xf5\x1c\x28\xe0\x43\xf1\x05\x28\xeb\x7e\x39\xb8\x59\x98\xe6\x39\x49\x5d\x9d\x9c\x7b\x9e\x25\x9c\x60\x4e\x86\x13\xac\xca\x3a\x20\x95\x19\x54\x35\x53\x52\xaf\x3e\x56\x06\x95\xdc\xf8\x92\xda\x94\xb1\x94\x3f\x7c\xd4\xbc\xe0\x52\xf1\x85\x82\x98\x4d\xc6\x3f\x0f\x18\xb3\xa0\x40\x10\x9a\xda\x2a\xe5\x24\xd6\xef\x3c\xc8\x1d\xd0\x04\x69\xa6\xda\x98\xfe\x39\x2b\x53\xad\x91\x2a\xf2\x5b\x6f\xe6\x8e\x93\x02\xad\x21\xaf\xba\xc5\x0a\xc3\x1d\xe6\x33\x32\x39\x9c\x1d\x31\xca\xd0\x50\xcc\xce\x2e\xc6\xe3\x57\x47\x4d\x38\xad\x63\x76\x36\xb2\xc4\xc9\x8e\xf6\xba\xc6\x5e\x05\x67\xe8\x9c\x82\xb1\x86\x54\xf7\x6d\xb7\x11\xbb\x97\xb4\x66\xc3\x4f\x5c\xe5\x60\x87\x1a\x13\xb8\x5a\x2e\xa5\x96\x54\xb2\xc7\xc7\xe6\x70\x3b\xc9\x3e\xa6\x6f\x17\x0f\xb6\x5b\x46\xf8\x85\xa7\x8a\x0d\xd9\x5f\x4c\xea\x04\x34\xb1\xc9\x78\x1f\xc0\xe5\x01\x9d\x38\xc1\xb1\xcc\x84\x0a\x4c\x4d\xe0\xde\xcf\x13\x1e\xc9\x72\x71\x2a\x89\xeb\x23\x2e\x35\x98\x96\x8d\x88\x09\x4c\x53\xae\x93\xfd\x51\x22\x36\x5a\x48\x3d\x6a\x7a\xae\x65\xcd\xac\x3c\x0e\x9d\xd9\x9e\xc2\x46\x62\xd7\x39\x25\x78\xaf\xa3\x94\x6b\xbe\x82\xbd\xb3\x4c\xf9\x0a\x62\xb6\xdd\x36\xa7\xdb\x45\x1f\x56\x8a\xc7\xc7\xb8\xaf\x21\xbe\x6a\x4f\xb2\x0b\x30\xcb\x95\x9a\xa1\x92\xa2\x8c\xd9\x95\xba\xe7\xa5\x6d\xf5\x4a\x2e\x41\x94\x42\x81\x0f\x31\x33\x30\x27\xcc\x7c\x11\x63\xf0\xb0\x2f\x77\xf3\xf5\x38\x78\x82\x8b\xe3\x1c\x74\x99\xf0\x00\x16\xa0\xc1\xda\x99\xc1\x45\x00\x72\x4d\x94\xbd\x05\x0a\x73\xd7\x2d\x3d\x5a\x03\x57\xb4\xfe\x1e\xaa\xaa\x81\xb8\x18\xff\x3a\xf6\xc4\xae\xef\x24\x57\x37\xa0\x78\x39\x07\x81\x3a\xb1\x31\x3b\xf7\xa9\x00\x23\x31\x69\x55\x93\xbd\x73\xbd\x7b\x8e\x54\x2e\xea\x94\x3d\x62\x91\xf0\x1b\x45\xa0\x5e\xca\xd5\xa8\x62\x61\xf8\xcd\xa2\xf6\x2d\x23\x5b\x2f\xd7\x48\xd4\xfb\x9b\xbd\x78\x79\x7d\x3b\xbd\xbb\xfd\xf8\xe1\xeb\xf4\xea\xfd\x9b\xf9\xec\xea\xfa\xcd\x2f\x07\x1d\xdc\x34\xb1\x17\x2f\xdf\x4c\x3f\xdd\x7e\xf9\x3a\xbb\xbd\xa9\xec\x43\x53\x85\xab\x48\x41\x01\x8a\x49\xbd\xc4\xc1\xd1\x4a\x76\x4b\xd4\xeb\xc4\x1a\x7b\xbf\x0f\x6b\xf9\x13\x5d\xf8\xe7\x72\x8a\x34\x33\x60\xa1\xba\x3c\x7c\x46\xc3\xb4\xa0\x0b\x1f\x53\x6d\xd2\x63\xc3\x2b\x58\xe1\x50\xfc\x6e\x30\x0d\x1b\x63\x29\x41\x25\xbb\xcb\xd1\xff\xfc\xab\xa8\x98\x74\x94\x95\xd3\xac\x6a\xa9\x66\x4d\x0f\xdb\x4b\xa6\x87\x2a\x64\xfd\x07\x43\xda\xaf\x78\x34\x14\x34\x5e\xbb\xbb\x66\xbb\x01\xf0\x67\x08\x2d\x1d\x10\xd7\xe7\x71\x03\x16\x2c\x05\x24\x14\xa8\x62\x76\x77\x3d\x3b\x1e\xff\xf5\xeb\xf3\x83\x09\x42\xf9\x3e\x83\x7d\x32\x85\x01\x9e\xc8\x7f\xbc\x02\x9c\x57\x79\x70\x01\x8c\x5f\xfd\x87\x05\xf0\xba\x55\x15\xa8\xf2\x14\xde\xbb\xe7\x4f\x67\xbf\xfb\xcf\xb7\x7a\xd0\x03\x1c\xa9\x73\xa9\x4b\x38\xea\xa9\x03\x6f\x01\x86\x8e\xbb\x82\xa1\x7f\xb3\xc7\x0f\x92\xd6\xd2\xd6\xdb\xbf\x21\x77\xc1\xf2\x74\x9f\x15\x6b\x70\x78\xff\xb8\xbb\x6b\x2a\xe6\x28\xbd\x3e\x70\x61\x76\x37\xe2\x02\x91\x2c\x19\x9e\x3d\x7f\x31\x3e\x24\x36\xe2\x49\x62\xc0\xda\xcb\xee\xdd\xd2\xe8\x1d\xd4\xcb\x8b\xf1\x78\xd2\xd3\x18\xb0\x98\x1b\x01\x51\x51\x8f\xd7\x65\x71\x1e\xd8\x34\x7a\x1b\x25\xd2\x5c\x36\x50\x5a\x69\x60\xbb\xab\x2e\x5f\x4a\x05\x97\x75\x2d\x46\x82\x0f\x85\x57\x30\xcf\x0c\x0c\x45\xbe\x25\x29\x7b\xc4\x74\x03\x65\xcf\x72\x03\xe5\xa9\x3d\xdd\x25\xe2\x7f\x7f\x33\xf8\xfd\xed\x8a\xdd\x69\xe2\xc3\x83\xf1\xc4\x58\x9c\x18\x8a\x53\x23\x71\x7c\x20\xea\xbd\x71\xab\x55\x19\x33\xf7\x4e\xfe\x61\x37\xca\x73\x2e\x0d\x9e\x13\x56\xd0\xc3\xbf\x51\x77\xb8\x01\x1d\xb3\x25\x57\xb6\xb1\xb4\x81\xc1\xb4\x77\x43\x12\x98\x54\xea\xea\x65\xfb\xd6\x70\x01\xb3\x70\x5d\x9d\x8f\x9b\x81\xad\xab\xf2\xfc\x82\x40\x9a\x51\x79\x23\x4d\xcc\xb6\x8f\xcf\x2a\x85\x05\x61\xba\x6b\xa5\x96\x4d\x0f\xfa\x18\xb0\xc4\x0d\x75\x7a\xec\xef\x00\x00\x00\xff\xff\xd6\x1f\x48\x81\x68\x0f\x00\x00"),
+			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x57\xdf\x6f\xdb\x36\x10\x7e\xf7\x5f\x41\x64\x1d\xd0\x3d\xc8\x3f\x9a\x3e\x64\x02\xf2\x90\x25\x5d\x37\xa0\x75\x8c\x38\x09\xd2\xa7\x82\xa6\xce\x36\x6b\x8a\x27\x90\x27\x25\xaa\x97\xff\x7d\xa0\x64\xc9\x94\x64\x39\xd9\x86\xd5\x4f\xc1\xdd\x77\xc7\x8f\xdf\xfd\xa0\x12\x04\xc1\xe0\x27\xf6\xf0\xf0\x10\xb2\x4f\xb8\xc1\x18\x49\x66\xc0\x6c\x02\x42\x2e\xa5\x60\x62\xcd\xf5\x0a\x06\x1b\xa9\xa3\x90\xdd\xa0\x82\xdf\xa4\x8e\xa4\x5e\x0d\x78\x22\xef\xc1\x58\x89\x3a\x64\x66\xc1\xc5\x90\xa7\xb4\x46\x23\xbf\x73\x92\xa8\x87\x9b\x33\x3b\x94\x38\xca\x26\x83\x18\x88\x47\x9c\x78\x38\x60\x4c\xf3\x18\x42\x06\x3a\xc3\x3c\x48\x8c\xcc\xa4\x82\x15\x44\x41\x62\x93\x9d\xd3\x26\x5c\x40\xc8\x12\x83\xdf\x40\x90\x40\x4d\x98\x9a\x81\x41\x05\x37\xb0\x74\x19\x4a\x22\x97\x2a\xb5\x04\xc6\xf1\xa9\xb3\x76\xf2\xf1\x44\x7e\x34\x98\x26\x47\xf8\x0d\x6c\xba\x70\x07\xd9\x70\x10\xec\x52\xcf\xc1\x64\x52\xc0\x85\x10\x98\x6a\x6a\x72\x3e\x4a\xd2\x09\xe9\x8b\xc2\x93\xc4\xba\xfb\x97\x69\xaf\x38\xc4\xa8\xe7\x40\x0d\x3d\x14\x5f\x80\xb2\xee\x2f\x47\x37\x69\x1e\xf3\x9a\x43\x5d\x9d\x5c\x78\x9a\x44\x9c\x60\x4e\x86\x13\xac\xf2\x32\x21\xe5\x09\x14\x35\x53\x52\xaf\xee\x0a\x40\x61\x37\xbe\xa5\x84\x32\x16\xf3\xa7\x3b\xcd\x33\x2e\x15\x5f\x28\x08\xd9\x64\xfc\xf3\x80\x31\x0b\x0a\x04\xa1\x29\x51\x31\x27\xb1\xfe\xe4\x51\x6e\x91\x26\x88\x13\x55\xe7\xf4\xef\x59\x40\xb5\x46\x2a\xc4\xaf\xa3\x99\xbb\x4e\x0c\xb4\x86\xb4\xe8\x16\x2b\x0c\x77\x9c\x4f\xc8\xa4\x70\xd2\x03\x4a\xd0\x50\xc8\x4e\xce\xc6\xe3\x77\xbd\x10\x4e\xeb\x90\x9d\x8c\x2c\x71\xb2\xa3\xbd\xaf\xc2\xab\xc6\x1d\x5a\xb7\x60\xac\x12\xd5\xfd\xb6\xdb\x80\x3d\x4a\x5a\xb3\xe1\x3d\x57\x29\xd8\xa1\xc6\x08\x2e\x96\x4b\xa9\x25\xe5\xec\xf9\xb9\xba\xdc\xce\xb2\xcf\xe9\xe3\xc2\xc1\x76\xcb\x08\xbf\xf0\x58\xb1\x21\xfb\x8b\x49\x1d\x81\x26\x36\x19\xef\x13\xb8\x73\x40\x47\xce\xd0\x77\x32\xa1\x02\x53\x0a\xb8\x8f\xf3\x8c\x3d\xa7\x9c\x1d\x3b\xc4\xf5\x11\x97\x1a\x4c\xad\x46\xc0\x04\xc6\x31\xd7\xd1\xfe\x2a\x01\x1b\x2d\xa4\x1e\x55\x3d\x57\xab\x66\x56\x9e\x86\x0e\xb6\x97\xb0\xb2\xd8\x75\x4a\x11\x3e\xea\x20\xe6\x9a\xaf\x60\x1f\x2c\x63\xbe\x82\x90\x6d\xb7\xd5\xed\x76\xd9\x87\x85\xe3\xf9\x39\xec\x7a\x88\xaf\xea\x9b\xec\x12\xcc\x52\xa5\x66\xa8\xa4\xc8\x43\x76\xa1\x1e\x79\x6e\x6b\xbf\x92\x4b\x10\xb9\x50\xe0\x53\x4c\x0c\xcc\x09\x13\xdf\xc4\x18\x3c\xed\xcb\x5d\xfd\x3a\x1a\xbc\xa0\x45\xbf\x06\x6d\x25\x3c\x82\x19\x68\xb0\x76\x66\x70\xd1\x20\xb9\x26\x4a\x3e\x02\x35\xcf\x2e\x5b\x7a\xb4\x06\xae\x68\xfd\xbd\xe9\x2a\x06\xe2\x6c\xfc\xeb\xd8\x33\xbb\xbe\x93\x5c\x5d\x81\xe2\xf9\x1c\x04\xea\xc8\x86\xec\xd4\x97\x02\x8c\xc4\xa8\x76\x4d\xf6\xc1\xe5\xee\xe9\xa9\x5c\xd0\x2a\x7b\xc0\x02\xe1\x37\x8a\x40\xbd\x94\xab\x51\xa1\xc2\xf0\x9b\x45\xed\x23\x03\x5b\x2e\xd7\x40\x94\xfb\x9b\xbd\x79\x7b\x79\x3d\xbd\xbd\xbe\xbb\xf9\x3a\xbd\xf8\xfc\x61\x3e\xbb\xb8\xfc\xf0\xcb\xc1\x00\x37\x4d\xec\xcd\xdb\x0f\xd3\xfb\xeb\x2f\x5f\x67\xd7\x57\x05\xbe\x09\x55\xb8\x0a\x14\x64\xa0\x98\xd4\x4b\x1c\xf4\x56\xb2\x5d\xa2\x4e\x27\x96\xdc\xbb\x7d\x58\xda\x5f\xe8\xc2\x3f\x97\x53\xa4\x99\x01\x0b\xc5\xe3\xe1\x2b\xda\x3c\x16\x74\xe6\x73\x2a\x21\x1d\x35\xbc\x82\x65\x8e\xc5\xef\x06\xe3\x66\x63\x2c\x25\xa8\x68\xf7\x38\xfa\x3f\xff\x29\xca\x26\x2d\x67\x11\x34\x2b\x5a\xaa\x5a\xd3\xc3\xfa\x91\xe9\xb0\x6a\xaa\xfe\x83\x29\xed\x57\x3c\x1a\x6a\x34\x5e\xbd\xbb\x66\xbb\x01\xf0\x32\x96\xc4\xdd\x24\x35\xa6\x1f\x09\x05\xaa\x90\xdd\x5e\xce\xfa\x13\xbd\x7f\x7f\x7a\x30\x93\x7d\x31\x95\x01\x1e\xc9\x7f\x3c\xd3\x2e\x2a\x3f\x38\xd1\xe3\x77\xff\x61\xa2\xdf\xd7\xae\x0c\x55\x1a\xc3\x67\xf7\x3d\xd3\x5a\xd8\xfe\xf7\x58\x39\xb9\x0d\x1e\xb1\x0b\x29\x6b\x32\xea\xb8\x1b\xd1\x02\x0c\xf5\x87\x82\xa1\x7f\xb3\x98\x0f\x8a\x56\xcb\xd6\x59\xa8\x4d\xed\x1a\xdb\xd0\xfd\xac\x58\x83\xe3\xfb\xc7\xed\x6d\x55\x31\x27\xe9\xe5\x81\x17\xb0\xbd\xe2\x16\x88\x64\xc9\xf0\xe4\xf5\x9b\xee\x29\xb2\x01\x8f\x22\x03\xd6\x9e\xb7\x1f\x8b\xca\xef\xa8\x9e\x9f\x8d\xc7\x93\x8e\xc7\x80\xc5\xd4\x08\x08\xb2\x72\x5e\xce\xb3\xd3\x06\xa6\xf2\xdb\x20\x92\xe6\xbc\xa2\x52\x5b\x1b\xd8\x5d\x75\xf9\x52\x2a\x38\x2f\x6b\x31\x12\x7c\x28\xbc\x82\x79\x30\x30\x14\xf8\x48\x52\xb6\x07\xba\x81\xbc\x83\xdc\x40\x7e\x6c\xf1\xb6\x85\xf8\xdf\x3f\x02\xfc\xfe\x76\xc5\x6e\x35\xf1\xe1\xc1\x78\x61\x2c\x8e\x0c\xc5\xb1\x91\xe8\x1f\x88\x72\x6f\x5c\x6b\x95\x87\xcc\x7d\xf8\xfe\xb0\x27\xe2\x35\xaf\x00\x4f\x09\x0b\xea\xcd\xff\x8b\x6e\x71\x03\x3a\x64\x4b\xae\x6c\x85\xb4\x0d\xc0\xb4\xf3\xe4\x11\x98\x58\xea\xe2\x53\xf5\xa3\xe1\x02\x66\xcd\x75\x75\x3a\xae\x06\xb6\xac\xca\xeb\x0b\x02\x71\x42\xf9\x95\x34\x21\xdb\x3e\xbf\xaa\x14\x16\x84\x69\xaf\x95\xd2\x36\x3d\x18\x63\xc0\x12\x37\xd4\xea\xb1\xbf\x03\x00\x00\xff\xff\x20\xa5\x2f\xc6\x39\x0f\x00\x00"),
 		},
 		"/charts/components/contour/templates/alerts-for-prometheus-operator-0.43.2.yaml": &vfsgen۰CompressedFileInfo{
 			name:             "alerts-for-prometheus-operator-0.43.2.yaml",
diff --git a/pkg/k8sutil/create.go b/pkg/k8sutil/create.go
index 32d5d60a..88b1eb64 100644
--- a/pkg/k8sutil/create.go
+++ b/pkg/k8sutil/create.go
@@ -28,8 +28,12 @@ import (
 	v1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/yaml"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
+	yamlutil "k8s.io/apimachinery/pkg/util/yaml"
 	corev1typed "k8s.io/client-go/kubernetes/typed/core/v1"
+	"sigs.k8s.io/yaml"
 
 	"github.com/kinvolk/lokomotive/internal"
 )
@@ -74,6 +78,7 @@ func (m manifest) Name() string {
 }
 
 // LoadManifests parses a map of Kubernetes manifests.
+// Deprecated: YAMLToObjectMetadata should be used instead.
 func LoadManifests(files map[string]string) ([]manifest, error) {
 	var manifests []manifest
 	for path, fileContent := range files {
@@ -90,7 +95,7 @@ func LoadManifests(files map[string]string) ([]manifest, error) {
 // parseManifests parses a YAML or JSON document that may contain one or more
 // kubernetes resources.
 func parseManifests(r io.Reader) ([]manifest, error) {
-	reader := yaml.NewYAMLReader(bufio.NewReader(r))
+	reader := yamlutil.NewYAMLReader(bufio.NewReader(r))
 	var manifests []manifest
 	for {
 		yamlManifest, err := reader.Read()
@@ -105,7 +110,7 @@ func parseManifests(r io.Reader) ([]manifest, error) {
 			continue
 		}
 
-		jsonManifest, err := yaml.ToJSON(yamlManifest)
+		jsonManifest, err := yamlutil.ToJSON(yamlManifest)
 		if err != nil {
 			return nil, fmt.Errorf("invalid manifest: %w", err)
 		}
@@ -227,3 +232,100 @@ func createNamespace(ns Namespace, nsclient corev1typed.NamespaceInterface) erro
 func ListNamespaces(nsclient corev1typed.NamespaceInterface) (*v1.NamespaceList, error) {
 	return nsclient.List(context.TODO(), metav1.ListOptions{})
 }
+
+// ObjectMetadata uniquely identifies any object in the list of YAML manifests.
+type ObjectMetadata struct {
+	Version string
+	Kind    string
+	Name    string
+}
+
+// YAMLToUnstructured accepts a Kubernetes manifest in YAML format and returns an object of type
+// `unstructured.Unstructured`. This object has many methods that can be used by the consumer to
+// extract metadata from the Kubernetes manifest.
+func YAMLToUnstructured(yamlObj []byte) (*unstructured.Unstructured, error) {
+	u := &unstructured.Unstructured{}
+
+	var gvk *schema.GroupVersionKind
+
+	// Decode YAML into `unstructured.Unstructured`.
+	dec := yamlserializer.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
+	if _, _, err := dec.Decode(yamlObj, gvk, u); err != nil {
+		return nil, fmt.Errorf("decoding: %w", err)
+	}
+
+	return u, nil
+}
+
+// SplitYAMLDocuments separates a YAML file with multiple YAML documents separated by `---` into
+// separate YAML files and returns a list.
+func SplitYAMLDocuments(yamlObj string) ([]string, error) {
+	var ret []string
+
+	reader := yamlutil.NewYAMLReader(bufio.NewReader(strings.NewReader(yamlObj)))
+
+	for {
+		// Read the YAML document delimited by `---`.
+		yamlManifest, err := reader.Read()
+		if err == io.EOF {
+			break
+		}
+
+		if err != nil {
+			return nil, fmt.Errorf("reading the YAML: %w", err)
+		}
+
+		yamlManifest, err = removeYAMLComments(yamlManifest)
+		if err != nil {
+			return nil, fmt.Errorf("removing YAML comments: %w", err)
+		}
+
+		// Check if the YAML is empty.
+		if string(yamlManifest) == "null\n" {
+			continue
+		}
+
+		ret = append(ret, string(yamlManifest))
+	}
+
+	return ret, nil
+}
+
+// YAMLToObjectMetadata extracts Kubernetes metadata from any YAML manifest and return an
+// ObjectMetadata.
+func YAMLToObjectMetadata(yamlObj string) (ObjectMetadata, error) {
+	u, err := YAMLToUnstructured([]byte(yamlObj))
+	if err != nil {
+		return ObjectMetadata{}, fmt.Errorf("converting YAML to Unstructured: %w", err)
+	}
+
+	if u.GetAPIVersion() == "" {
+		return ObjectMetadata{}, fmt.Errorf("invalid configuration, no 'APIVersion' specified:\n%s", yamlObj)
+	}
+
+	if u.GetName() == "" {
+		return ObjectMetadata{}, fmt.Errorf("invalid configuration, no 'Name' specified:\n%s", yamlObj)
+	}
+
+	return ObjectMetadata{
+		Name:    u.GetName(),
+		Kind:    u.GetKind(),
+		Version: u.GetAPIVersion(),
+	}, nil
+}
+
+// removeYAMLComments converts YAML to JSON and back again, this removes the comments in the YAML
+// and any extra whitespaces spaces.
+func removeYAMLComments(yamlObj []byte) ([]byte, error) {
+	jsonObj, err := yaml.YAMLToJSON(yamlObj)
+	if err != nil {
+		return nil, fmt.Errorf("converting YAML to JSON: %w", err)
+	}
+
+	yamlObj, err = yaml.JSONToYAML(jsonObj)
+	if err != nil {
+		return nil, fmt.Errorf("converting JSON to YAML: %w", err)
+	}
+
+	return yamlObj, nil
+}


Test patch:
diff --git a/pkg/components/aws-ebs-csi-driver/component_test.go b/pkg/components/aws-ebs-csi-driver/component_test.go
index c33cad08..019b7148 100644
--- a/pkg/components/aws-ebs-csi-driver/component_test.go
+++ b/pkg/components/aws-ebs-csi-driver/component_test.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/kinvolk/lokomotive/pkg/components/internal/testutil"
 	"github.com/kinvolk/lokomotive/pkg/components/util"
+	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 )
 
 func TestStorageClassEmptyConfig(t *testing.T) {
@@ -144,16 +145,18 @@ func TestConversion(t *testing.T) { //nolint:funlen
 	testCases := []struct {
 		name                 string
 		inputConfig          string
-		expectedManifestName string
+		expectedManifestName k8sutil.ObjectMetadata
 		expected             string
 		jsonPath             string
 	}{
 		{
-			name:                 "no_tolerations_node",
-			inputConfig:          `component "aws-ebs-csi-driver" {}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/node.yaml",
-			jsonPath:             "{.spec.template.spec.tolerations[0].operator}",
-			expected:             "Exists",
+			name:        "no_tolerations_node",
+			inputConfig: `component "aws-ebs-csi-driver" {}`,
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "apps/v1", Kind: "DaemonSet", Name: "ebs-csi-node",
+			},
+			jsonPath: "{.spec.template.spec.tolerations[0].operator}",
+			expected: "Exists",
 		},
 		{
 			name: "tolerations_node",
@@ -165,16 +168,20 @@ func TestConversion(t *testing.T) { //nolint:funlen
 								effect   = "NoSchedule"
 							}
 						}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/node.yaml",
-			jsonPath:             "{.spec.template.spec.tolerations[0].value}",
-			expected:             "awesome",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "apps/v1", Kind: "DaemonSet", Name: "ebs-csi-node",
+			},
+			jsonPath: "{.spec.template.spec.tolerations[0].value}",
+			expected: "awesome",
 		},
 		{
-			name:                 "no_tolerations_csi_controller",
-			inputConfig:          `component "aws-ebs-csi-driver" {}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/controller.yaml",
-			jsonPath:             "{.spec.template.spec.tolerations[0].operator}",
-			expected:             "Exists",
+			name:        "no_tolerations_csi_controller",
+			inputConfig: `component "aws-ebs-csi-driver" {}`,
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "apps/v1", Kind: "Deployment", Name: "ebs-csi-controller",
+			},
+			jsonPath: "{.spec.template.spec.tolerations[0].operator}",
+			expected: "Exists",
 		},
 		{
 			name: "tolerations_csi_controller",
@@ -186,16 +193,20 @@ func TestConversion(t *testing.T) { //nolint:funlen
 								effect   = "NoSchedule"
 							}
 						}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/controller.yaml",
-			jsonPath:             "{.spec.template.spec.tolerations[0].key}",
-			expected:             "lokomotive.io",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "apps/v1", Kind: "Deployment", Name: "ebs-csi-controller",
+			},
+			jsonPath: "{.spec.template.spec.tolerations[0].key}",
+			expected: "lokomotive.io",
 		},
 		{
-			name:                 "no_tolerations_snapshot_controller",
-			inputConfig:          `component "aws-ebs-csi-driver" {}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/statefulset.yaml",
-			jsonPath:             "{.spec.template.spec.tolerations[0].operator}",
-			expected:             "Exists",
+			name:        "no_tolerations_snapshot_controller",
+			inputConfig: `component "aws-ebs-csi-driver" {}`,
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "apps/v1", Kind: "StatefulSet", Name: "ebs-snapshot-controller",
+			},
+			jsonPath: "{.spec.template.spec.tolerations[0].operator}",
+			expected: "Exists",
 		},
 		{
 			name: "tolerations_snapshot_controller",
@@ -207,9 +218,11 @@ func TestConversion(t *testing.T) { //nolint:funlen
 								effect   = "NoSchedule"
 							}
 						}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/statefulset.yaml",
-			jsonPath:             "{.spec.template.spec.tolerations[0].effect}",
-			expected:             "NoSchedule",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "apps/v1", Kind: "StatefulSet", Name: "ebs-snapshot-controller",
+			},
+			jsonPath: "{.spec.template.spec.tolerations[0].effect}",
+			expected: "NoSchedule",
 		},
 		{
 			name: "affinity_csi_controller",
@@ -220,7 +233,9 @@ func TestConversion(t *testing.T) { //nolint:funlen
 								values   = ["storage"]
 							}
 						}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/controller.yaml",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "apps/v1", Kind: "Deployment", Name: "ebs-csi-controller",
+			},
 			jsonPath: "{.spec.template.spec.affinity.nodeAffinity." +
 				"requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key}",
 			expected: "lokomotive.io/role",
@@ -234,26 +249,32 @@ func TestConversion(t *testing.T) { //nolint:funlen
 								values   = ["storage"]
 							}
 						}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/statefulset.yaml",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "apps/v1", Kind: "StatefulSet", Name: "ebs-snapshot-controller",
+			},
 			jsonPath: "{.spec.template.spec.affinity.nodeAffinity." +
 				"requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]}",
 			expected: "storage",
 		},
 		{
-			name:                 "storage_class",
-			inputConfig:          `component "aws-ebs-csi-driver" {}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/storageclass.yaml",
-			jsonPath:             "{.reclaimPolicy}",
-			expected:             "Retain",
+			name:        "storage_class",
+			inputConfig: `component "aws-ebs-csi-driver" {}`,
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "storage.k8s.io/v1", Kind: "StorageClass", Name: "ebs-sc",
+			},
+			jsonPath: "{.reclaimPolicy}",
+			expected: "Retain",
 		},
 		{
 			name: "default_storage_class",
 			inputConfig: `component "aws-ebs-csi-driver" {
 							enable_default_storage_class = true
 						}`,
-			expectedManifestName: "aws-ebs-csi-driver/templates/storageclass.yaml",
-			jsonPath:             `{.metadata.annotations.storageclass\.kubernetes\.io\/is\-default\-class}`,
-			expected:             "true",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "storage.k8s.io/v1", Kind: "StorageClass", Name: "ebs-sc",
+			},
+			jsonPath: `{.metadata.annotations.storageclass\.kubernetes\.io\/is\-default\-class}`,
+			expected: "true",
 		},
 	}
 
diff --git a/pkg/components/contour/component_test.go b/pkg/components/contour/component_test.go
index 996483ee..02174830 100644
--- a/pkg/components/contour/component_test.go
+++ b/pkg/components/contour/component_test.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/kinvolk/lokomotive/pkg/components/internal/testutil"
 	"github.com/kinvolk/lokomotive/pkg/components/util"
+	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 )
 
 //nolint:funlen
@@ -96,7 +97,7 @@ func TestConversion(t *testing.T) {
 	testCases := []struct {
 		name                 string
 		inputConfig          string
-		expectedManifestName string
+		expectedManifestName k8sutil.ObjectMetadata
 		expected             string
 		jsonPath             string
 	}{
@@ -108,9 +109,11 @@ func TestConversion(t *testing.T) {
 					metrics_scrape_interval = "10s"
 				}
 			}`,
-			expectedManifestName: "contour/templates/service-monitor.yaml",
-			jsonPath:             "{.spec.endpoints[0].interval}",
-			expected:             "10s",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "monitoring.coreos.com/v1", Kind: "ServiceMonitor", Name: "envoy",
+			},
+			jsonPath: "{.spec.endpoints[0].interval}",
+			expected: "10s",
 		},
 	}
 
@@ -127,3 +130,18 @@ func TestConversion(t *testing.T) {
 		})
 	}
 }
+
+func TestEnvoyHostPort(t *testing.T) {
+	t.Parallel()
+
+	componentCfg := `component "contour" {}`
+	expectedManifestName := k8sutil.ObjectMetadata{Version: "apps/v1", Kind: "DaemonSet", Name: "envoy"}
+	jsonPath := "{.spec.template.spec.containers[1].ports[0].hostPort}"
+	errExpected := "hostPort is not found"
+
+	component := NewConfig()
+	m := testutil.RenderManifests(t, component, Name, componentCfg)
+	gotConfig := testutil.ConfigFromMap(t, m, expectedManifestName)
+
+	testutil.JSONPathExists(t, gotConfig, jsonPath, errExpected)
+}
diff --git a/pkg/components/internal/testutil/common.go b/pkg/components/internal/testutil/common.go
index 1cb0b3d8..7284a282 100644
--- a/pkg/components/internal/testutil/common.go
+++ b/pkg/components/internal/testutil/common.go
@@ -21,17 +21,33 @@ import (
 
 	"github.com/kinvolk/lokomotive/pkg/components"
 	"github.com/kinvolk/lokomotive/pkg/components/util"
+	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 )
 
-// ConfigFromMap takes a map and a key and returns the value associated with the key. If the key
-// does not exist in that map, the function fails.
-func ConfigFromMap(t *testing.T, m map[string]string, k string) string {
-	ret, ok := m[k]
-	if !ok {
-		t.Fatalf("Config not found with filename: %q", k)
+// ConfigFromMap takes a map and a key. The function returns the YAML object associated with the
+// key. If the key does not exist in that map, the function fails.
+func ConfigFromMap(t *testing.T, m map[string]string, key k8sutil.ObjectMetadata) string {
+	for _, v := range m {
+		splittedYAML, err := k8sutil.SplitYAMLDocuments(v)
+		if err != nil {
+			t.Fatalf("Splitting YAML doc separated by '---': %v", err)
+		}
+
+		for _, val := range splittedYAML {
+			obj, err := k8sutil.YAMLToObjectMetadata(val)
+			if err != nil {
+				t.Fatalf("Converting YAML to ObjectMetadata: %v", err)
+			}
+
+			if obj == key {
+				return val
+			}
+		}
 	}
 
-	return ret
+	t.Fatalf("Given object not found: %+v", key)
+
+	return ""
 }
 
 // RenderManifests converts a component into YAML manifests.
diff --git a/pkg/components/internal/testutil/jsonpath.go b/pkg/components/internal/testutil/jsonpath.go
index 9e3fa2c8..21ee130c 100644
--- a/pkg/components/internal/testutil/jsonpath.go
+++ b/pkg/components/internal/testutil/jsonpath.go
@@ -15,70 +15,65 @@
 package testutil
 
 import (
+	"fmt"
 	"reflect"
+	"strings"
 	"testing"
 
+	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
 	"k8s.io/client-go/util/jsonpath"
 )
 
-// unstructredObj accepts a Kubernetes manifest in YAML format and returns an object of type
-// `unstructured.Unstructured`. This object has many methods that can be used by the consumer to
-// extract metadata from the Kubernetes manifest.
-func unstructredObj(t *testing.T, yamlObj string) *unstructured.Unstructured {
-	u := &unstructured.Unstructured{}
-
-	// Decode YAML into `unstructured.Unstructured`.
-	dec := yamlserializer.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
-	if _, _, err := dec.Decode([]byte(yamlObj), nil, u); err != nil {
-		t.Fatalf("Converting config to unstructured.Unstructured: %v", err)
-	}
-
-	return u
-}
-
 // valFromObject takes a JSON path as a string and an object of type `unstructured.Unstructured`.
 // This function returns an object of type `reflect.Value` at that JSON path.
-func valFromObject(t *testing.T, jp string, obj *unstructured.Unstructured) reflect.Value {
+func valFromObject(jp string, obj *unstructured.Unstructured) (reflect.Value, error) {
 	jPath := jsonpath.New("parse")
 	if err := jPath.Parse(jp); err != nil {
-		t.Fatalf("Parsing JSONPath: %v", err)
+		return reflect.Value{}, fmt.Errorf("parsing JSONPath: %w", err)
 	}
 
 	v, err := jPath.FindResults(obj.Object)
 	if err != nil {
-		t.Fatalf("Finding results using JSONPath in the YAML file: %v", err)
+		return reflect.Value{}, fmt.Errorf("finding results using JSONPath in the YAML file: %w", err)
 	}
 
 	if len(v) == 0 || len(v[0]) == 0 {
-		t.Fatalf("No result found")
+		return reflect.Value{}, nil
 	}
 
-	return v[0][0]
+	return v[0][0], nil
 }
 
 // jsonPathValue extracts an object at a JSON path from a YAML config, and returns an interface
 // object.
-func jsonPathValue(t *testing.T, yamlConfig string, jsonPath string) interface{} {
-	u := unstructredObj(t, yamlConfig)
-	got := valFromObject(t, jsonPath, u)
+func jsonPathValue(yamlConfig string, jsonPath string) (interface{}, error) {
+	u, err := k8sutil.YAMLToUnstructured([]byte(yamlConfig))
+	if err != nil {
+		return nil, fmt.Errorf("YAML to unstructured object: %w", err)
+	}
+
+	got, err := valFromObject(jsonPath, u)
+	if err != nil {
+		return nil, fmt.Errorf("JSON path value in YAML: %w", err)
+	}
 
 	switch got.Kind() { //nolint:exhaustive
 	case reflect.Interface:
 		// TODO: Add type switch here for concrete types.
-		return got.Interface()
+		return got.Interface(), nil
 	default:
-		t.Fatalf("Extracted object has an unknown type: %v", got.Kind())
+		return nil, fmt.Errorf("extracted object has an unknown type: %v", got.Kind())
 	}
-
-	return nil
 }
 
 // MatchJSONPathStringValue is a helper function for component unit tests. It compares the string at
 // a JSON path in a YAML config to the expected string.
 func MatchJSONPathStringValue(t *testing.T, yamlConfig string, jsonPath string, expected string) {
-	obj := jsonPathValue(t, yamlConfig, jsonPath)
+	obj, err := jsonPathValue(yamlConfig, jsonPath)
+	if err != nil {
+		t.Fatalf("Extracting JSON path value: %v", err)
+	}
 
 	got, ok := obj.(string)
 	if !ok {
@@ -93,7 +88,10 @@ func MatchJSONPathStringValue(t *testing.T, yamlConfig string, jsonPath string,
 // MatchJSONPathInt64Value is a helper function for component unit tests. It compares the integer at
 // a JSON path in a YAML config to the expected integer.
 func MatchJSONPathInt64Value(t *testing.T, yamlConfig string, jsonPath string, expected int64) {
-	obj := jsonPathValue(t, yamlConfig, jsonPath)
+	obj, err := jsonPathValue(yamlConfig, jsonPath)
+	if err != nil {
+		t.Fatalf("Extracting JSON path value: %v", err)
+	}
 
 	got, ok := obj.(int64)
 	if !ok {
@@ -104,3 +102,22 @@ func MatchJSONPathInt64Value(t *testing.T, yamlConfig string, jsonPath string, e
 		t.Fatalf("Expected: %d, Got: %d", expected, got)
 	}
 }
+
+// JSONPathExists checks if the given YAML config has an object at the given JSON path, also provide
+// what error to expect.
+func JSONPathExists(t *testing.T, yamlConfig string, jsonPath string, errExp string) {
+	_, err := jsonPathValue(yamlConfig, jsonPath)
+	if err != nil && errExp == "" {
+		t.Fatalf("Error not expected and failed with: %v", err)
+	}
+
+	if err == nil && errExp != "" {
+		t.Fatalf("Expected error %q but got none", errExp)
+	}
+
+	if err != nil && !strings.Contains(err.Error(), errExp) {
+		t.Fatalf("Extracting JSON path value, expected error: %v to contain: %q", err, errExp)
+	}
+
+	t.Logf("Failed with error: %v", err)
+}
diff --git a/pkg/components/istio-operator/component_test.go b/pkg/components/istio-operator/component_test.go
index 1b5db9d6..2677def2 100644
--- a/pkg/components/istio-operator/component_test.go
+++ b/pkg/components/istio-operator/component_test.go
@@ -18,31 +18,36 @@ import (
 	"testing"
 
 	"github.com/kinvolk/lokomotive/pkg/components/internal/testutil"
+	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 )
 
 func TestConversion(t *testing.T) {
 	testCases := []struct {
 		name                 string
 		inputConfig          string
-		expectedManifestName string
+		expectedManifestName k8sutil.ObjectMetadata
 		expected             string
 		jsonPath             string
 	}{
 		{
-			name:                 "default profile",
-			inputConfig:          `component "experimental-istio-operator" {}`,
-			expectedManifestName: "istio-operator/templates/istio-operator-cr.yaml",
-			jsonPath:             "{.spec.profile}",
-			expected:             "minimal",
+			name:        "default profile",
+			inputConfig: `component "experimental-istio-operator" {}`,
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "install.istio.io/v1alpha1", Kind: "IstioOperator", Name: "istiocontrolplane",
+			},
+			jsonPath: "{.spec.profile}",
+			expected: "minimal",
 		},
 		{
 			name: "demo profile",
 			inputConfig: `component "experimental-istio-operator" {
 				profile = "demo"
 			}`,
-			expectedManifestName: "istio-operator/templates/istio-operator-cr.yaml",
-			jsonPath:             "{.spec.profile}",
-			expected:             "demo",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "install.istio.io/v1alpha1", Kind: "IstioOperator", Name: "istiocontrolplane",
+			},
+			jsonPath: "{.spec.profile}",
+			expected: "demo",
 		},
 	}
 
@@ -67,5 +72,7 @@ func TestVerifyServiceMonitor(t *testing.T) {
 
 	component := NewConfig()
 	m := testutil.RenderManifests(t, component, Name, inputConfig)
-	testutil.ConfigFromMap(t, m, "istio-operator/templates/service-monitor.yaml")
+	testutil.ConfigFromMap(t, m, k8sutil.ObjectMetadata{
+		Version: "monitoring.coreos.com/v1", Kind: "ServiceMonitor", Name: "istio-operator",
+	})
 }
diff --git a/pkg/components/node-problem-detector/component_test.go b/pkg/components/node-problem-detector/component_test.go
index b98ce532..fc09b3df 100644
--- a/pkg/components/node-problem-detector/component_test.go
+++ b/pkg/components/node-problem-detector/component_test.go
@@ -21,6 +21,7 @@ import (
 	"github.com/kinvolk/lokomotive/pkg/components/internal/testutil"
 	nodeproblemdetector "github.com/kinvolk/lokomotive/pkg/components/node-problem-detector"
 	"github.com/kinvolk/lokomotive/pkg/components/util"
+	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 )
 
 const name = "node-problem-detector"
@@ -92,7 +93,9 @@ component "node-problem-detector" {
 	jsonPath := "{.data.custom-monitor-0\\.json}"
 	expected := "testdata"
 
-	gotConfig := testutil.ConfigFromMap(t, m, "node-problem-detector/templates/custom-config-configmap.yaml")
+	gotConfig := testutil.ConfigFromMap(t, m, k8sutil.ObjectMetadata{
+		Name: "node-problem-detector-custom-config", Kind: "ConfigMap", Version: "v1",
+	})
 
 	testutil.MatchJSONPathStringValue(t, gotConfig, jsonPath, expected)
 }
diff --git a/pkg/components/prometheus-operator/component_test.go b/pkg/components/prometheus-operator/component_test.go
index edc918f7..d5dced9a 100644
--- a/pkg/components/prometheus-operator/component_test.go
+++ b/pkg/components/prometheus-operator/component_test.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/kinvolk/lokomotive/pkg/components/internal/testutil"
 	"github.com/kinvolk/lokomotive/pkg/components/util"
+	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 )
 
 //nolint:funlen
@@ -133,7 +134,7 @@ func TestConversion(t *testing.T) {
 	testCases := []struct {
 		name                 string
 		inputConfig          string
-		expectedManifestName string
+		expectedManifestName k8sutil.ObjectMetadata
 		expected             string
 		jsonPath             string
 	}{
@@ -146,9 +147,11 @@ component "prometheus-operator" {
   }
 }
 `,
-			expectedManifestName: "kube-prometheus-stack/templates/prometheus/prometheus.yaml",
-			expected:             "https://prometheus.externalurl.net",
-			jsonPath:             "{.spec.externalUrl}",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "monitoring.coreos.com/v1", Kind: "Prometheus", Name: "prometheus-operator-kube-p-prometheus",
+			},
+			expected: "https://prometheus.externalurl.net",
+			jsonPath: "{.spec.externalUrl}",
 		},
 		{
 			name: "no external_url param",
@@ -163,9 +166,11 @@ component "prometheus-operator" {
 		  }
 		}
 		`,
-			expectedManifestName: "kube-prometheus-stack/templates/prometheus/prometheus.yaml",
-			expected:             "https://prometheus.mydomain.net",
-			jsonPath:             "{.spec.externalUrl}",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "monitoring.coreos.com/v1", Kind: "Prometheus", Name: "prometheus-operator-kube-p-prometheus",
+			},
+			expected: "https://prometheus.mydomain.net",
+			jsonPath: "{.spec.externalUrl}",
 		},
 		{
 			name: "ingress creation for prometheus",
@@ -180,14 +185,18 @@ component "prometheus-operator" {
 		  }
 		}
 		`,
-			expectedManifestName: "kube-prometheus-stack/templates/prometheus/ingress.yaml",
-			expected:             "prometheus.mydomain.net",
-			jsonPath:             "{.spec.rules[0].host}",
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "networking.k8s.io/v1beta1", Kind: "Ingress", Name: "prometheus-operator-kube-p-prometheus",
+			},
+			expected: "prometheus.mydomain.net",
+			jsonPath: "{.spec.rules[0].host}",
 		},
 		{
-			name:                 "verify foldersFromFilesStructure in configmap",
-			inputConfig:          `component "prometheus-operator" {}`,
-			expectedManifestName: "kube-prometheus-stack/charts/grafana/templates/configmap-dashboard-provider.yaml",
+			name:        "verify foldersFromFilesStructure in configmap",
+			inputConfig: `component "prometheus-operator" {}`,
+			expectedManifestName: k8sutil.ObjectMetadata{
+				Version: "v1", Kind: "ConfigMap", Name: "prometheus-operator-grafana-config-dashboards",
+			},
 			expected: `apiVersion: 1
 providers:
 - name: 'sidecarProvider'
diff --git a/pkg/components/velero/component_test.go b/pkg/components/velero/component_test.go
index 47e8ca85..e9da151a 100644
--- a/pkg/components/velero/component_test.go
+++ b/pkg/components/velero/component_test.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/kinvolk/lokomotive/pkg/components/internal/testutil"
 	"github.com/kinvolk/lokomotive/pkg/components/util"
+	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 )
 
 func TestEmptyConfig(t *testing.T) {
@@ -210,6 +211,7 @@ component "velero" {
       provider = "aws"
       region   = "myregion"
     }
+
     tolerations {
       key                = "TestResticTolerationKey"
       value              = "TestResticTolerationValue"
@@ -235,15 +237,16 @@ component "velero" {
 	m := testutil.RenderManifests(t, component, Name, configHCL)
 	jsonPath := "{.spec.template.spec.tolerations[0].key}"
 	expected := "TestResticTolerationKey"
-
-	gotConfig := testutil.ConfigFromMap(t, m, "velero/templates/restic-daemonset.yaml")
+	resticDS := k8sutil.ObjectMetadata{
+		Version: "apps/v1", Kind: "DaemonSet", Name: "restic",
+	}
+	gotConfig := testutil.ConfigFromMap(t, m, resticDS)
 
 	testutil.MatchJSONPathStringValue(t, gotConfig, jsonPath, expected)
 
 	jsonPath = "{.spec.template.spec.tolerations[0].tolerationSeconds}"
 	expectedNumber := int64(1)
-
-	gotConfig = testutil.ConfigFromMap(t, m, "velero/templates/restic-daemonset.yaml")
+	gotConfig = testutil.ConfigFromMap(t, m, resticDS)
 
 	testutil.MatchJSONPathInt64Value(t, gotConfig, jsonPath, expectedNumber)
 }
diff --git a/pkg/k8sutil/create_test.go b/pkg/k8sutil/create_test.go
index dbd2ef42..56c93aff 100644
--- a/pkg/k8sutil/create_test.go
+++ b/pkg/k8sutil/create_test.go
@@ -21,8 +21,11 @@ import (
 
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/client-go/kubernetes/fake"
 
+	"github.com/google/go-cmp/cmp"
+
 	"github.com/kinvolk/lokomotive/internal"
 )
 
@@ -287,3 +290,200 @@ func TestCreateOrUpdateNamespaceUpdateSuccess(t *testing.T) {
 		t.Fatalf("expected %q, got: %q", name, mockns.ObjectMeta.Labels[internal.NamespaceLabelKey])
 	}
 }
+
+func TestYAMLToUnstructured(t *testing.T) {
+	tests := []struct {
+		name    string
+		yamlObj []byte
+		want    *unstructured.Unstructured
+		wantErr bool
+	}{
+		{
+			name: "Valid_Kubernetes_object",
+			yamlObj: []byte(`apiVersion: v1
+kind: Namespace
+metadata:
+  name: test`),
+			want: &unstructured.Unstructured{
+				Object: map[string]interface{}{
+					"apiVersion": "v1",
+					"kind":       "Namespace",
+					"metadata":   map[string]interface{}{"name": "test"},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "Invalid_Kubernetes_object",
+			yamlObj: []byte(`foobar`),
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			got, err := YAMLToUnstructured(tt.yamlObj)
+			if err != nil && !tt.wantErr || err == nil && tt.wantErr {
+				t.Fatalf("got error: %v\nwantErr: %v", err, tt.wantErr)
+			}
+
+			if err != nil && tt.wantErr {
+				t.Logf("successfully failed with error: %v", err)
+
+				return
+			}
+
+			if diff := cmp.Diff(got, tt.want); diff != "" {
+				t.Fatalf("-want +got)\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestSplitYAMLDocuments(t *testing.T) { //nolint:funlen
+	tests := []struct {
+		name    string
+		yamlObj string
+		want    []string
+		wantErr bool
+	}{
+		{
+			name: "valid_YAML_files",
+			yamlObj: `
+foo
+---
+bar`,
+			want:    []string{"foo\n", "bar\n"},
+			wantErr: false,
+		},
+		{
+			name: "valid_YAML_with_comments",
+			yamlObj: `# This is comment 1
+foo
+---
+# This is comment 2
+bar`,
+			want:    []string{"foo\n", "bar\n"},
+			wantErr: false,
+		},
+		{
+			name: "invalid_YAML_1",
+			yamlObj: "	foobar",
+			wantErr: true,
+		},
+		{
+			name:    "invalid_YAML_2",
+			yamlObj: "foo: bar:",
+			wantErr: true,
+		},
+
+		{
+			name: "empty_YAML_document",
+			yamlObj: `# This is comment 1
+foo
+---
+# This is empty doc
+---
+# This is comment 2
+bar
+`,
+			want:    []string{"foo\n", "bar\n"},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			got, err := SplitYAMLDocuments(tt.yamlObj)
+			if err != nil && !tt.wantErr || err == nil && tt.wantErr {
+				t.Fatalf("got error: %v\nwantErr: %v", err, tt.wantErr)
+			}
+
+			if err != nil && tt.wantErr {
+				t.Logf("successfully failed with error: %v", err)
+
+				return
+			}
+
+			if diff := cmp.Diff(got, tt.want); diff != "" {
+				t.Fatalf("-want +got)\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestYAMLToObjectMetadata(t *testing.T) { //nolint:funlen
+	tests := []struct {
+		name    string
+		yamlObj string
+		want    ObjectMetadata
+		wantErr bool
+	}{
+		{
+			name: "Valid object",
+			yamlObj: `apiVersion: v1
+kind: Namespace
+metadata:
+  name: test
+`,
+			want: ObjectMetadata{
+				Name:    "test",
+				Kind:    "Namespace",
+				Version: "v1",
+			},
+		},
+		{
+			name:    "Invalid_Kubernetes_object",
+			yamlObj: `foobar`,
+			wantErr: true,
+		},
+		{
+			name: "No_name_Kubernetes_object",
+			yamlObj: `apiVersion: v1
+kind: Namespace`,
+			wantErr: true,
+		},
+		{
+			name: "Kubernetes_object_with_no_kind",
+			yamlObj: `apiVersion: v1
+kind: ""
+metadata:
+  name: test`,
+			wantErr: true,
+		},
+		{
+			name: "Kubernetes_object_with_no_apiversion",
+			yamlObj: `apiVersion: ""
+kind: Namespace
+metadata:
+  name: test`,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			got, err := YAMLToObjectMetadata(tt.yamlObj)
+			if err != nil && !tt.wantErr || err == nil && tt.wantErr {
+				t.Fatalf("got error: %v\nwantErr: %v", err, tt.wantErr)
+			}
+
+			if err != nil && tt.wantErr {
+				t.Logf("successfully failed with error: %v", err)
+
+				return
+			}
+
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("got: %#v\nwant: %#v", got, tt.want)
+			}
+
+			if diff := cmp.Diff(got, tt.want); diff != "" {
+				t.Fatalf("-want +got)\n%s", diff)
+			}
+		})
+	}
+}