diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1b06aac --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: 'CI' + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22.x + - name: Install dependencies + run: npm install + - name: Compile + run: npm run compile + test: + needs: compile + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22.x + - name: Install dependencies + run: npm install + - name: Run tests on Linux + if: runner.os == 'Linux' + uses: coactions/setup-xvfb@v1 + with: + run: npm test + + - name: Run tests on Windows and macOS + if: runner.os != 'Linux' + run: npm test diff --git a/.vscode-test.mjs b/.vscode-test.mjs index b62ba25..31477b9 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -1,5 +1,11 @@ import { defineConfig } from '@vscode/test-cli'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); export default defineConfig({ files: 'out/test/**/*.test.js', + workspaceFolder: path.resolve(__dirname), }); diff --git a/README.md b/README.md index 0f96605..58d7913 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,12 @@ Install the extension from either VSCode marketplace or Open-VSX The extension will try to download helm-ls automatically. A `helm_ls` (see [getting Started](https://github.com/mrjosh/helm-ls/#getting-started)) executable on your `PATH` will instead be preferred if found. -The [kubernetes extension](https://github.com/vscode-kubernetes-tools/vscode-kubernetes-tools) is a dependency for this extension and will be installed automatically. The extension will automatically use the bundled yaml-language-server. If `yaml-language-server` is found on `PATH` or `helm-ls.yamlls.path` is configured this will be used instead. See the helm-ls [readme](https://github.com/mrjosh/helm-ls/?tab=readme-ov-file#integration-with-yaml-language-server) for more info on yaml-language-server integration. +**Note:** While this extension provides language server features (autocompletion, linting, hover, etc.), the [kubernetes extension](https://github.com/vscode-kubernetes-tools/vscode-kubernetes-tools) is still recommended for additional features like syntax highlighting, snippets, and other Helm tooling. + ## Extension Settings The extension can be configured via the `helm-ls` extension settings. diff --git a/package-lock.json b/package-lock.json index 50cf39d..7cd4ef0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -921,7 +921,6 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -1176,7 +1175,6 @@ "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1255,7 +1253,6 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -1730,7 +1727,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1996,7 +1992,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2996,7 +2991,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4275,7 +4269,6 @@ "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -5566,7 +5559,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@nodeutils/defaults-deep": "1.1.0", "@octokit/rest": "22.0.0", @@ -5967,7 +5959,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6473,7 +6464,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -6571,7 +6561,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6822,7 +6811,6 @@ "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -6872,7 +6860,6 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -7189,7 +7176,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/package.json b/package.json index 96ed5b4..c226ba7 100644 --- a/package.json +++ b/package.json @@ -77,11 +77,25 @@ "default": "" } } - } + }, + "languages": [ + { + "id": "helm", + "aliases": [ + "helm-template", + "helm" + ], + "filenamePatterns": [ + "**/templates/*.yaml", + "**/templates/*.yml", + "**/templates/*.tpl", + "**/templates/**/*.yaml", + "**/templates/**/*.yml", + "**/templates/**/*.tpl" + ] + } + ] }, - "extensionDependencies": [ - "ms-kubernetes-tools.vscode-kubernetes-tools" - ], "scripts": { "vscode:prepublish": "npm run package", "compile": "webpack", @@ -89,7 +103,7 @@ "package": "webpack --mode production --devtool hidden-source-map", "compile-tests": "tsc -p . --outDir out", "watch-tests": "tsc -p . -w --outDir out", - "pretest": "npm run compile-tests && npm run compile && npm run lint", + "pretest": "npm run compile-tests && npm run compile", "lint": "eslint src --ext ts", "test": "vscode-test", "release": "release-it" diff --git a/src/test/executable.test.ts b/src/test/executable.test.ts index 747b4a9..e3f7f79 100644 --- a/src/test/executable.test.ts +++ b/src/test/executable.test.ts @@ -69,6 +69,7 @@ class MockExtensionContext implements vscode.ExtensionContext { delete: () => Promise.resolve(), onDidChange: new vscode.EventEmitter() .event, + keys: () => Promise.resolve([]), }; asAbsolutePath = (relativePath: string) => path.join(this.extensionPath, relativePath); @@ -81,105 +82,107 @@ class MockExtensionContext implements vscode.ExtensionContext { }; } -suite("Executable Test Suite", () => { - test("Download binary for macOS Intel (amd64)", async function () { - this.timeout(30000); - const context = new MockExtensionContext(); - const platformInfo = { - platform: "darwin", - arch: "amd64", - extension: "", - }; - const helmExecutable = await downloadHelmLs(context, platformInfo); - assert.ok(helmExecutable); - assert.ok(!helmExecutable.endsWith(".exe")); - assert.ok(await fs.stat(helmExecutable)); - }); +if (process.env.CI) { + suite("Executable Test Suite", () => { + test("Download binary for macOS Intel (amd64)", async function () { + this.timeout(30000); + const context = new MockExtensionContext(); + const platformInfo = { + platform: "darwin", + arch: "amd64", + extension: "", + }; + const helmExecutable = await downloadHelmLs(context, platformInfo); + assert.ok(helmExecutable); + assert.ok(!helmExecutable.endsWith(".exe")); + assert.ok(await fs.stat(helmExecutable)); + }); - test("Download binary for macOS Apple Silicon (arm64)", async function () { - this.timeout(30000); - const context = new MockExtensionContext(); - const platformInfo = { - platform: "darwin", - arch: "arm64", - extension: "", - }; - const helmExecutable = await downloadHelmLs(context, platformInfo); - assert.ok(helmExecutable); - assert.ok(!helmExecutable.endsWith(".exe")); - assert.ok(await fs.stat(helmExecutable)); - }); + test("Download binary for macOS Apple Silicon (arm64)", async function () { + this.timeout(30000); + const context = new MockExtensionContext(); + const platformInfo = { + platform: "darwin", + arch: "arm64", + extension: "", + }; + const helmExecutable = await downloadHelmLs(context, platformInfo); + assert.ok(helmExecutable); + assert.ok(!helmExecutable.endsWith(".exe")); + assert.ok(await fs.stat(helmExecutable)); + }); - test("Download binary for Linux amd64", async function () { - this.timeout(30000); - const context = new MockExtensionContext(); - const platformInfo = { - platform: "linux", - arch: "amd64", - extension: "", - }; - const helmExecutable = await downloadHelmLs(context, platformInfo); - assert.ok(helmExecutable); - assert.ok(!helmExecutable.endsWith(".exe")); - assert.ok(await fs.stat(helmExecutable)); - }); + test("Download binary for Linux amd64", async function () { + this.timeout(30000); + const context = new MockExtensionContext(); + const platformInfo = { + platform: "linux", + arch: "amd64", + extension: "", + }; + const helmExecutable = await downloadHelmLs(context, platformInfo); + assert.ok(helmExecutable); + assert.ok(!helmExecutable.endsWith(".exe")); + assert.ok(await fs.stat(helmExecutable)); + }); - test("Download binary for Linux ARM (32-bit)", async function () { - this.timeout(30000); - const context = new MockExtensionContext(); - const platformInfo = { - platform: "linux", - arch: "arm", - extension: "", - }; - const helmExecutable = await downloadHelmLs(context, platformInfo); - assert.ok(helmExecutable); - assert.ok(!helmExecutable.endsWith(".exe")); - assert.ok(await fs.stat(helmExecutable)); - }); + test("Download binary for Linux ARM (32-bit)", async function () { + this.timeout(30000); + const context = new MockExtensionContext(); + const platformInfo = { + platform: "linux", + arch: "arm", + extension: "", + }; + const helmExecutable = await downloadHelmLs(context, platformInfo); + assert.ok(helmExecutable); + assert.ok(!helmExecutable.endsWith(".exe")); + assert.ok(await fs.stat(helmExecutable)); + }); - test("Download binary for Linux ARM64", async function () { - this.timeout(30000); - const context = new MockExtensionContext(); - const platformInfo = { - platform: "linux", - arch: "arm64", - extension: "", - }; - const helmExecutable = await downloadHelmLs(context, platformInfo); - assert.ok(helmExecutable); - assert.ok(!helmExecutable.endsWith(".exe")); - assert.ok(await fs.stat(helmExecutable)); - }); + test("Download binary for Linux ARM64", async function () { + this.timeout(30000); + const context = new MockExtensionContext(); + const platformInfo = { + platform: "linux", + arch: "arm64", + extension: "", + }; + const helmExecutable = await downloadHelmLs(context, platformInfo); + assert.ok(helmExecutable); + assert.ok(!helmExecutable.endsWith(".exe")); + assert.ok(await fs.stat(helmExecutable)); + }); - test("Download binary for Windows x64", async function () { - this.timeout(30000); - const context = new MockExtensionContext(); - const platformInfo = { - platform: "windows", - arch: "amd64", - extension: ".exe", - }; - const helmExecutable = await downloadHelmLs(context, platformInfo); - assert.ok(helmExecutable); - assert.ok(helmExecutable.endsWith(".exe")); - assert.ok(await fs.stat(helmExecutable)); - }); + test("Download binary for Windows x64", async function () { + this.timeout(30000); + const context = new MockExtensionContext(); + const platformInfo = { + platform: "windows", + arch: "amd64", + extension: ".exe", + }; + const helmExecutable = await downloadHelmLs(context, platformInfo); + assert.ok(helmExecutable); + assert.ok(helmExecutable.endsWith(".exe")); + assert.ok(await fs.stat(helmExecutable)); + }); - test("Reuse downloaded binary", async function () { - this.timeout(30000); - const context = new MockExtensionContext(); - const platformInfo = { - platform: "linux", - arch: "amd64", - extension: "", - }; - // First download - const helmExecutable1 = await downloadHelmLs(context, platformInfo); - assert.ok(helmExecutable1); + test("Reuse downloaded binary", async function () { + this.timeout(30000); + const context = new MockExtensionContext(); + const platformInfo = { + platform: "linux", + arch: "amd64", + extension: "", + }; + // First download + const helmExecutable1 = await downloadHelmLs(context, platformInfo); + assert.ok(helmExecutable1); - // Second attempt should reuse the same file - const helmExecutable2 = await downloadHelmLs(context, platformInfo); - assert.strictEqual(helmExecutable1, helmExecutable2); + // Second attempt should reuse the same file + const helmExecutable2 = await downloadHelmLs(context, platformInfo); + assert.strictEqual(helmExecutable1, helmExecutable2); + }); }); -}); +} diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 2f671d3..d95bbcd 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -1,15 +1,150 @@ import * as assert from "assert"; - -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it +import * as path from "path"; import * as vscode from "vscode"; -// import * as myExtension from '../../extension'; + +/** + * Helper function to wait for extension activation with timeout + * Extension should activate automatically when Helm documents are opened + */ +async function waitForExtensionActivation( + extensionId: string, + timeout: number = 7000, +): Promise> { + const startTime = Date.now(); + + while (Date.now() - startTime < timeout) { + const ext = vscode.extensions.getExtension(extensionId); + if (ext?.isActive) { + return ext; + } + + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + throw new Error( + `Extension ${extensionId} failed to activate within ${timeout}ms`, + ); +} + +/** + * Helper function to open a Helm template file and wait for language server + */ +async function openHelmDocument( + workspaceFolder: vscode.WorkspaceFolder, +): Promise<{ document: vscode.TextDocument; uri: vscode.Uri }> { + const docPath = path.join( + workspaceFolder.uri.fsPath, + "src", + "test", + "fixtures", + "testChart", + "templates", + "deployment.yaml", + ); + const docUri = vscode.Uri.file(docPath); + const document = await vscode.workspace.openTextDocument(docUri); + await vscode.window.showTextDocument(document); + + // Give language server a moment to initialize + await new Promise((resolve) => setTimeout(resolve, 1000)); + + return { document, uri: docUri }; +} suite("Extension Test Suite", () => { - vscode.window.showInformationMessage("Start all tests."); + vscode.window.showInformationMessage("Starting Helm LS extension tests"); + + test("Extension loads and activates when opening Helm documents", async function () { + this.timeout(7000); + + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + assert.ok(workspaceFolder, "No workspace folder found"); + + // Extension should not be active initially + const extBefore = vscode.extensions.getExtension("helm-ls.helm-ls"); + assert.ok(extBefore, "Extension should be installed"); + + // Open a Helm file - this should trigger activation via onLanguage:helm + await openHelmDocument(workspaceFolder); + + // Wait for extension to activate automatically + const ext = await waitForExtensionActivation("helm-ls.helm-ls"); + assert.ok( + ext.isActive, + "Extension should be activated after opening Helm document", + ); + assert.strictEqual(ext.id, "helm-ls.helm-ls"); + }); + + test("Hover support for Helm templates", async function () { + this.timeout(3000); + + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + assert.ok(workspaceFolder, "No workspace folder found"); + + // Ensure extension is activated + await waitForExtensionActivation("helm-ls.helm-ls"); + + const { uri: docUri } = await openHelmDocument(workspaceFolder); + + // Test hover on .Values.replicaCount (line 9, assuming it's around column 25) + const helmPosition = new vscode.Position(8, 25); + const helmHovers = await vscode.commands.executeCommand( + "vscode.executeHoverProvider", + docUri, + helmPosition, + ); + + assert.ok( + helmHovers && helmHovers.length > 0, + "Should have hover information for Helm property", + ); + + const helmHoverContent = helmHovers[0].contents[0] as vscode.MarkdownString; + assert.ok(helmHoverContent, "Hover content should exist"); + + // Check for expected content (replicaCount value from values.yaml) + assert.ok( + helmHoverContent.value.includes("1") || + helmHoverContent.value.includes("replicaCount"), + `Hover should show replicaCount value or reference. Got: ${helmHoverContent.value}`, + ); + }); + + test("Hover support for YAML schema", async function () { + this.timeout(3000); + + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + assert.ok(workspaceFolder, "No workspace folder found"); + + // Ensure extension is activated + await waitForExtensionActivation("helm-ls.helm-ls"); + + const { uri: docUri } = await openHelmDocument(workspaceFolder); + + // Test hover on 'spec' property (line 7) + const yamlPosition = new vscode.Position(6, 3); + const yamlHovers = await vscode.commands.executeCommand( + "vscode.executeHoverProvider", + docUri, + yamlPosition, + ); + + assert.ok( + yamlHovers && yamlHovers.length > 0, + "Should have hover information for YAML property", + ); + + const yamlHoverContent = yamlHovers[0].contents[0] as vscode.MarkdownString; + assert.ok(yamlHoverContent, "YAML hover content should exist"); - test("Sample test", () => { - assert.strictEqual(-1, [1, 2, 3].indexOf(5)); - assert.strictEqual(-1, [1, 2, 3].indexOf(0)); + // Check for Kubernetes documentation + const content = yamlHoverContent.value.toLowerCase(); + assert.ok( + content.includes("deployment") || + content.includes("spec") || + content.includes("specification"), + `Hover should show Kubernetes schema info. Got: ${yamlHoverContent.value}`, + ); }); }); diff --git a/src/test/fixtures/testChart/.helmignore b/src/test/fixtures/testChart/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/src/test/fixtures/testChart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/src/test/fixtures/testChart/Chart.yaml b/src/test/fixtures/testChart/Chart.yaml new file mode 100644 index 0000000..cd8a397 --- /dev/null +++ b/src/test/fixtures/testChart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: testChart +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/src/test/fixtures/testChart/templates/NOTES.txt b/src/test/fixtures/testChart/templates/NOTES.txt new file mode 100644 index 0000000..dfbaf1e --- /dev/null +++ b/src/test/fixtures/testChart/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "testChart.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "testChart.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "testChart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "testChart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/src/test/fixtures/testChart/templates/_helpers.tpl b/src/test/fixtures/testChart/templates/_helpers.tpl new file mode 100644 index 0000000..dfe738e --- /dev/null +++ b/src/test/fixtures/testChart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "testChart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "testChart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "testChart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "testChart.labels" -}} +helm.sh/chart: {{ include "testChart.chart" . }} +{{ include "testChart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "testChart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "testChart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "testChart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "testChart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/src/test/fixtures/testChart/templates/deployment.yaml b/src/test/fixtures/testChart/templates/deployment.yaml new file mode 100644 index 0000000..c5e21e9 --- /dev/null +++ b/src/test/fixtures/testChart/templates/deployment.yaml @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "testChart.fullname" . }} + labels: + {{- include "testChart.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "testChart.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "testChart.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "testChart.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/src/test/fixtures/testChart/values.yaml b/src/test/fixtures/testChart/values.yaml new file mode 100644 index 0000000..51ef389 --- /dev/null +++ b/src/test/fixtures/testChart/values.yaml @@ -0,0 +1,123 @@ +# Default values for testChart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +replicaCount: 1 + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + repository: nginx + # This sets the pull policy for images. + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + port: 80 + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http + +# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/tsconfig.json b/tsconfig.json index 8a79f20..064a687 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,17 @@ { - "compilerOptions": { - "module": "Node16", - "target": "ES2022", - "lib": [ - "ES2022" - ], - "sourceMap": true, - "rootDir": "src", - "strict": true /* enable all strict type-checking options */ - /* Additional Checks */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - } + "compilerOptions": { + "module": "Node16", + "moduleResolution": "node16", + "target": "ES2022", + "lib": ["ES2022"], + "sourceMap": true, + "rootDir": "src", + "strict": true /* enable all strict type-checking options */, + /* Additional Checks */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + "skipLibCheck": true + }, + "exclude": ["node_modules", ".vscode-test", "dist", "out"] }