Skip to content

Commit 3a22d76

Browse files
Add CreateYAMLFromFileWithEnvvars method to ApplyClient for environment variable substitution
- Implemented CreateYAMLFromFileWithEnvvars to read a YAML file, validate environment variables, and create resources. - Added FindUnsetEnvVars function to identify unset environment variables in the YAML content. - Updated stress tests to validate environment variables in custom resource files before applying them. - Adjusted cluster_config.yml to specify Kubernetes version and added new CSI modules with dependencies.
1 parent 30ef6e1 commit 3a22d76

5 files changed

Lines changed: 175 additions & 31 deletions

File tree

pkg/kubernetes/apply.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package kubernetes
1919
import (
2020
"context"
2121
"fmt"
22+
"os"
23+
"regexp"
2224
"strings"
2325

2426
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -223,6 +225,57 @@ func (c *ApplyClient) createDocument(ctx context.Context, yamlDoc string, defaul
223225
return nil
224226
}
225227

228+
// CreateYAMLFromFileWithEnvvars reads a YAML file, validates environment variables, substitutes them, and creates resources
229+
// Returns error if file cannot be read, any ${VAR} is not set, or resource creation fails
230+
func (c *ApplyClient) CreateYAMLFromFileWithEnvvars(ctx context.Context, filePath string, namespace string) error {
231+
// Read file content
232+
content, err := os.ReadFile(filePath)
233+
if err != nil {
234+
return fmt.Errorf("failed to read file %s: %w", filePath, err)
235+
}
236+
237+
yamlContent := string(content)
238+
239+
// Find all ${VAR} patterns and check if they're set
240+
unsetVars := FindUnsetEnvVars(yamlContent)
241+
if len(unsetVars) > 0 {
242+
return fmt.Errorf("environment variables not set: %v", unsetVars)
243+
}
244+
245+
// Substitute environment variables
246+
expanded := os.ExpandEnv(yamlContent)
247+
248+
// Create resources
249+
return c.CreateYAML(ctx, expanded, namespace)
250+
}
251+
252+
// FindUnsetEnvVars finds all ${VAR} patterns in content and returns those that are not set
253+
func FindUnsetEnvVars(content string) []string {
254+
// Match ${VAR} pattern
255+
re := regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)\}`)
256+
matches := re.FindAllStringSubmatch(content, -1)
257+
258+
seen := make(map[string]bool)
259+
var unset []string
260+
261+
for _, match := range matches {
262+
if len(match) < 2 {
263+
continue
264+
}
265+
varName := match[1]
266+
if seen[varName] {
267+
continue
268+
}
269+
seen[varName] = true
270+
271+
if os.Getenv(varName) == "" {
272+
unset = append(unset, varName)
273+
}
274+
}
275+
276+
return unset
277+
}
278+
226279
// splitYAMLDocuments splits YAML content by "---" separator
227280
func splitYAMLDocuments(yamlContent string) []string {
228281
// Split by document separator

tests/csi-all-stress-tests/cluster_config.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ clusterDefinition:
3939
diskSize: 30
4040
# DKP parameters
4141
dkpParameters:
42-
kubernetesVersion: "Automatic"
42+
kubernetesVersion: "1.35"
4343
podSubnetCIDR: "10.112.0.0/16"
4444
serviceSubnetCIDR: "10.225.0.0/16"
4545
clusterDomain: "cluster.local"
@@ -63,4 +63,21 @@ clusterDefinition:
6363
settings:
6464
enableThinProvisioning: true
6565
dependencies: []
66+
- name: "csi-huawei"
67+
version: 1
68+
enabled: true
69+
modulePullOverride: "mr48"
70+
dependencies:
71+
- "snapshot-controller"
72+
- name: "csi-hpe"
73+
version: 1
74+
enabled: true
75+
dependencies:
76+
- "snapshot-controller"
77+
- name: "csi-netapp"
78+
version: 1
79+
enabled: true
80+
dependencies:
81+
- "snapshot-controller"
82+
6683

tests/csi-all-stress-tests/csi_all_stress_tests_test.go

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"os"
2222
"path/filepath"
2323
"runtime"
24+
"strings"
2425
"time"
2526

2627
. "github.com/onsi/ginkgo/v2"
@@ -35,9 +36,48 @@ import (
3536
var _ = Describe("All CSIs Stress Tests", Ordered, func() {
3637
var (
3738
testClusterResources *cluster.TestClusterResources
39+
testDir string
40+
crFiles []string
41+
crFilesDir string
42+
storageClassNames []string
3843
)
3944

4045
BeforeAll(func() {
46+
By("Setting up test variables", func() {
47+
_, callerFile, _, ok := runtime.Caller(0)
48+
Expect(ok).To(BeTrue(), "Failed to determine test file path")
49+
testDir = filepath.Dir(callerFile)
50+
51+
crFiles = []string{"csi-huawei-cr.yml", "csi-hpe-cr.yml", "csi-netapp-cr.yml"}
52+
crFilesDir = filepath.Join(testDir, "files")
53+
54+
storageClassNames = []string{"hsclass-200", "hpe-iscsi", "csi-netapp-sc1"}
55+
})
56+
57+
By("Validating environment variables in CR files", func() {
58+
var allUnsetVars []string
59+
60+
for _, fileName := range crFiles {
61+
filePath := filepath.Join(crFilesDir, fileName)
62+
63+
// Skip if file doesn't exist
64+
if _, err := os.Stat(filePath); os.IsNotExist(err) {
65+
continue
66+
}
67+
68+
content, err := os.ReadFile(filePath)
69+
Expect(err).NotTo(HaveOccurred(), "Failed to read file: "+fileName)
70+
71+
unsetVars := kubernetes.FindUnsetEnvVars(string(content))
72+
if len(unsetVars) > 0 {
73+
GinkgoWriter.Printf(" ❌ %s requires env vars: %v\n", fileName, unsetVars)
74+
allUnsetVars = append(allUnsetVars, unsetVars...)
75+
}
76+
}
77+
78+
Expect(allUnsetVars).To(BeEmpty(), "Environment variables for custom resources are not set: "+strings.Join(allUnsetVars, ", "))
79+
})
80+
4181
By("Outputting environment variables", func() {
4282
GinkgoWriter.Printf(" 📋 Environment variables (without default values):\n")
4383

@@ -154,12 +194,6 @@ var _ = Describe("All CSIs Stress Tests", Ordered, func() {
154194
It("should create NGCs", func() {
155195
ctx := context.Background()
156196

157-
// Resolve file path relative to test directory (same approach as CreateTestCluster)
158-
// runtime.Caller(0) gets this test file's location
159-
_, callerFile, _, ok := runtime.Caller(0)
160-
Expect(ok).To(BeTrue(), "Failed to determine test file path")
161-
testDir := filepath.Dir(callerFile)
162-
163197
yamlFilePathNGCs := filepath.Join(testDir, "files", "ngc.yml")
164198

165199
By("Applying NGCs", func() {
@@ -180,48 +214,31 @@ var _ = Describe("All CSIs Stress Tests", Ordered, func() {
180214

181215
It("should create modules' custom resources", func() {
182216
ctx := context.Background()
183-
crFiles := []string{"csi-huawei-cr.yml", "csi-hpe-cr.yml", "csi-netapp-cr.yml"}
184-
185-
// Resolve file path relative to test directory
186-
_, callerFile, _, ok := runtime.Caller(0)
187-
Expect(ok).To(BeTrue(), "Failed to determine test file path")
188-
testDir := filepath.Dir(callerFile)
189-
filesDir := filepath.Join(testDir, "files")
190217

191218
By("Applying all storage custom resources", func() {
192219
GinkgoWriter.Printf(" ▶️ Creating storage resources from %d files...\n", len(crFiles))
193220

194-
var combinedContent string
221+
applyClient, err := kubernetes.NewApplyClient(testClusterResources.Kubeconfig)
222+
Expect(err).NotTo(HaveOccurred(), "Failed to create apply client")
223+
195224
for _, fileName := range crFiles {
196-
filePath := filepath.Join(filesDir, fileName)
225+
filePath := filepath.Join(crFilesDir, fileName)
197226

198227
// Skip if file doesn't exist
199228
if _, err := os.Stat(filePath); os.IsNotExist(err) {
200229
GinkgoWriter.Printf(" ⏭️ Skipping %s (file not found)\n", fileName)
201230
continue
202231
}
203232

204-
content, err := os.ReadFile(filePath)
205-
Expect(err).NotTo(HaveOccurred(), "Failed to read file: "+fileName)
206-
207-
// Add document separator if not first file
208-
if combinedContent != "" {
209-
combinedContent += "\n---\n"
210-
}
211-
combinedContent += string(content)
233+
GinkgoWriter.Printf(" 📄 Applying %s...\n", fileName)
234+
err = applyClient.CreateYAMLFromFileWithEnvvars(ctx, filePath, "")
235+
Expect(err).NotTo(HaveOccurred(), "Failed to apply "+fileName)
212236
}
213237

214-
applyClient, err := kubernetes.NewApplyClient(testClusterResources.Kubeconfig)
215-
Expect(err).NotTo(HaveOccurred(), "Failed to create apply client")
216-
217-
err = applyClient.CreateYAML(ctx, combinedContent, "")
218-
Expect(err).NotTo(HaveOccurred(), "Failed to apply YAML resources")
219-
220238
GinkgoWriter.Printf(" ✅ Resources created successfully\n")
221239
})
222240

223241
By("Waiting for StorageClasses to become available", func() {
224-
storageClassNames := []string{"hsclass-200", "hpe", "netapp"}
225242

226243
GinkgoWriter.Printf(" ▶️ Waiting for %d StorageClasses...\n", len(storageClassNames))
227244

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
apiVersion: storage.deckhouse.io/v1alpha1
3+
kind: HPEStorageConnection
4+
metadata:
5+
name: hpe
6+
spec:
7+
controlPlane:
8+
backendAddress: 172.17.1.55
9+
password: '${HPE_PASSWORD}'
10+
serviceName: primera3par-csp-svc
11+
servicePort: "8080"
12+
username: 3paradm
13+
---
14+
apiVersion: storage.deckhouse.io/v1alpha1
15+
kind: HPEStorageClass
16+
metadata:
17+
name: hpe-iscsi
18+
spec:
19+
accessProtocol: iscsi
20+
cpg: test-cpg
21+
fsType: xfs
22+
pool: test-cpg
23+
reclaimPolicy: Delete
24+
storageConnectionName: hpe
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
apiVersion: storage.deckhouse.io/v1alpha1
3+
kind: NetappStorageConnection
4+
metadata:
5+
name: csi-netapp-conn1
6+
spec:
7+
controlPlane:
8+
address: 172.17.1.82
9+
password: '${NETAPP_PASSWORD}'
10+
protocol: ontapi
11+
svm: svm0
12+
username: admin
13+
dataPlane:
14+
iSCSI:
15+
chap:
16+
chapInitiatorSecret: '${NETAPP_CHAP_INITIATOR_SECRET}'
17+
chapTargetInitiatorSecret: '${NETAPP_CHAP_TARGET_INITIATOR_SECRET}'
18+
chapTargetUsername: target@dm|n
19+
chapUsername: chap@dm1n
20+
useChap: false
21+
nfs:
22+
address: 10.200.0.80
23+
version: "3"
24+
protocol: iscsi
25+
---
26+
apiVersion: storage.deckhouse.io/v1alpha1
27+
kind: NetappStorageClass
28+
metadata:
29+
name: csi-netapp-sc1
30+
spec:
31+
connectionName: csi-netapp-conn1
32+
fsType: ext4
33+
provisioningType: thin

0 commit comments

Comments
 (0)