github.com/tilt-dev/tilt@v0.36.0/internal/tiltfile/helm_test.go (about) 1 //go:build !skiplargetiltfiletests 2 // +build !skiplargetiltfiletests 3 4 // On windows, running Helm can take ~0.5 seconds, 5 // which starts to blow up test times. 6 7 package tiltfile 8 9 import ( 10 "testing" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "github.com/tilt-dev/tilt/internal/k8s" 16 "github.com/tilt-dev/tilt/internal/tiltfile/testdata" 17 ) 18 19 func TestHelm(t *testing.T) { 20 f := newFixture(t) 21 22 f.setupHelm() 23 24 f.file("Tiltfile", ` 25 yml = helm('helm') 26 k8s_yaml(yml) 27 `) 28 29 f.load() 30 31 f.assertNextManifestUnresourced("chart-helloworld-chart") 32 f.assertConfigFiles( 33 "Tiltfile", 34 ".tiltignore", 35 "helm", 36 ) 37 } 38 39 func TestHelmArgs(t *testing.T) { 40 f := newFixture(t) 41 42 f.setupHelm() 43 44 f.file("Tiltfile", ` 45 yml = helm('./helm', name='rose-quartz', namespace='garnet', values=['./dev/helm/values-dev.yaml']) 46 k8s_yaml(yml) 47 `) 48 49 f.load() 50 51 m := f.assertNextManifestUnresourced("rose-quartz-helloworld-chart") 52 yaml := m.K8sTarget().YAML 53 assert.Contains(t, yaml, "release: rose-quartz") 54 assert.Contains(t, yaml, "namespace: garnet") 55 assert.Contains(t, yaml, "namespaceLabel: garnet") 56 assert.Contains(t, yaml, "name: nginx-dev") 57 58 entities, err := k8s.ParseYAMLFromString(yaml) 59 require.NoError(t, err) 60 61 names := k8s.UniqueNames(entities, 2) 62 expectedNames := []string{"rose-quartz-helloworld-chart:service"} 63 assert.ElementsMatch(t, expectedNames, names) 64 65 f.assertConfigFiles("./helm/", "./dev/helm/values-dev.yaml", ".tiltignore", "Tiltfile") 66 } 67 68 func TestHelmNamespaceFlagDoesNotInsertNSEntityIfNSInChart(t *testing.T) { 69 f := newFixture(t) 70 71 f.setupHelm() 72 73 valuesWithNamespace := ` 74 namespace: 75 enabled: true 76 name: foobarbaz` 77 f.file("helm/extra_values.yaml", valuesWithNamespace) 78 79 f.file("Tiltfile", ` 80 yml = helm('./helm', name='rose-quartz', namespace="foobarbaz", values=['./helm/extra_values.yaml']) 81 k8s_yaml(yml) 82 `) 83 84 f.load() 85 86 m := f.assertNextManifestUnresourced("foobarbaz", "rose-quartz-helloworld-chart") 87 yaml := m.K8sTarget().YAML 88 89 entities, err := k8s.ParseYAMLFromString(yaml) 90 require.NoError(t, err) 91 require.Len(t, entities, 2) 92 e := entities[0] 93 require.Equal(t, "Namespace", e.GVK().Kind) 94 assert.Equal(t, "foobarbaz", e.Name()) 95 assert.Equal(t, "indeed", e.Labels()["somePersistedLabel"], 96 "label originally specified in chart YAML should persist") 97 } 98 99 func TestHelmNamespaceFlagInsertsNSEntityIfDifferentNSInChart(t *testing.T) { 100 f := newFixture(t) 101 102 f.setupHelm() 103 104 valuesWithNamespace := ` 105 namespace: 106 enabled: true 107 name: not-the-one-specified-in-flag` // what kind of jerk would do this? 108 f.file("helm/extra_values.yaml", valuesWithNamespace) 109 110 f.file("Tiltfile", ` 111 yml = helm('./helm', name='rose-quartz', namespace="foobarbaz", values=['./helm/extra_values.yaml']) 112 k8s_yaml(yml) 113 `) 114 115 f.load() 116 117 f.assertNextManifestUnresourced("not-the-one-specified-in-flag", "rose-quartz-helloworld-chart") 118 } 119 120 func TestHelmInvalidDirectory(t *testing.T) { 121 f := newFixture(t) 122 123 f.file("Tiltfile", ` 124 yml = helm('helm') 125 k8s_yaml(yml) 126 `) 127 128 f.loadErrString("Could not read Helm chart directory") 129 } 130 131 func TestHelmFromRepoPath(t *testing.T) { 132 f := newFixture(t) 133 134 f.gitInit(".") 135 f.setupHelm() 136 137 f.file("Tiltfile", ` 138 r = local_git_repo('.') 139 yml = helm(r.paths('helm')) 140 k8s_yaml(yml) 141 `) 142 143 f.load() 144 145 f.assertNextManifestUnresourced("chart-helloworld-chart") 146 f.assertConfigFiles( 147 "Tiltfile", 148 ".tiltignore", 149 "helm", 150 ) 151 } 152 153 func TestHelmMalformedChart(t *testing.T) { 154 f := newFixture(t) 155 156 f.WriteFile("./helm/Chart.yaml", "brrrrr") 157 158 f.file("Tiltfile", ` 159 yml = helm('helm') 160 k8s_yaml(yml) 161 `) 162 163 f.loadErrString("error unmarshaling JSON") 164 f.assertConfigFiles( 165 "Tiltfile", 166 ".tiltignore", 167 "helm", 168 ) 169 } 170 171 func TestHelmNamespace(t *testing.T) { 172 f := newFixture(t) 173 174 f.setupHelm() 175 f.file("helm/templates/public-config.yaml", `apiVersion: v1 176 kind: ConfigMap 177 metadata: 178 name: public-config 179 namespace: kube-public 180 data: 181 noData: "true" 182 `) 183 184 f.file("Tiltfile", ` 185 yml = helm('./helm', name='rose-quartz', namespace='garnet') 186 k8s_yaml(yml) 187 `) 188 189 f.load() 190 191 m := f.assertNextManifestUnresourced( 192 "public-config", 193 "rose-quartz-helloworld-chart") 194 yaml := m.K8sTarget().YAML 195 196 assert.Contains(t, yaml, "name: rose-quartz-helloworld-chart\n namespace: garnet") 197 assert.Contains(t, yaml, "name: public-config\n namespace: kube-public") 198 } 199 200 func TestHelmSetArgs(t *testing.T) { 201 f := newFixture(t) 202 203 f.setupHelm() 204 205 f.file("Tiltfile", ` 206 yml = helm('./helm', name='rose-quartz', namespace='garnet', set=[ 207 'ingress.enabled=true', 208 'service.externalPort=1234', 209 'service.internalPort=5678' 210 ]) 211 k8s_yaml(yml) 212 `) 213 214 f.load() 215 216 m := f.assertNextManifestUnresourced( 217 // A service and ingress with the same name 218 "rose-quartz-helloworld-chart", 219 "rose-quartz-helloworld-chart") 220 yaml := m.K8sTarget().YAML 221 222 // Set on the service 223 assert.Contains(t, yaml, "port: 1234") 224 assert.Contains(t, yaml, "targetPort: 5678") 225 226 // Set on the ingress 227 assert.Contains(t, yaml, "serviceName: rose-quartz-helloworld-chart") 228 assert.Contains(t, yaml, "servicePort: 1234") 229 } 230 231 func TestHelmSetArgsMap(t *testing.T) { 232 f := newFixture(t) 233 234 f.setupHelm() 235 236 f.file("Tiltfile", ` 237 yml = helm('./helm', name='rose-quartz', namespace='garnet', set={'a': 'b'}) 238 k8s_yaml(yml) 239 `) 240 241 f.loadErrString("helm: for parameter \"set\"", "string", "List", "type dict") 242 } 243 244 const exampleHelmV2VersionOutput = `Client: v2.12.3geecf22f` 245 const exampleHelmV3_0VersionOutput = `v3.0.0` 246 const exampleHelmV3_1VersionOutput = `v3.1.0` 247 const exampleHelmV3_2VersionOutput = `v3.2.4` 248 const examplePkgxHelmV3_15VersionOutput = `3.15.2` 249 const exampleHelmV4_0VersionOutput = `v4.0.0+g99cd196` 250 251 // see https://github.com/tilt-dev/tilt/issues/3788 252 const exampleHelmV3_3VersionOutput = `WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /Users/someone/.kube/config 253 WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /Users/someone/.kube/config 254 v3.3.3+g55e3ca0 255 ` 256 257 func TestParseHelmV2Version(t *testing.T) { 258 _, err := parseVersion(exampleHelmV2VersionOutput) 259 require.Error(t, err) 260 require.Contains(t, err.Error(), "could not parse Helm version from string") 261 } 262 263 func TestParseHelmV3Version(t *testing.T) { 264 expected := helmV3_0 265 assertHelmVersion(t, exampleHelmV3_0VersionOutput, expected) 266 } 267 268 func TestParseHelmV3_1Version(t *testing.T) { 269 expected := helmV3_1andAbove 270 assertHelmVersion(t, exampleHelmV3_1VersionOutput, expected) 271 } 272 273 func TestParseHelmV3_2Version(t *testing.T) { 274 expected := helmV3_1andAbove 275 assertHelmVersion(t, exampleHelmV3_2VersionOutput, expected) 276 } 277 278 func TestParseHelmV3_3Version(t *testing.T) { 279 expected := helmV3_1andAbove 280 assertHelmVersion(t, exampleHelmV3_3VersionOutput, expected) 281 } 282 283 func TestParsePkgxHelmV3_15Version(t *testing.T) { 284 expected := helmV3_1andAbove 285 assertHelmVersion(t, examplePkgxHelmV3_15VersionOutput, expected) 286 } 287 288 func TestParsePkgxHelmV4(t *testing.T) { 289 assertHelmVersion(t, exampleHelmV4_0VersionOutput, helmV3_1andAbove) 290 } 291 292 func TestHelmUnknownVersionError(t *testing.T) { 293 _, err := parseVersion("vx.1.2") 294 require.Error(t, err) 295 require.Contains(t, err.Error(), "could not parse Helm version from string") 296 } 297 298 const fileRequirementsYAML = `dependencies: 299 - name: foobar 300 version: 1.0.1 301 repository: file://./foobar` 302 303 func TestLocalSubchartFileDependencies(t *testing.T) { 304 input := []byte(fileRequirementsYAML) 305 expected := "./foobar" 306 actual, err := localSubchartDependencies(input) 307 if err != nil { 308 t.Fatal(err) 309 } 310 311 assert.Contains(t, actual, expected) 312 } 313 314 const remoteRequirementsYAML = ` 315 dependencies: 316 - name: etcd 317 version: 0.6.2 318 repository: https://kubernetes-charts-incubator.storage.googleapis.com/ 319 condition: etcd.deployChart` 320 321 func TestSubchartRemoteDependencies(t *testing.T) { 322 input := []byte(remoteRequirementsYAML) 323 actual, err := localSubchartDependencies(input) 324 if err != nil { 325 t.Fatal(err) 326 } 327 328 assert.Empty(t, actual) 329 } 330 331 func TestHelmReleaseName(t *testing.T) { 332 f := newFixture(t) 333 334 f.file("helm/Chart.yaml", `apiVersion: v1 335 description: grafana chart 336 name: grafana 337 version: 0.1.0`) 338 339 f.file("helm/values.yaml", testdata.GrafanaHelmValues) 340 f.file("helm/templates/_helpers.tpl", testdata.GrafanaHelmHelpers) 341 f.file("helm/templates/service-account.yaml", testdata.GrafanaHelmServiceAccount) 342 343 f.file("Tiltfile", ` 344 k8s_yaml(helm('./helm')) 345 `) 346 347 f.load() 348 349 manifests := f.loadResult.Manifests 350 require.Equal(t, 1, len(manifests)) 351 352 m := manifests[0] 353 yaml := m.K8sTarget().YAML 354 assert.NotContains(t, yaml, "RELEASE-NAME") 355 assert.Contains(t, yaml, "name: chart-grafana") 356 } 357 358 func TestHelm3CRD(t *testing.T) { 359 f := newFixture(t) 360 361 f.file("helm/Chart.yaml", `apiVersion: v1 362 description: crd chart 363 name: crd 364 version: 0.1.0`) 365 366 f.file("helm/templates/service-account.yaml", `apiVersion: v1 367 kind: ServiceAccount 368 metadata: 369 name: crd-sa`) 370 371 // Only works in Helm3 372 // https://helm.sh/docs/chart_best_practices/custom_resource_definitions/ 373 f.file("helm/crds/um.yaml", `apiVersion: tilt.dev/v1alpha1 374 kind: UselessMachine 375 metadata: 376 name: bobo 377 spec: 378 image: bobo`) 379 380 f.file("Tiltfile", ` 381 k8s_yaml(helm('./helm')) 382 `) 383 384 f.load() 385 386 manifests := f.loadResult.Manifests 387 require.Equal(t, 1, len(manifests)) 388 389 m := manifests[0] 390 yaml := m.K8sTarget().YAML 391 v, err := getHelmVersion() 392 assert.NoError(t, err) 393 assert.Contains(t, yaml, "kind: ServiceAccount") 394 if v == helmV3_0 || v == helmV3_1andAbove { 395 assert.Contains(t, yaml, "kind: UselessMachine") 396 } else { 397 assert.NotContains(t, yaml, "kind: UselessMachine") 398 } 399 } 400 401 func assertHelmVersion(t *testing.T, versionOutput string, expectedV helmVersion) { 402 actualV, err := parseVersion(versionOutput) 403 require.NoError(t, err, "parsing helm version") 404 require.Equal(t, expectedV, actualV) 405 } 406 407 func TestYamlErrorFromHelm(t *testing.T) { 408 f := newFixture(t) 409 f.setupHelm() 410 f.file("helm/templates/foo.yaml", "hi") 411 f.file("Tiltfile", ` 412 k8s_yaml(helm('helm')) 413 `) 414 f.loadErrString("in helm") 415 } 416 417 func TestHelmSkipsTests(t *testing.T) { 418 f := newFixture(t) 419 420 f.setupHelmWithTest() 421 f.file("Tiltfile", ` 422 yml = helm('helm') 423 k8s_yaml(yml) 424 `) 425 426 f.load() 427 428 f.assertNextManifestUnresourced("chart-helloworld-chart") 429 f.assertConfigFiles( 430 "Tiltfile", 431 ".tiltignore", 432 "helm", 433 ) 434 } 435 436 func TestHelmIncludesRequirements(t *testing.T) { 437 f := newFixture(t) 438 439 f.setupHelmWithRequirements() 440 f.file("Tiltfile", ` 441 yml = helm('helm') 442 k8s_yaml(yml) 443 `) 444 445 f.load() 446 f.assertNextManifest("chart-nginx-ingress-controller") 447 }