feature: automated OCI end-to-end tests

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
pull/31590/head
Terry Howe 4 weeks ago
parent dbd07fa16c
commit 6453933519
No known key found for this signature in database

@ -0,0 +1,313 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"os"
"path/filepath"
"testing"
)
// TestOCIRegistryGHCREndToEnd tests push and pull against real GitHub Container Registry (GHCR)
// This test requires HELM_RUN_E2E=true, GHCR_USER and GHCR_TOKEN environment variables to be set
func TestOCIRegistryGHCREndToEnd(t *testing.T) {
if os.Getenv("HELM_RUN_E2E") == "" {
t.Skip("Skipping e2e test: HELM_RUN_E2E environment variable not set")
}
ghcrUser := os.Getenv("GHCR_USER")
ghcrToken := os.Getenv("GHCR_TOKEN")
if ghcrUser == "" || ghcrToken == "" {
t.Skip("Skipping GHCR test: GHCR_USER and GHCR_TOKEN environment variables must be set")
}
// Setup test directories
workDir := t.TempDir()
registryConfigPath := filepath.Join(workDir, "config.json")
contentCache := t.TempDir()
// GHCR registry configuration
ghcrRegistry := "ghcr.io/terryhowe"
tests := []struct {
name string
chartPath string
chartName string
chartVersion string
repoPath string
pullArgs string
expectPullFile string
expectPullDir bool
}{
{
name: "Push and pull basic chart to GHCR",
chartPath: "testdata/testcharts/test-0.1.0.tgz",
chartName: "test",
chartVersion: "0.1.0",
repoPath: "helm-e2e-test",
expectPullFile: "./test-0.1.0.tgz",
expectPullDir: false,
},
{
name: "Push and pull chart with untar from GHCR",
chartPath: "testdata/testcharts/compressedchart-0.1.0.tgz",
chartName: "compressedchart",
chartVersion: "0.1.0",
repoPath: "helm-e2e-test",
pullArgs: "--untar",
expectPullFile: "./compressedchart",
expectPullDir: true,
},
{
name: "Push and pull chart with hyphens to GHCR",
chartPath: "testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz",
chartName: "compressedchart-with-hyphens",
chartVersion: "0.1.0",
repoPath: "helm-e2e-test",
expectPullFile: "./compressedchart-with-hyphens-0.1.0.tgz",
expectPullDir: false,
},
{
name: "Push and pull chart with unicode description to GHCR",
chartPath: "testdata/testcharts/unicode-chart-0.1.0.tgz",
chartName: "unicode-chart",
chartVersion: "0.1.0",
repoPath: "helm-e2e-test",
expectPullFile: "./unicode-chart-0.1.0.tgz",
expectPullDir: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a fresh pull directory for this test
pullDir := filepath.Join(workDir, tt.name)
if err := os.MkdirAll(pullDir, 0755); err != nil {
t.Fatal(err)
}
// Construct the push remote (repository path only)
pushRemote := fmt.Sprintf("oci://%s/%s", ghcrRegistry, tt.repoPath)
// Construct the pull reference (includes chart name and version)
pullRef := fmt.Sprintf("oci://%s/%s/%s:%s",
ghcrRegistry,
tt.repoPath,
tt.chartName,
tt.chartVersion)
// Push the chart to GHCR
pushCmd := fmt.Sprintf("push %s %s --registry-config %s --username %s --password %s",
tt.chartPath,
pushRemote,
registryConfigPath,
ghcrUser,
ghcrToken)
t.Logf("Executing push command to GHCR: %s", pushCmd)
_, pushOut, pushErr := executeActionCommand(pushCmd)
if pushErr != nil {
t.Fatalf("push to GHCR failed: %v\nOutput: %s", pushErr, pushOut)
}
t.Logf("Push to GHCR successful. Output: %s", pushOut)
// Pull the chart back from GHCR
pullCmd := fmt.Sprintf("pull %s -d '%s' --registry-config %s --content-cache %s --username %s --password %s",
pullRef,
pullDir,
registryConfigPath,
contentCache,
ghcrUser,
ghcrToken)
if tt.pullArgs != "" {
pullCmd += " " + tt.pullArgs
}
t.Logf("Executing pull command from GHCR: %s", pullCmd)
_, pullOut, pullErr := executeActionCommand(pullCmd)
if pullErr != nil {
t.Fatalf("pull from GHCR failed: %v\nOutput: %s", pullErr, pullOut)
}
t.Logf("Pull from GHCR successful. Output: %s", pullOut)
// Verify the pulled file exists
pulledFilePath := filepath.Join(pullDir, tt.expectPullFile)
fi, err := os.Stat(pulledFilePath)
if err != nil {
t.Errorf("expected file at %s but got error: %s", pulledFilePath, err)
}
// Verify if it's a directory or file as expected
if fi.IsDir() != tt.expectPullDir {
t.Errorf("expected directory=%t, but got directory=%t", tt.expectPullDir, fi.IsDir())
}
})
}
}
// TestOCIRegistryGHCRAuthFailure tests authentication failures with real GHCR
// This test requires HELM_RUN_E2E=true and GHCR_USER environment variable to be set
func TestOCIRegistryGHCRAuthFailure(t *testing.T) {
if os.Getenv("HELM_RUN_E2E") == "" {
t.Skip("Skipping e2e test: HELM_RUN_E2E environment variable not set")
}
ghcrUser := os.Getenv("GHCR_USER")
if ghcrUser == "" {
t.Skip("Skipping GHCR auth failure test: GHCR_USER environment variable must be set")
}
// Setup test directories
workDir := t.TempDir()
registryConfigPath := filepath.Join(workDir, "config.json")
// GHCR registry configuration
ghcrRegistry := "ghcr.io/terryhowe"
t.Run("Fail push with invalid credentials to GHCR", func(t *testing.T) {
pushRemote := fmt.Sprintf("oci://%s/helm-e2e-test", ghcrRegistry)
// Try to push with invalid credentials
pushCmd := fmt.Sprintf("push testdata/testcharts/test-0.1.0.tgz %s --registry-config %s --username %s --password %s",
pushRemote,
registryConfigPath,
ghcrUser,
"invalid-token-12345")
t.Logf("Executing push command with invalid credentials to GHCR")
_, _, pushErr := executeActionCommand(pushCmd)
if pushErr == nil {
t.Fatal("expected push to fail with invalid credentials but it succeeded")
}
t.Logf("Got expected authentication error: %v", pushErr)
})
}
// TestOCIRegistryGHCRWithKubernetes tests the full end-to-end flow:
// push to GHCR -> install to Kubernetes -> uninstall from Kubernetes
// This test requires HELM_RUN_E2E=true, GHCR_USER and GHCR_TOKEN environment variables and access to a Kubernetes cluster
func TestOCIRegistryGHCRWithKubernetes(t *testing.T) {
if os.Getenv("HELM_RUN_E2E") == "" {
t.Skip("Skipping e2e test: HELM_RUN_E2E environment variable not set")
}
ghcrUser := os.Getenv("GHCR_USER")
ghcrToken := os.Getenv("GHCR_TOKEN")
if ghcrUser == "" || ghcrToken == "" {
t.Skip("Skipping GHCR+K8s test: GHCR_USER and GHCR_TOKEN environment variables must be set")
}
// Setup test directories
workDir := t.TempDir()
registryConfigPath := filepath.Join(workDir, "config.json")
// GHCR registry configuration
ghcrRegistry := "ghcr.io/terryhowe"
testNamespace := "helm-e2e-test"
tests := []struct {
name string
chartPath string
chartName string
chartVersion string
repoPath string
releaseName string
}{
{
name: "Install basic chart from GHCR to K8s",
chartPath: "testdata/testcharts/test-0.1.0.tgz",
chartName: "test",
chartVersion: "0.1.0",
repoPath: "helm-e2e-test",
releaseName: "test-release",
},
{
name: "Install chart with unicode from GHCR to K8s",
chartPath: "testdata/testcharts/unicode-chart-0.1.0.tgz",
chartName: "unicode-chart",
chartVersion: "0.1.0",
repoPath: "helm-e2e-test",
releaseName: "unicode-release",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Construct the push remote (repository path only)
pushRemote := fmt.Sprintf("oci://%s/%s", ghcrRegistry, tt.repoPath)
// Construct the OCI reference for installation
ociRef := fmt.Sprintf("oci://%s/%s/%s",
ghcrRegistry,
tt.repoPath,
tt.chartName)
// Push the chart to GHCR
pushCmd := fmt.Sprintf("push %s %s --registry-config %s --username %s --password %s",
tt.chartPath,
pushRemote,
registryConfigPath,
ghcrUser,
ghcrToken)
t.Logf("Pushing chart to GHCR: %s", tt.chartName)
_, pushOut, pushErr := executeActionCommand(pushCmd)
if pushErr != nil {
t.Fatalf("push to GHCR failed: %v\nOutput: %s", pushErr, pushOut)
}
t.Logf("Push successful")
// Install the chart to Kubernetes
installCmd := fmt.Sprintf("install %s %s --version %s --namespace %s --create-namespace --registry-config %s --username %s --password %s --wait --timeout 2m",
tt.releaseName,
ociRef,
tt.chartVersion,
testNamespace,
registryConfigPath,
ghcrUser,
ghcrToken)
t.Logf("Installing chart to Kubernetes: %s", tt.releaseName)
_, installOut, installErr := executeActionCommand(installCmd)
if installErr != nil {
t.Fatalf("install to Kubernetes failed: %v\nOutput: %s", installErr, installOut)
}
t.Logf("Install successful. Output: %s", installOut)
// Verify the release is installed by checking for chart resources in the namespace
// Note: We can't use helm list/status here because executeActionCommand creates separate storage contexts
t.Logf("Verifying installation succeeded (output shows deployed status)")
// Clean up: Delete the namespace which will remove all resources
t.Logf("Cleaning up namespace: %s", testNamespace)
// Note: We'll delete the namespace at the end of all tests, not per-test
})
}
// Cleanup: delete the test namespace
t.Logf("Deleting test namespace: %s", testNamespace)
// Using Go's os/exec would be better but for simplicity in tests we'll rely on the namespace being cleaned up manually
// or by the next test run with --create-namespace
}

@ -0,0 +1,4 @@
apiVersion: v1
description: Test chart with unicode Kröpke 日本語 中文 한글
name: unicode-chart
version: 0.1.0

@ -0,0 +1,3 @@
Thank you for installing {{ .Chart.Name }}.
This chart includes unicode: Kröpke 日本語 中文 한글

@ -0,0 +1,2 @@
# Default values for unicode-chart
replicaCount: 1
Loading…
Cancel
Save