diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10cb60cf..32807aed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -489,6 +489,7 @@ jobs: shell: bash - name: Replace plugin version + id: replace-plugin-version if: ${{ inputs.plugin-version-suffix != '' }} run: | package_json_path="$PLUGIN_DIRECTORY/package.json" @@ -572,8 +573,8 @@ jobs: if [ -n "${PLUGIN_VALIDATOR_CONFIG}" ]; then # User-provided configuration content echo "Using provided plugin-validator configuration content." - PLUGIN_VALIDATOR_CONFIG_PATH=".plugin-validator.yaml" - echo "${PLUGIN_VALIDATOR_CONFIG}" > ${PLUGIN_VALIDATOR_CONFIG_PATH} + PLUGIN_VALIDATOR_CONFIG_PATH=$(mktemp) + echo "${PLUGIN_VALIDATOR_CONFIG}" > "${PLUGIN_VALIDATOR_CONFIG_PATH}" elif [ -n "${PLUGIN_VALIDATOR_CONFIG_PATH}" ]; then # User-provided configuration file path echo "Using plugin-validator configuration file at path: ${PLUGIN_VALIDATOR_CONFIG_PATH}" @@ -583,8 +584,8 @@ jobs: fi else # Default hardcoded configuration - PLUGIN_VALIDATOR_CONFIG_PATH=".plugin-validator.yaml" echo "${PLUGIN_VALIDATOR_CONFIG_PATH} configuration file is missing. Providing a default one as fallback." + PLUGIN_VALIDATOR_CONFIG_PATH=$(mktemp) cat < "${PLUGIN_VALIDATOR_CONFIG_PATH}" global: enabled: true @@ -598,11 +599,18 @@ jobs: # will recurse into the plugin's directory (including node_modules). mkdir -p /tmp/empty + # Copy workspace to a new tempdir to avoid issues when mounting the folder when running with act + TEMP_WORKSPACE_DIR=$(mktemp -d) + cp -r "${PLUGIN_DIRECTORY}"/* "$TEMP_WORKSPACE_DIR/" + + # Give a UUID to each container so multiple can be run in parallel when running with act + UUID=$(cat /proc/sys/kernel/random/uuid) + # Do not run clamav because it takes too long - docker run --name=plugin-validator --pull=always \ - -v "$PWD/${PLUGIN_VALIDATOR_CONFIG_PATH}:/workspace/.plugin-validator.yaml:ro" \ - -v "$PWD/${PLUGIN_DIRECTORY}/dist-artifacts:/workspace/dist-artifacts:ro" \ - -v "$PWD/${PLUGIN_DIRECTORY}:/workspace" \ + docker run --name="plugin-validator-${UUID}" --pull=always --platform=linux/amd64 \ + -v "${PLUGIN_VALIDATOR_CONFIG_PATH}:/workspace/.plugin-validator.yaml:ro" \ + -v "${TEMP_WORKSPACE_DIR}/dist-artifacts:/workspace/dist-artifacts:ro" \ + -v "${TEMP_WORKSPACE_DIR}:/workspace" \ -v "/tmp/empty:/workspace/node_modules" \ -e SKIP_CLAMAV=1 \ grafana/plugin-validator-cli \ @@ -610,7 +618,9 @@ jobs: -config=/workspace/.plugin-validator.yaml \ -sourceCodeUri=file:///workspace \ "/workspace/dist-artifacts/${UNIVERSAL_ZIP}" - exit "$(docker inspect plugin-validator --format='{{.State.ExitCode}}')" + EXIT_CODE=$(docker inspect "plugin-validator-${UUID}" --format='{{.State.ExitCode}}') + docker rm "plugin-validator-${UUID}" + exit "$EXIT_CODE" env: UNIVERSAL_ZIP: ${{ steps.universal-zip.outputs.zip }} PLUGIN_VALIDATOR_CONFIG: ${{ inputs.plugin-validator-config }} diff --git a/Makefile b/Makefile index b93b314a..7eb29f32 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,11 @@ -.PHONY: clean-mockdata clean-node-modules clean-act-tmp mockdata-dist +.PHONY: reset-mockdata clean-node-modules clean-act-tmp clean-lfs mockdata-dist mockdata-dist-artifacts mockdata clean-node-modules: find tests -name node_modules -type d -prune -exec rm -rf '{}' + +clean-dist: + find tests ! -path '*/act/*' -name dist -type d -prune -exec rm -rf '{}' + + clean-act-tmp: rm -rf /tmp/act-artifacts rm -rf /tmp/act-cache @@ -11,16 +14,25 @@ clean-act-tmp: clean-act-toolcache-volumes: docker volume ls -q | grep "^act-toolcache-" | xargs docker volume rm -clean: clean-node-modules clean-act-tmp clean-act-toolcache-volumes +clean-lfs: + git lfs prune + +clean: clean-node-modules clean-dist clean-act-tmp clean-act-toolcache-volumes clean-lfs -clean-mockdata: +reset-mockdata: rm -rf tests/act/mockdata/dist/* + rm -rf tests/act/mockdata/dist-artifacts-unsigned/* -mockdata-dist: clean-mockdata - ./scripts/mockdata-dist.sh simple-frontend - ./scripts/mockdata-dist.sh simple-frontend-yarn - ./scripts/mockdata-dist.sh simple-frontend-pnpm - ./scripts/mockdata-dist.sh simple-backend +mockdata-dist: reset-mockdata + for tc in $$(./scripts/find-tests.sh); do \ + ./scripts/mockdata-dist.sh $$tc; \ + done @echo All done! +mockdata-dist-artifacts: mockdata-dist + for tc in $$(./scripts/find-tests.sh); do \ + ./scripts/mockdata-dist-artifacts.sh $$tc; \ + done + @echo All done! +mockdata: mockdata-dist-artifacts diff --git a/actions/internal/plugins/package/package.sh b/actions/internal/plugins/package/package.sh index e35dc234..594a255b 100755 --- a/actions/internal/plugins/package/package.sh +++ b/actions/internal/plugins/package/package.sh @@ -101,7 +101,7 @@ fi exe_basename=$(basename $exe) for file in $(find "$backend_folder" -type f -name "${exe_basename}_*"); do # Extract os+arch from the file name - os_arch=$(echo $(basename $file) | sed -E "s|${exe_basename}_(\w+)(.exe)?|\1|") + os_arch=$(echo $(basename $file) | sed -E "s|${exe_basename}_([a-zA-Z0-9_]+)(.exe)?|\1|") # Temporary folder for the zip file tmp=$(mktemp -d) diff --git a/scripts/find-tests.sh b/scripts/find-tests.sh new file mode 100755 index 00000000..a0a1eac0 --- /dev/null +++ b/scripts/find-tests.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -euo pipefail + +# Prints all testdata plugins found in the tests/ folder to stdout, one per line. +# The "act" folder is excluded since it contains the tests themselves. +# Can be used to loop over all test plugins, like this: +# +# for tc in $(./scripts/find-tests.sh); do +# echo $tc +# done + +cd tests +find . -maxdepth 1 -type d ! -name '.' ! -name 'act' -print \ No newline at end of file diff --git a/scripts/mockdata-dist-artifacts.sh b/scripts/mockdata-dist-artifacts.sh new file mode 100755 index 00000000..ad305b1a --- /dev/null +++ b/scripts/mockdata-dist-artifacts.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -euo pipefail + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "[$1] Preparing mockdata (dist-artifacts)" +cd "$(dirname "$0")/.." + + +mkdir -p "tests/act/mockdata/dist-artifacts-unsigned/$1" + +echo "[$1] Packaging os/arch ZIPs" +# Will exit with 0 if the plugin has no backend +# (in that case, there's no need for os/arch ZIPs, just universal) +./actions/internal/plugins/package/package.sh "tests/act/mockdata/dist/$1" "tests/act/mockdata/dist-artifacts-unsigned/$1" + +echo "[$1] Packaging universal ZIPs" +./actions/internal/plugins/package/package.sh -u "tests/act/mockdata/dist/$1" "tests/act/mockdata/dist-artifacts-unsigned/$1" \ No newline at end of file diff --git a/scripts/mockdata-dist.sh b/scripts/mockdata-dist.sh index 4587a6d1..d6e16af8 100755 --- a/scripts/mockdata-dist.sh +++ b/scripts/mockdata-dist.sh @@ -22,7 +22,7 @@ if [ "$#" -ne 1 ]; then exit 1 fi -echo "[$1] Preparing mockdata" +echo "[$1] Preparing mockdata (dist)" cd "$(dirname "$0")/.." cd tests/$1 diff --git a/tests/act/internal/act/act.go b/tests/act/internal/act/act.go index 45220da5..806283f0 100644 --- a/tests/act/internal/act/act.go +++ b/tests/act/internal/act/act.go @@ -58,10 +58,38 @@ type Runner struct { // Verbose enables logging of JSON output from act back to stdout. Verbose bool + + // ContainerArchitecture is the architecture to use for act containers. + // By default, act uses the architecture of the host machine. + // This can be useful to force a specific platform when running on ARM Macs. + ContainerArchitecture string +} + +// RunnerOption is a function that configures a Runner. +type RunnerOption func(r *Runner) + +// WithVerbose enables or disables verbose logging of act output. +func WithVerbose(verbose bool) RunnerOption { + return func(r *Runner) { + r.Verbose = verbose + } +} + +// WithContainerArchitecture sets the container architecture to use for act. +func WithContainerArchitecture(architecture string) RunnerOption { + return func(r *Runner) { + r.ContainerArchitecture = architecture + } +} + +// WithLinuxAMD64ContainerArchitecture sets the container architecture to linux/amd64. +// This is useful when running on ARM Macs to ensure compatibility with x64 images. +func WithLinuxAMD64ContainerArchitecture() RunnerOption { + return WithContainerArchitecture("linux/amd64") } // NewRunner creates a new Runner instance. -func NewRunner(t *testing.T) (*Runner, error) { +func NewRunner(t *testing.T, opts ...RunnerOption) (*Runner, error) { // Get GitHub token from environment (GHA) or gh CLI (local) ghToken, ok := os.LookupEnv("GITHUB_TOKEN") if !ok || ghToken == "" { @@ -86,6 +114,12 @@ func NewRunner(t *testing.T) (*Runner, error) { if err != nil { return nil, fmt.Errorf("new gcs: %w", err) } + + // Apply options + for _, opt := range opts { + opt(r) + } + return r, nil } @@ -113,8 +147,11 @@ func (r *Runner) args(eventKind EventKind, workflowFile string, payloadFile stri // Required for cloning private repos "--secret", "GITHUB_TOKEN=" + r.gitHubToken, - // Mount mockdata (for mocked testdata, dist artifacts) and GCS (for mocked GCS) - "--container-options", `"-v $PWD/tests/act/mockdata:/mockdata -v ` + r.GCS.basePath + `:/gcs"`, + // Mounts: + // - mockdata: for mocked testdata, dist artifacts + // - GCS: for mocked GCS + // - /tmp: for temporary files, so the host's /tmp is used + "--container-options", `"-v $PWD/tests/act/mockdata:/mockdata -v ` + r.GCS.basePath + `:/gcs -v /tmp:/tmp"`, } // Map local all possible references of plugin-ci-workflows to the local repository @@ -127,6 +164,9 @@ func (r *Runner) args(eventKind EventKind, workflowFile string, payloadFile stri if r.ConcurrentJobs > 0 { args = append(args, "--concurrent-jobs", fmt.Sprint(r.ConcurrentJobs)) } + if r.ContainerArchitecture != "" { + args = append(args, "--container-architecture", r.ContainerArchitecture) + } // Map all self-hosted runners otherwise they don't run in act. for _, label := range selfHostedRunnerLabels { args = append(args, "-P", label+"="+nektosActRunnerImage) @@ -296,9 +336,18 @@ func (r *Runner) parseGHACommand(data logLine, runResult *RunResult) { // Store the output value. StepID can be an array in case of composite actions, // group all composite action outputs under the first step ID for simplicity. runResult.Outputs.Set(data.JobID, data.StepID[0], data.Name, data.Arg) + case "debug", "notice", "warning", "error": + // Annotations + runResult.Annotations = append(runResult.Annotations, Annotation{ + Level: AnnotationLevel(data.Command), + Title: data.KvPairs["title"], + Message: data.Arg, + }) default: - // Nothing special to do, ignore silently - break + // Nothing special to do + if r.Verbose && data.Command != "" { + fmt.Printf("%s: [%s]: unhandled GHA command %q, ignoring", r.t.Name(), data.Job, data.Command) + } } } @@ -366,6 +415,32 @@ type RunResult struct { // Outputs contains the outputs for each job + step of the workflow run. Outputs Outputs + + // Annotations contains the GitHub Actions annotations generated during the workflow run. + Annotations []Annotation +} + +// AnnotationLevel represents the level of a GitHub Actions annotation. +type AnnotationLevel string + +// Annotation levels +const ( + AnnotationLevelDebug AnnotationLevel = "debug" + AnnotationLevelNotice AnnotationLevel = "notice" + AnnotationLevelWarning AnnotationLevel = "warning" + AnnotationLevelError AnnotationLevel = "error" +) + +// Annotation represents a single GitHub Actions annotation. +type Annotation struct { + // Level is the level of the annotation. + Level AnnotationLevel + + // Title is the optional title of the annotation. + Title string + + // Message is the message of the annotation itself. + Message string } // newRunResult creates a new empty RunResult instance. diff --git a/tests/act/internal/act/logger.go b/tests/act/internal/act/logger.go index 5f077675..1b6b02bb 100644 --- a/tests/act/internal/act/logger.go +++ b/tests/act/internal/act/logger.go @@ -18,7 +18,8 @@ type logLine struct { // Intercepted GHA commands - Command string `json:"command,omitempty"` - Name string `json:"name,omitempty"` - Arg string `json:"arg,omitempty"` + Command string `json:"command,omitempty"` + Name string `json:"name,omitempty"` + Arg string `json:"arg,omitempty"` + KvPairs map[string]string `json:"kvPairs,omitempty"` } diff --git a/tests/act/internal/workflow/ci.go b/tests/act/internal/workflow/ci.go index ae9ac8a4..8f3a6d93 100644 --- a/tests/act/internal/workflow/ci.go +++ b/tests/act/internal/workflow/ci.go @@ -2,6 +2,7 @@ package workflow import ( "fmt" + "os" "path/filepath" "strings" "testing" @@ -133,6 +134,13 @@ func WithRunPluginValidatorInput(enabled bool) SimpleCIOption { } } +// WithPluginValidatorConfigInput sets the plugin-validator-config input for the CI job in the SimpleCI workflow. +func WithPluginValidatorConfigInput(config string) SimpleCIOption { + return func(w *SimpleCI) { + w.BaseWorkflow.Jobs["ci"].With["plugin-validator-config"] = config + } +} + // WithRunTruffleHogInput sets the run-trufflehog input for the CI job in the SimpleCI workflow. func WithRunTruffleHogInput(enabled bool) SimpleCIOption { return func(w *SimpleCI) { @@ -149,22 +157,101 @@ func WithAllowUnsignedInput(enabled bool) SimpleCIOption { // WithMockedDist modifies the SimpleCI workflow to mock the test-and-build job // to copy pre-built dist files (js + assets + backend executable, NOT the ZIP files) -// from the tests/act/mockdata folder instead of building them. +// from a mockdata folder instead of building them. // This can be used for tests that need to assert on side-effects of building the plugin, // without actually building it, which saves execution time. -// The pluginFolder parameter is the name of the plugin folder inside tests/act/mockdata/dist. -func WithMockedDist(t *testing.T, pluginFolder string) SimpleCIOption { +// The distFolder is relative to tests/act/mockdata (e.g.: `dist/simple-frontend`). +// The distFolder should use slashes as path separators. +// The function will convert it to the correct OS-specific separators when needed. +// The distFolder is sanity-checked to ensure they contain valid data. +func WithMockedDist(t *testing.T, distFolder string) SimpleCIOption { return func(w *SimpleCI) { testAndBuild := w.CIWorkflow().BaseWorkflow.Jobs["test-and-build"] - // require.NoError(t, testAndBuild.RemoveStep("setup")) + distFolder = filepath.FromSlash(distFolder) + + // Sanity check that the folder contains dist files + _, err := os.Stat(filepath.Join(localMockdataPath(distFolder), "plugin.json")) + if err != nil && os.IsNotExist(err) { + require.FailNowf(t, "malformed dist folder", "the specified dist folder %q doesn't seem to contain dist artifacts (plugin.json is missing)", distFolder) + } + require.NoError(t, testAndBuild.ReplaceStep( "frontend", - CopyMockFilesStep("dist/"+pluginFolder, "${{ github.workspace }}/${{ inputs.plugin-directory }}/dist/"), + CopyMockFilesStep(distFolder, "${{ github.workspace }}/${{ inputs.plugin-directory }}/dist/"), )) require.NoError(t, testAndBuild.RemoveStep("backend")) } } +// WithMockedPackagedDistArtifacts modifies the SimpleCI workflow to mock the steps that create +// the packaged dist artifacts (ZIP files) in the test-and-build job to copy pre-packaged ZIP files. +// It also modifies the workflow to mock the dist files using WithMockedDist. +// This way if any further steps need the dist files (e.g. for extracting metadata from plugin.json), they are present. +// The distFolder parameter is the name of the plugin folder inside `mockdata` that contains the dist files (js + assets, etc), not the ZIP file. +// The packagedFolder parameter is the name of the folder inside `mockdata` that contains the pre-packaged ZIP files. +// Both folders are relative to tests/act/mockdata (e.g.: `dist/simple-frontend` and `dist-artifacts-unsigned/simple-frontend`). +// Both folders should use slashes as path separators. +// The function will convert them to the correct OS-specific separators when needed. +// The specified mock folders are sanity-checked to ensure they contain valid data. +func WithMockedPackagedDistArtifacts(t *testing.T, distFolder string, packagedFolder string) SimpleCIOption { + return func(w *SimpleCI) { + // Sanity check that the packaged folder contains ZIP files + packagedFolder = filepath.FromSlash(packagedFolder) + entries, err := os.ReadDir(localMockdataPath(packagedFolder)) + if err != nil { + require.FailNowf(t, "malformed packaged dist folder", "could not read the specified packaged dist folder %q", packagedFolder) + } + hasZip := false + for _, e := range entries { + if !e.IsDir() && strings.HasSuffix(e.Name(), ".zip") { + hasZip = true + break + } + } + if !hasZip { + require.FailNowf(t, "the specified packaged dist folder %q doesn't seem to contain any ZIP files", packagedFolder) + } + + // Mock dist files as well (unpackaged plugin files), so if any steps require the dist files, they are present + WithMockedDist(t, distFolder)(w) + + testAndBuild := w.CIWorkflow().BaseWorkflow.Jobs["test-and-build"] + // Remove unnecessary steps (those that build the plugin) + for _, id := range []string{ + "setup", + "replace-plugin-version", + } { + require.NoError(t, testAndBuild.RemoveStep(id)) + } + + // Mock package steps + dest := "${{ github.workspace }}/${{ inputs.plugin-directory }}/dist-artifacts/" + for i, id := range []string{ + "universal-zip", + "os-arch-zips", + } { + mockStep := CopyMockFilesStep(packagedFolder, dest) + // Set step output + if i == 0 { + // Universal + mockStep.Run += "\n" + Commands{ + // Output ONE zip file, get the name by excluding file names that contain '_' + // (which is used as a separator in os/arch zips) + `echo zip=$(ls -1 ` + dest + `/*.zip | xargs -n 1 basename | grep -v '_') >> "${GITHUB_OUTPUT}"`, + }.String() + } else { + // os/arch + mockStep.Run += "\n" + Commands{ + // Output ALL ZIP files that contains an '_' (separator for os/arch in zip file names) + // as a JSON array + `echo zip=$(ls -1 ` + dest + `/*.zip | xargs -n 1 basename | grep '_' | jq -RncM '[inputs]') >> "${GITHUB_OUTPUT}"`, + }.String() + } + require.NoError(t, testAndBuild.ReplaceStep(id, mockStep)) + } + } +} + // Context represents the mocked workflow context. // It is the JSON payload returned by the "workflow-context" step. type Context struct { diff --git a/tests/act/internal/workflow/mock.go b/tests/act/internal/workflow/mock.go index d3c84355..88201aa2 100644 --- a/tests/act/internal/workflow/mock.go +++ b/tests/act/internal/workflow/mock.go @@ -4,6 +4,7 @@ package workflow import ( "encoding/json" "fmt" + "path/filepath" "strings" ) @@ -19,7 +20,7 @@ const ( // You can use GitHub Actions expressions in destFolder, e.g., "${{ github.workspace }}/plugins/my-plugin/dist". func CopyMockFilesStep(sourceFolder string, destFolder string) Step { return Step{ - Name: "Copy mock dist files", + Name: "Copy mock files", Run: Commands{ "set -x", "mkdir -p " + destFolder, @@ -103,3 +104,9 @@ func MockWorkflowContextStep(ctx Context) (Step, error) { Shell: "bash", }, nil } + +// localMockdataPath returns the full path to a file or folder inside tests/act/mockdata +// used for accessing mock data locally, outside of the act container. +func localMockdataPath(parts ...string) string { + return filepath.Join("tests", "act", "mockdata", filepath.Join(parts...)) +} diff --git a/tests/act/main_gcs_test.go b/tests/act/main_gcs_test.go index 613f93e1..4cb80830 100644 --- a/tests/act/main_gcs_test.go +++ b/tests/act/main_gcs_test.go @@ -55,7 +55,7 @@ func TestGCS(t *testing.T) { workflow.WithRunPluginValidatorInput(false), // Mock dist so we don't spend time building the plugin - workflow.WithMockedDist(t, tc.folder), + workflow.WithMockedDist(t, "dist/"+tc.folder), // Mock a trusted context to enable GCS upload workflow.WithMockedWorkflowContext(t, workflow.Context{ IsTrusted: true, diff --git a/tests/act/main_package_test.go b/tests/act/main_package_test.go index aab0823a..31be8f85 100644 --- a/tests/act/main_package_test.go +++ b/tests/act/main_package_test.go @@ -50,7 +50,7 @@ func TestPackage(t *testing.T) { workflow.WithRunTruffleHogInput(false), // Mock the test-and-build job to copy pre-built dist files - workflow.WithMockedDist(t, tc.folder), + workflow.WithMockedDist(t, "dist/"+tc.folder), ) require.NoError(t, err) diff --git a/tests/act/main_validator_test.go b/tests/act/main_validator_test.go new file mode 100644 index 00000000..a975276d --- /dev/null +++ b/tests/act/main_validator_test.go @@ -0,0 +1,125 @@ +package main + +import ( + "strings" + "testing" + + "github.com/grafana/plugin-ci-workflows/tests/act/internal/act" + "github.com/grafana/plugin-ci-workflows/tests/act/internal/workflow" + "github.com/stretchr/testify/require" +) + +func TestValidator(t *testing.T) { + baseValidatorAnnotations := []act.Annotation{ + { + Level: act.AnnotationLevelWarning, + Title: "plugin-validator: Warning: unsigned plugin", + Message: `This is a new (unpublished) plugin. This is expected during the initial review process. Please allow the review to continue, and a member of our team will inform you when your plugin can be signed.`, + }, { + Level: act.AnnotationLevelWarning, + Title: "plugin-validator: Warning: plugin.json: should include screenshots for the Plugin catalog", + Message: `Screenshots are displayed in the Plugin catalog. Please add at least one screenshot to your plugin.json.`, + }, { + Level: act.AnnotationLevelNotice, + Title: "plugin-validator: Recommendation: You can include a sponsorship link if you want users to support your work", + Message: `Consider to add a sponsorship link in your plugin.json file (Info.Links section: with Name: 'sponsor' or Name: 'sponsorship'), which will be shown on the plugin details page to allow users to support your work if they wish.`, + }, { + Level: act.AnnotationLevelWarning, + Title: "plugin-validator: Warning: plugin.json: description is empty", + Message: `Consider providing a plugin description for better discoverability.`, + }, { + Level: act.AnnotationLevelWarning, + Title: "plugin-validator: Warning: License file contains generic text", + Message: `Your current license file contains generic text from the license template. Please make sure to replace {name of copyright owner} and {yyyy} with the correct values in your LICENSE file.`, + }, + } + for _, tc := range []struct { + name string + sourceFolder string + packagedDistFolder string + + expSuccess bool + expAnnotations []act.Annotation + }{ + { + name: "simple-backend succeeds with warnings", + sourceFolder: "simple-backend", + packagedDistFolder: "dist-artifacts-unsigned/simple-backend", + expSuccess: true, + expAnnotations: baseValidatorAnnotations, + }, + { + name: "simple-frontend-yarn succeeds with warnings", + sourceFolder: "simple-frontend-yarn", + packagedDistFolder: "dist-artifacts-unsigned/simple-frontend-yarn", + expSuccess: true, + expAnnotations: baseValidatorAnnotations, + }, + // Special ZIP where the archive is malformed, used to test plugin-validator error handling + { + name: "simple-frontend-validator-error fails", + sourceFolder: "simple-frontend", + packagedDistFolder: "dist-artifacts-other/simple-frontend-validator-error", + expSuccess: false, + expAnnotations: []act.Annotation{ + { + Level: act.AnnotationLevelError, + Title: "plugin-validator: Error: Archive contains more than one directory", + Message: `Archive should contain only one directory named after plugin id. Found 2 directories. Please see https://grafana.com/developers/plugin-tools/publish-a-plugin/package-a-plugin for more information on how to package a plugin.`, + }, { + Level: act.AnnotationLevelError, + Title: "plugin-validator: Error: Plugin archive is improperly structured", + Message: `It is possible your plugin archive structure is incorrect. Please see https://grafana.com/developers/plugin-tools/publish-a-plugin/package-a-plugin for more information on how to package a plugin.`, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + runner, err := act.NewRunner(t, act.WithLinuxAMD64ContainerArchitecture()) + require.NoError(t, err) + + validatorConfig := ` +global: + enabled: true +analyzers: + osv-scanner: + enabled: false +` + wf, err := workflow.NewSimpleCI( + workflow.WithPluginDirectoryInput("tests/"+tc.sourceFolder), + workflow.WithDistArtifactPrefixInput(tc.sourceFolder+"-"), + + // Disable some features to speed up the test + workflow.WithPlaywrightInput(false), + workflow.WithRunTruffleHogInput(false), + + // Enable the plugin validator (opt-in) + workflow.WithRunPluginValidatorInput(true), + workflow.WithPluginValidatorConfigInput(validatorConfig), + + // Mock dist so we don't spend time building the plugin + workflow.WithMockedPackagedDistArtifacts(t, "dist/"+tc.sourceFolder, tc.packagedDistFolder), + ) + require.NoError(t, err) + + r, err := runner.Run(wf, act.NewEmptyEventPayload()) + require.NoError(t, err) + if tc.expSuccess { + require.True(t, r.Success, "workflow should succeed") + } else { + require.False(t, r.Success, "workflow should fail") + } + + // Check annotation entries + require.Subset(t, r.Annotations, tc.expAnnotations) + var validatorAnnotationCount int + for _, s := range r.Annotations { + if strings.HasPrefix(s.Title, "plugin-validator:") { + validatorAnnotationCount++ + } + } + require.Equal(t, validatorAnnotationCount, len(tc.expAnnotations), "found unexpected plugin-validator gha annotation entries") + }) + } +} diff --git a/tests/act/mockdata/dist-artifacts-other/simple-frontend-validator-error/grafana-simplefrontend-panel-1.0.0.zip b/tests/act/mockdata/dist-artifacts-other/simple-frontend-validator-error/grafana-simplefrontend-panel-1.0.0.zip new file mode 100644 index 00000000..7dd23801 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-other/simple-frontend-validator-error/grafana-simplefrontend-panel-1.0.0.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:936b93339bf1af8d35257bbe7f270a417ac43b6a9a65eae21b3ca47a03d2efba +size 15824 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip new file mode 100644 index 00000000..d38b0743 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6a2d51027d39d725dfa61e64772fd804ebef8e869f8344fd4dbe2752ff7f466 +size 8797466 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip.md5 new file mode 100644 index 00000000..8fca466f --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip.md5 @@ -0,0 +1 @@ +2ee4a68c150cce98597f7f6e4fc6bfe7 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip.sha1 new file mode 100644 index 00000000..1ea0ec80 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_amd64.zip.sha1 @@ -0,0 +1 @@ +139f8f597297359e9d7f2cb84b6c68e6a3d96d08 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip new file mode 100644 index 00000000..0eca53ac --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c93dce1b91720d710d3c2bb5846141099b582c8b5985de2ac6639be402ccd13 +size 8124180 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip.md5 new file mode 100644 index 00000000..6179b1c0 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip.md5 @@ -0,0 +1 @@ +5e8bd24662b4ace934678dd0baf9a997 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip.sha1 new file mode 100644 index 00000000..1d4f2129 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.darwin_arm64.zip.sha1 @@ -0,0 +1 @@ +f56d813b76c3dd4d7798f93cf2c6d42333971ccb \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip new file mode 100644 index 00000000..18455e9d --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba37022f175e189ffcf04103cc6fff513fbba964133dabe117788a9b9887fae9 +size 8557935 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip.md5 new file mode 100644 index 00000000..4a36e795 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip.md5 @@ -0,0 +1 @@ +aa2044f0787aa3a97b0bcbfcf903618f \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip.sha1 new file mode 100644 index 00000000..758183a8 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_amd64.zip.sha1 @@ -0,0 +1 @@ +79d52eb274626461aa312d0c89a06fd4605276bd \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip new file mode 100644 index 00000000..ca7c7fe9 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37238af3626e51f24860fd45775f7dd8231361bcfd3fd349fb64ef0414c9d7e0 +size 8039390 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip.md5 new file mode 100644 index 00000000..f695e579 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip.md5 @@ -0,0 +1 @@ +5b2631779d70144846268fdc690b0196 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip.sha1 new file mode 100644 index 00000000..3ef2e28b --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm.zip.sha1 @@ -0,0 +1 @@ +2cbda746da18fe3534425f86195155f0dd0972a9 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip new file mode 100644 index 00000000..e00ede2d --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dfb827b3fbb261d8f8522f65912652db40143a36db1523fec19fa4682751d9e +size 7712901 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip.md5 new file mode 100644 index 00000000..2b5bf907 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip.md5 @@ -0,0 +1 @@ +f6b5649b692dcc19fef3350ceee09a2f \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip.sha1 new file mode 100644 index 00000000..a36e1cc2 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.linux_arm64.zip.sha1 @@ -0,0 +1 @@ +addb81b127ed6b433174f4e4aa7e8cd6f7fe1d88 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip new file mode 100644 index 00000000..53720298 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a45badbe102d36b6a7acf6a3ce1f0409aa7cbfbda394ec669189143c102a08e8 +size 8768190 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip.md5 new file mode 100644 index 00000000..f365dcc3 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip.md5 @@ -0,0 +1 @@ +cac31f32b5493e209dce8f09275ac547 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip.sha1 new file mode 100644 index 00000000..5badfb2e --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.windows_amd64.zip.sha1 @@ -0,0 +1 @@ +01aa05db0ff0ecc3f72f7b7c458cfa7627eeaf8c \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip new file mode 100644 index 00000000..17398089 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9eb01671ad77a71389d7acc57f2230b331f83575607d9e5abde782daec936457 +size 49928537 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip.md5 new file mode 100644 index 00000000..81a7b637 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip.md5 @@ -0,0 +1 @@ +dcc0acfea56bc3c6ac589f943e3b0041 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip.sha1 new file mode 100644 index 00000000..e49025d8 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-backend/grafana-simplebackend-datasource-1.0.0.zip.sha1 @@ -0,0 +1 @@ +a7d478f18ea602f6ba62eecb9df5b98d194466a7 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip new file mode 100644 index 00000000..fd79c297 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef591ca249385b38a1c4b603bcf5dec7e6f531d15094d48179a302c7408a99e5 +size 12837 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip.md5 new file mode 100644 index 00000000..ca53dab7 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip.md5 @@ -0,0 +1 @@ +cd161ecec6ad7986c5ffa9cc24fe375c \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip.sha1 new file mode 100644 index 00000000..11696702 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-pnpm/grafana-simplefrontendpnpm-panel-1.0.0.zip.sha1 @@ -0,0 +1 @@ +a47159a7b4250d2e8b287e2251730850cb76d3ab \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip new file mode 100644 index 00000000..cfe263d4 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e9125802029ef2454b6922df631c2607456fbdae1bbc3bfa1b2ad4de5116358 +size 12845 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip.md5 new file mode 100644 index 00000000..ef33afe1 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip.md5 @@ -0,0 +1 @@ +a2087578fa58d0b7dd0003e87d2a058f \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip.sha1 new file mode 100644 index 00000000..c5f15053 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend-yarn/grafana-simplefrontendyarn-panel-1.0.0.zip.sha1 @@ -0,0 +1 @@ +fbef15e1267695bd0f2baa37625cdac907de4b16 \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip new file mode 100644 index 00000000..c9c652f2 --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e481845b882cb8f37816a2a522139039f3dfcd53dc56170f26c3d4ec30c1599 +size 12754 diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip.md5 b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip.md5 new file mode 100644 index 00000000..17bda05a --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip.md5 @@ -0,0 +1 @@ +f4e5ded149aa9b1f9d669d0b1ed1e6fe \ No newline at end of file diff --git a/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip.sha1 b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip.sha1 new file mode 100644 index 00000000..bafdb62f --- /dev/null +++ b/tests/act/mockdata/dist-artifacts-unsigned/simple-frontend/grafana-simplefrontend-panel-1.0.0.zip.sha1 @@ -0,0 +1 @@ +eb227856702d4c30ac039b1a5d9a45104f2c7923 \ No newline at end of file