Skip to content

Commit 4d09a08

Browse files
authored
test(act): add support for glob and parent inputs in mock gcs (#503)
1 parent 3b4cbcf commit 4d09a08

3 files changed

Lines changed: 176 additions & 17 deletions

File tree

tests/act/internal/workflow/mock.go

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ func NoOpStep(originalStep Step) Step {
5252
// The original step must use the `google-github-actions/upload-cloud-storage` action
5353
// and have valid "path" and "destination" inputs.
5454
// If those conditions are not met, an error is returned.
55+
// The "parent" input (optional) is also handled and mimics the behavior of the original action.
56+
// The "glob" input (optional) filters which files to upload using a glob pattern (e.g., "*.txt", "**/*.json").
5557
func MockGCSUploadStep(originalStep Step) (Step, error) {
5658
// Make sure the original step is indeed a GCS upload step
5759
if !strings.HasPrefix(originalStep.Uses, GCSUploadAction) {
@@ -66,26 +68,109 @@ func MockGCSUploadStep(originalStep Step) (Step, error) {
6668
return Step{}, fmt.Errorf("could not mock gcs step %q (id: %q) because inputs are not valid", originalStep.Name, originalStep.ID)
6769
}
6870

69-
return Step{
70-
Name: originalStep.Name + " (mocked)",
71-
Run: Commands{
72-
"set -x",
73-
`mkdir -p /gcs/` + destPath,
74-
"cp -r " + srcPath + " /gcs/" + destPath,
71+
// Glob input is optional, empty string means no filtering (copy all files).
72+
globPattern := ""
73+
if v, ok := originalStep.With["glob"].(string); ok {
74+
globPattern = v
75+
}
76+
77+
// Parent input is optional, default to true.
78+
// If false, the contents of the folder are copied, but not the folder itself.
79+
// If true, the folder (including itself) is copied.
80+
// This mimics the behavior of the original Google Cloud Storage upload action.
81+
parent := true
82+
if v, ok := originalStep.With["parent"].(bool); ok {
83+
parent = v
84+
}
85+
// Handle folder copying behavior:
86+
// - parent=true: copy the folder (including itself), e.g., cp src/folder dest → dest/folder/...
87+
// - parent=false: copy the contents of the folder (without the folder itself), e.g., cp src/folder/. dest → dest/...
88+
var folderCpCmdSuffix string
89+
var folderNameForOutput string
90+
if parent {
91+
// No special handling for cp. Copy the folder itself, recursively.
92+
folderCpCmdSuffix = ""
93+
// Include the folder name in the files list.
94+
// MUST have a trailing slash in the output name (for sed).
95+
folderNameForOutput = filepath.Base(srcPath) + "/"
96+
} else {
97+
// Copy the contents of the folder, without the folder itself.
98+
folderCpCmdSuffix = "/."
99+
// No folder in output name. The content is copied, not the folder itself.
100+
folderNameForOutput = ""
101+
}
75102

76-
// For debugging
77-
"echo 'Mock GCS upload complete. Mock GCS bucket content:'",
78-
"find /gcs -type f",
79-
"cd " + srcPath,
103+
// Build the bash commands based on whether glob is provided
104+
var commands Commands
105+
commands = append(commands,
106+
"set -x",
107+
`mkdir -p /gcs/${DEST_PATH}`,
80108

109+
// Handle both file and directory srcPath
110+
`if [ -f "${SRC_PATH}" ]; then`,
111+
// srcPath is a file: copy directly (glob doesn't apply to single files)
112+
` cp "${SRC_PATH}" /gcs/${DEST_PATH}/`,
113+
` filename=$(basename "${SRC_PATH}")`,
114+
` files="${DEST_PATH}/${filename}"`,
115+
` files=$(echo "$files" | cut -d'/' -f2-)`,
116+
`else`,
117+
// srcPath is a directory
118+
` cd "${SRC_PATH}"`,
119+
)
120+
121+
if globPattern != "" {
122+
// Glob mode: copy only files matching the pattern
123+
// - globstar enables ** to match directories recursively
124+
// - nullglob makes the pattern expand to nothing if no files match (avoids literal pattern in output)
125+
commands = append(commands,
126+
` shopt -s globstar nullglob`,
127+
` files=""`,
128+
` for file in ${GLOB_PATTERN}; do`,
129+
` if [ -f "$file" ]; then`,
130+
// Create target directory preserving structure, then copy the file
131+
` target_dir="/gcs/${DEST_PATH}/${FOLDER_NAME}$(dirname "$file")"`,
132+
` mkdir -p "$target_dir"`,
133+
` cp "$file" "$target_dir/"`,
134+
// Build the files list (comma-separated)
135+
` rel_path="${DEST_PATH}/${FOLDER_NAME}${file}"`,
136+
` rel_path=$(echo "$rel_path" | cut -d'/' -f2-)`,
137+
` if [ -n "$files" ]; then files="$files,"; fi`,
138+
` files="$files$rel_path"`,
139+
` fi`,
140+
` done`,
141+
)
142+
} else {
143+
// Original behavior: copy all files recursively
144+
// if parent is true, copy the folder (including itself)
145+
// if parent is false, copy the contents of the folder (without the folder itself)
146+
commands = append(commands,
147+
` cp -r "${SRC_PATH}`+folderCpCmdSuffix+`" /gcs/${DEST_PATH}`,
81148
// Get a list of all uploaded files, separated by commas.
82-
// Find all files, prepend destPath, remove leading ./, get relative path (remove bucket name after `/gcs`), join with commas
83-
`files=$(find . -type f | sed 's|^\./|` + destPath + `/|' | cut -d'/' -f2- | tr '\n' ',' | sed 's/,$//')`,
149+
// Find all files, prepend destPath (and folder name if parent=true), remove leading ./, get relative path (remove bucket name after `/gcs`), join with commas
150+
` files=$(find . -type f | sed "s|^\./|${DEST_PATH}/`+folderNameForOutput+`|" | cut -d'/' -f2- | tr '\n' ',' | sed 's/,$//')`,
151+
)
152+
}
84153

85-
// Set output (simplified)
86-
`echo "uploaded=$files" >> "$GITHUB_OUTPUT"`,
87-
}.String(),
154+
commands = append(commands,
155+
`fi`,
156+
// For debugging
157+
"echo 'Mock GCS upload complete. Mock GCS bucket content:'",
158+
"find /gcs -type f",
159+
// Set output (simplified)
160+
`echo "uploaded=$files" >> "$GITHUB_OUTPUT"`,
161+
)
162+
163+
return Step{
164+
// TODO: remove so it's handled by mockedName(), in other WIP PR
165+
Name: originalStep.Name + " (mocked)",
166+
Run: commands.String(),
88167
Shell: "bash",
168+
Env: map[string]string{
169+
"SRC_PATH": srcPath,
170+
"DEST_PATH": destPath,
171+
"GLOB_PATTERN": globPattern,
172+
"FOLDER_NAME": folderNameForOutput,
173+
},
89174
}, nil
90175
}
91176

tests/act/internal/workflow/mock_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,80 @@ import (
77
"github.com/stretchr/testify/require"
88
)
99

10+
func TestMockGCSUploadStep(t *testing.T) {
11+
t.Run("basic upload without glob", func(t *testing.T) {
12+
step := Step{
13+
Name: "Upload GCS",
14+
Uses: "google-github-actions/upload-cloud-storage@6397bd7208e18d13ba2619ee21b9873edc94427a",
15+
With: map[string]any{
16+
"path": "/tmp/dist-artifacts",
17+
"destination": "integration-artifacts/grafana-foo-plugin/folder",
18+
},
19+
}
20+
mockedStep, err := MockGCSUploadStep(step)
21+
require.NoError(t, err)
22+
require.Equal(t, "Upload GCS (mocked)", mockedStep.Name)
23+
require.Contains(t, mockedStep.Run, `echo "uploaded=$files" >> "$GITHUB_OUTPUT"`, "should contain echo to output")
24+
require.Contains(t, mockedStep.Run, `mkdir -p /gcs/${DEST_PATH}`, "should contain bucket folder creation")
25+
require.Contains(t, mockedStep.Run, ` cp -r "${SRC_PATH}" /gcs/${DEST_PATH}`, "should contain cp command")
26+
require.NotContains(t, mockedStep.Run, "globstar", "should not use globstar without glob pattern")
27+
require.Equal(t, map[string]string{
28+
"DEST_PATH": "integration-artifacts/grafana-foo-plugin/folder",
29+
"SRC_PATH": "/tmp/dist-artifacts",
30+
"GLOB_PATTERN": "",
31+
"FOLDER_NAME": "dist-artifacts/",
32+
}, mockedStep.Env, "should have correct env vars")
33+
})
34+
35+
t.Run("upload with glob pattern", func(t *testing.T) {
36+
step := Step{
37+
Name: "Upload GCS",
38+
Uses: "google-github-actions/upload-cloud-storage@6397bd7208e18d13ba2619ee21b9873edc94427a",
39+
With: map[string]any{
40+
"path": "/tmp/dist-artifacts",
41+
"destination": "integration-artifacts/grafana-foo-plugin/folder",
42+
"glob": "**/*.zip",
43+
},
44+
}
45+
mockedStep, err := MockGCSUploadStep(step)
46+
require.NoError(t, err)
47+
require.Equal(t, "Upload GCS (mocked)", mockedStep.Name)
48+
require.Contains(t, mockedStep.Run, `echo "uploaded=$files" >> "$GITHUB_OUTPUT"`, "should contain echo to output")
49+
require.Contains(t, mockedStep.Run, `mkdir -p /gcs/${DEST_PATH}`, "should contain bucket folder creation")
50+
require.Contains(t, mockedStep.Run, "shopt -s globstar nullglob", "should enable globstar")
51+
require.Contains(t, mockedStep.Run, `for file in ${GLOB_PATTERN}`, "should iterate over glob matches")
52+
require.NotContains(t, mockedStep.Run, `cp -r "${SRC_PATH}"`, "should not use recursive cp with glob")
53+
require.Equal(t, map[string]string{
54+
"DEST_PATH": "integration-artifacts/grafana-foo-plugin/folder",
55+
"SRC_PATH": "/tmp/dist-artifacts",
56+
"GLOB_PATTERN": "**/*.zip",
57+
"FOLDER_NAME": "dist-artifacts/",
58+
}, mockedStep.Env, "should have correct env vars including glob pattern")
59+
})
60+
61+
t.Run("upload with glob and parent=false", func(t *testing.T) {
62+
step := Step{
63+
Name: "Upload GCS",
64+
Uses: "google-github-actions/upload-cloud-storage@6397bd7208e18d13ba2619ee21b9873edc94427a",
65+
With: map[string]any{
66+
"path": "/tmp/dist-artifacts",
67+
"destination": "integration-artifacts/grafana-foo-plugin/folder",
68+
"glob": "*.txt",
69+
"parent": false,
70+
},
71+
}
72+
mockedStep, err := MockGCSUploadStep(step)
73+
require.NoError(t, err)
74+
require.Contains(t, mockedStep.Run, "shopt -s globstar nullglob", "should enable globstar")
75+
require.Equal(t, map[string]string{
76+
"DEST_PATH": "integration-artifacts/grafana-foo-plugin/folder",
77+
"SRC_PATH": "/tmp/dist-artifacts",
78+
"GLOB_PATTERN": "*.txt",
79+
"FOLDER_NAME": "",
80+
}, mockedStep.Env, "should have empty FOLDER_NAME when parent=false")
81+
})
82+
}
83+
1084
func TestMockVaultSecretsStep(t *testing.T) {
1185
const (
1286
vaultAction = "grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593"

tests/act/main_gcs_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ func TestGCS(t *testing.T) {
8585

8686
// Assert files uploaded to GCS (commit hash)
8787
anyZipFn := anyZipFileName(tc.id, tc.version)
88-
commitBasePath := filepath.Join("integration-artifacts", tc.id, tc.version, "main", commitHash, tc.folder+"-dist-artifacts")
89-
latestBasePath := filepath.Join("integration-artifacts", tc.id, tc.version, "main", "latest", tc.folder+"-dist-artifacts")
88+
commitBasePath := filepath.Join("integration-artifacts", tc.id, tc.version, "main", commitHash)
89+
latestBasePath := filepath.Join("integration-artifacts", tc.id, tc.version, "main", "latest")
9090

9191
// Expect commit hash any zip
9292
expFiles := []string{filepath.Join(commitBasePath, anyZipFn)}

0 commit comments

Comments
 (0)