diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9a2142..3842ee0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,13 +67,15 @@ jobs: - name: Generate build summary run: | - echo "## Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Registry**: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Tags**:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Platforms**: linux/amd64, linux/arm64, linux/ppc64le, linux/s390x" >> $GITHUB_STEP_SUMMARY + { + echo "## Build Summary" + echo "" + echo "**Registry**: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + echo "" + echo "**Tags**:" + echo '```' + echo "${{ steps.meta.outputs.tags }}" + echo '```' + echo "" + echo "**Platforms**: linux/amd64, linux/arm64, linux/ppc64le, linux/s390x" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 51f9fed..3d8210f 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -66,3 +66,9 @@ jobs: - name: Run chart-testing (lint) run: ct lint charts/stackrox-mcp --validate-maintainers=false --all + + - name: Run shellcheck + run: make shell-lint + + - name: Run actionlint + run: make actionlint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 06cfcba..c8875e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: if [ -n "$(git status --porcelain)" ]; then echo "Error: go.mod or go.sum files are not up to date" echo "Modified files:" - git status --porcelain + git diff echo "" echo "Please run 'go mod tidy' in all directories containing go.mod and commit the changes" exit 1 @@ -53,6 +53,6 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: - file: ./coverage.out + files: ./coverage.out token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..b482d0b --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,9 @@ +# Follow external sources (equivalent to -x flag) +external-sources=true + +# Enable all optional checks +enable=all + +# Exclude checks that are too noisy +# SC2312: Consider invoking this command separately to avoid masking its return value +exclude=SC2312 diff --git a/Makefile b/Makefile index e1e468b..b74dcfa 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,17 @@ lint: ## Run golangci-lint go install -v "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6" golangci-lint run +.PHONY: shell-lint +shell-lint: ## Run shellcheck on shell scripts + @echo "Running shellcheck..." + @shellcheck scripts/*.sh e2e-tests/scripts/*.sh + +.PHONY: actionlint +actionlint: ## Run actionlint on GitHub Actions workflows + @echo "Running actionlint..." + @cd e2e-tests/tools && go build -o ../../bin/actionlint github.com/rhysd/actionlint/cmd/actionlint + @./bin/actionlint -color + ############## ## Protobuf ## ############## diff --git a/README.md b/README.md index 1069d35..47f9247 100644 --- a/README.md +++ b/README.md @@ -409,3 +409,23 @@ Common commands: - `make test` - Run tests - `make fmt` - Format code - `make lint` - Run linter + +### Code Style Checks + +The project enforces several code style checks: + +- **Go formatting**: `make fmt-check` (or `make fmt` to auto-fix) +- **Go linting**: `make lint` +- **Shell scripts**: `make shell-lint` +- **GitHub Actions workflows**: `make actionlint` +- **Dockerfile**: `make dockerfile-lint` +- **Helm charts**: `make helm-lint` + +All checks run automatically in CI on pull requests. + +#### Shell Script Guidelines + +- All scripts must pass `shellcheck` with no errors +- Use `set -e` for error handling +- Use `SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"` for directory detection +- Include cleanup traps for temporary resources (see existing scripts for examples) diff --git a/e2e-tests/scripts/build-mcpchecker.sh b/e2e-tests/scripts/build-mcpchecker.sh index 9f846c5..dd7a159 100755 --- a/e2e-tests/scripts/build-mcpchecker.sh +++ b/e2e-tests/scripts/build-mcpchecker.sh @@ -4,9 +4,9 @@ set -e E2E_DIR="$(cd "$(dirname "$0")/.." && pwd)" echo "Building mcpchecker from tool dependencies..." -cd "$E2E_DIR/tools" +cd "${E2E_DIR}/tools" go build -o ../bin/mcpchecker github.com/mcpchecker/mcpchecker/cmd/mcpchecker echo "mcpchecker built successfully" -cd "$E2E_DIR" +cd "${E2E_DIR}" ./bin/mcpchecker help diff --git a/e2e-tests/scripts/run-tests.sh b/e2e-tests/scripts/run-tests.sh index 9dc8d6c..28def09 100755 --- a/e2e-tests/scripts/run-tests.sh +++ b/e2e-tests/scripts/run-tests.sh @@ -2,15 +2,15 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -E2E_DIR="$(dirname "$SCRIPT_DIR")" -ROOT_DIR="$(dirname "$E2E_DIR")" +E2E_DIR="$(dirname "${SCRIPT_DIR}")" +ROOT_DIR="$(dirname "${E2E_DIR}")" # Cleanup function WIREMOCK_WAS_STARTED=false cleanup() { - if [ "$WIREMOCK_WAS_STARTED" = true ]; then + if [[ "${WIREMOCK_WAS_STARTED}" = true ]]; then echo "Stopping WireMock..." - cd "$ROOT_DIR" + cd "${ROOT_DIR}" make mock-stop > /dev/null 2>&1 || true fi } @@ -23,16 +23,16 @@ echo "════════════════════════ echo "" # Load environment variables -if [ -f "$E2E_DIR/.env" ]; then +if [[ -f "${E2E_DIR}/.env" ]]; then echo "Loading environment variables from .env..." # shellcheck source=/dev/null - set -a && source "$E2E_DIR/.env" && set +a + set -a && source "${E2E_DIR}/.env" && set +a fi # Check if WireMock is already running if ! curl -skf https://localhost:8081/__admin/mappings > /dev/null 2>&1; then echo "Starting WireMock mock service..." - cd "$ROOT_DIR" + cd "${ROOT_DIR}" make mock-start WIREMOCK_WAS_STARTED=true else @@ -45,15 +45,15 @@ export STACKROX_MCP__CENTRAL__API_TOKEN="test-token-admin" export STACKROX_MCP__CENTRAL__INSECURE_SKIP_TLS_VERIFY="true" # Check OpenAI API key for judge -if [ -z "$OPENAI_API_KEY" ]; then +if [[ -z "${OPENAI_API_KEY}" ]]; then echo "Warning: OPENAI_API_KEY is not set (needed for LLM judge)" echo "Note: mcpchecker only supports OpenAI-compatible APIs for the judge" fi # Build mcpchecker if not present -if [ ! -f "$E2E_DIR/bin/mcpchecker" ]; then +if [[ ! -f "${E2E_DIR}/bin/mcpchecker" ]]; then echo "mcpchecker binary not found. Building..." - "$SCRIPT_DIR/build-mcpchecker.sh" + "${SCRIPT_DIR}/build-mcpchecker.sh" echo "" fi @@ -65,29 +65,29 @@ export MODEL_NAME="${MODEL_NAME:-gpt-5-nano}" # Set judge environment variables (use OpenAI) export JUDGE_BASE_URL="${JUDGE_BASE_URL:-https://api.openai.com/v1}" -export JUDGE_API_KEY="${JUDGE_API_KEY:-$OPENAI_API_KEY}" +export JUDGE_API_KEY="${JUDGE_API_KEY:-${OPENAI_API_KEY}}" export JUDGE_MODEL_NAME="${JUDGE_MODEL_NAME:-gpt-5-nano}" echo "Configuration:" -echo " Central URL: $STACKROX_MCP__CENTRAL__URL (WireMock)" -echo " Agent: $MODEL_NAME (OpenAI)" -echo " Judge: $JUDGE_MODEL_NAME (OpenAI)" +echo " Central URL: ${STACKROX_MCP__CENTRAL__URL} (WireMock)" +echo " Agent: ${MODEL_NAME} (OpenAI)" +echo " Judge: ${JUDGE_MODEL_NAME} (OpenAI)" echo " MCP Server: stackrox-mcp (via go run)" echo "" # Run mcpchecker -cd "$E2E_DIR/mcpchecker" +cd "${E2E_DIR}/mcpchecker" echo "Running mcpchecker tests..." echo "" EVAL_FILE="eval.yaml" -echo "Using eval file: $EVAL_FILE" -"$E2E_DIR/bin/mcpchecker" check "$EVAL_FILE" +echo "Using eval file: ${EVAL_FILE}" +"${E2E_DIR}/bin/mcpchecker" check "${EVAL_FILE}" EXIT_CODE=$? echo "" -if [ $EXIT_CODE -eq 0 ]; then +if [[ "${EXIT_CODE}" -eq 0 ]]; then echo "══════════════════════════════════════════════════════════" echo " Tests Completed Successfully!" echo "══════════════════════════════════════════════════════════" @@ -95,5 +95,5 @@ else echo "══════════════════════════════════════════════════════════" echo " Tests Failed" echo "══════════════════════════════════════════════════════════" - exit $EXIT_CODE + exit "${EXIT_CODE}" fi diff --git a/e2e-tests/scripts/smoke-test-mock.sh b/e2e-tests/scripts/smoke-test-mock.sh index 12bbd90..2c132e0 100755 --- a/e2e-tests/scripts/smoke-test-mock.sh +++ b/e2e-tests/scripts/smoke-test-mock.sh @@ -2,8 +2,8 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -E2E_DIR="$(dirname "$SCRIPT_DIR")" -ROOT_DIR="$(dirname "$E2E_DIR")" +E2E_DIR="$(dirname "${SCRIPT_DIR}")" +ROOT_DIR="$(dirname "${E2E_DIR}")" echo "══════════════════════════════════════════════════════════" echo " WireMock Integration Smoke Test" @@ -12,14 +12,14 @@ echo "" # Start WireMock echo "1. Starting WireMock..." -cd "$ROOT_DIR" +cd "${ROOT_DIR}" make mock-stop > /dev/null 2>&1 || true make mock-start # Wait for WireMock to be ready echo "" echo "2. Waiting for WireMock to be ready..." -for i in {1..10}; do +for _ in {1..10}; do if nc -z localhost 8081 2>/dev/null; then echo "✓ WireMock is ready" break @@ -30,7 +30,7 @@ done # Test MCP server can connect echo "" echo "3. Testing MCP server connection..." -cd "$ROOT_DIR" +cd "${ROOT_DIR}" # Run MCP server and test a simple tool call timeout 10 bash -c ' @@ -50,11 +50,11 @@ echo "4. Testing WireMock responses..." AUTH_RESULT=$(grpcurl -insecure -H "Authorization: Bearer test-token-admin" \ -d '{}' localhost:8081 v1.ClustersService/GetClusters 2>&1 || true) -if echo "$AUTH_RESULT" | grep -q "clusters"; then +if echo "${AUTH_RESULT}" | grep -q "clusters"; then echo "✓ Authentication works" else echo "✗ Authentication failed" - echo "$AUTH_RESULT" + echo "${AUTH_RESULT}" fi # Test CVE query @@ -62,17 +62,17 @@ CVE_RESULT=$(grpcurl -insecure -H "Authorization: Bearer test-token-admin" \ -d '{"query": "CVE:\"CVE-2021-44228\""}' \ localhost:8081 v1.DeploymentService/ListDeployments 2>&1 || true) -if echo "$CVE_RESULT" | grep -q "deployments"; then +if echo "${CVE_RESULT}" | grep -q "deployments"; then echo "✓ CVE query returns data" else echo "✗ CVE query failed" - echo "$CVE_RESULT" + echo "${CVE_RESULT}" fi # Cleanup echo "" echo "5. Cleaning up..." -cd "$ROOT_DIR" +cd "${ROOT_DIR}" make mock-stop echo "" diff --git a/e2e-tests/scripts/smoke-test.sh b/e2e-tests/scripts/smoke-test.sh index 3022bc8..7ade581 100755 --- a/e2e-tests/scripts/smoke-test.sh +++ b/e2e-tests/scripts/smoke-test.sh @@ -2,7 +2,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -E2E_DIR="$(dirname "$SCRIPT_DIR")" +E2E_DIR="$(dirname "${SCRIPT_DIR}")" echo "══════════════════════════════════════════════════════════" echo " E2E Tests Smoke Test" @@ -11,15 +11,15 @@ echo "" # Step 1: Build mcpchecker binary echo "Step 1/2: Building mcpchecker binary..." -cd "$E2E_DIR/tools" +cd "${E2E_DIR}/tools" go build -o ../bin/mcpchecker github.com/mcpchecker/mcpchecker/cmd/mcpchecker echo "✓ mcpchecker built successfully" echo "" # Step 2: Verify mcpchecker binary works echo "Step 2/2: Verifying mcpchecker binary works..." -"$E2E_DIR/bin/mcpchecker" --version > /dev/null 2>&1 || true -"$E2E_DIR/bin/mcpchecker" help > /dev/null +"${E2E_DIR}/bin/mcpchecker" --version > /dev/null 2>&1 || true +"${E2E_DIR}/bin/mcpchecker" help > /dev/null echo "✓ mcpchecker binary works correctly" echo "" diff --git a/e2e-tests/tools/go.mod b/e2e-tests/tools/go.mod index 03a867e..5e4b7d0 100644 --- a/e2e-tests/tools/go.mod +++ b/e2e-tests/tools/go.mod @@ -5,6 +5,7 @@ go 1.25.5 require ( github.com/fullstorydev/grpcurl v1.9.3 github.com/mcpchecker/mcpchecker v0.0.4 + github.com/rhysd/actionlint v1.7.11 ) require ( @@ -13,6 +14,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/blang/semver v3.5.1+incompatible // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bufbuild/protocompile v0.14.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect @@ -64,12 +66,16 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.17 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect github.com/modelcontextprotocol/go-sdk v1.2.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/openai/openai-go/v2 v2.7.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/protobuf-specs v0.5.0 // indirect @@ -97,6 +103,7 @@ require ( go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp/event v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/exp/jsonrpc2 v0.0.0-20260112195511-716be5621a96 // indirect diff --git a/e2e-tests/tools/go.sum b/e2e-tests/tools/go.sum index 5e36cec..5828876 100644 --- a/e2e-tests/tools/go.sum +++ b/e2e-tests/tools/go.sum @@ -70,6 +70,8 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -266,6 +268,10 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ= +github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mcpchecker/mcpchecker v0.0.4 h1:xpnFI/NRSkEZB6ofixo4dyWurf2RjAlsRCeUkyXQEYs= github.com/mcpchecker/mcpchecker v0.0.4/go.mod h1:IIvKxkVxHN6kOOPe+Hah/oG84Xei5inFRAq3ycshaew= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -290,6 +296,13 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgm github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rhysd/actionlint v1.7.11 h1:m+aSuCpCIClS8X02xMG4Z8s87fCHPsAtYkAoWGQZgEE= +github.com/rhysd/actionlint v1.7.11/go.mod h1:8n50YougV9+50niD7oxgDTZ1KbN/ZnKiQ2xpLFeVhsI= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -399,6 +412,8 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= +go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp/event v0.0.0-20260112195511-716be5621a96 h1:l+bY+u9cx/1NImWfu0OVcMmlK19fFvQEXUrm3c/qj/o= diff --git a/e2e-tests/tools/tools.go b/e2e-tests/tools/tools.go index bd46238..8867bda 100644 --- a/e2e-tests/tools/tools.go +++ b/e2e-tests/tools/tools.go @@ -6,4 +6,5 @@ package tools import ( _ "github.com/fullstorydev/grpcurl/cmd/grpcurl" _ "github.com/mcpchecker/mcpchecker/cmd/mcpchecker" + _ "github.com/rhysd/actionlint/cmd/actionlint" ) diff --git a/go.mod b/go.mod index c40ac72..e8f8db5 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/stretchr/testify v1.11.1 golang.stackrox.io/grpc-http1 v0.5.1 google.golang.org/grpc v1.79.1 + google.golang.org/protobuf v1.36.10 ) require ( @@ -38,7 +39,6 @@ require ( golang.org/x/text v0.32.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/scripts/download-wiremock.sh b/scripts/download-wiremock.sh index 53ede54..62d3e2f 100755 --- a/scripts/download-wiremock.sh +++ b/scripts/download-wiremock.sh @@ -5,14 +5,14 @@ WIREMOCK_VERSION="3.9.1" GRPC_EXTENSION_VERSION="0.8.0" WIREMOCK_DIR="wiremock/lib" -mkdir -p "$WIREMOCK_DIR" +mkdir -p "${WIREMOCK_DIR}" echo "Downloading WireMock standalone JAR..." curl -L "https://repo1.maven.org/maven2/org/wiremock/wiremock-standalone/${WIREMOCK_VERSION}/wiremock-standalone-${WIREMOCK_VERSION}.jar" \ - -o "$WIREMOCK_DIR/wiremock-standalone.jar" + -o "${WIREMOCK_DIR}/wiremock-standalone.jar" echo "Downloading WireMock gRPC extension..." curl -L "https://repo1.maven.org/maven2/org/wiremock/wiremock-grpc-extension-standalone/${GRPC_EXTENSION_VERSION}/wiremock-grpc-extension-standalone-${GRPC_EXTENSION_VERSION}.jar" \ - -o "$WIREMOCK_DIR/wiremock-grpc-extension.jar" + -o "${WIREMOCK_DIR}/wiremock-grpc-extension.jar" -echo "✓ WireMock JARs downloaded to $WIREMOCK_DIR" +echo "✓ WireMock JARs downloaded to ${WIREMOCK_DIR}" diff --git a/scripts/generate-proto-descriptors.sh b/scripts/generate-proto-descriptors.sh index 5b4aac9..8256318 100755 --- a/scripts/generate-proto-descriptors.sh +++ b/scripts/generate-proto-descriptors.sh @@ -6,10 +6,10 @@ ROX_PROTO_PATH="wiremock/proto/stackrox" GOOGLEAPIS_PATH="wiremock/proto/googleapis" GRPC_DIR="wiremock/grpc" -mkdir -p "$DESCRIPTOR_DIR" "$GRPC_DIR" +mkdir -p "${DESCRIPTOR_DIR}" "${GRPC_DIR}" # Ensure proto files are present -if [ ! -d "$ROX_PROTO_PATH" ]; then +if [[ ! -d "${ROX_PROTO_PATH}" ]]; then echo "Proto files not found. Running setup..." ./scripts/setup-proto-files.sh fi @@ -17,25 +17,25 @@ fi # Use PROTOC_BIN if set (from Makefile), otherwise use system protoc PROTOC_CMD="${PROTOC_BIN:-protoc}" -if ! command -v "$PROTOC_CMD" &> /dev/null; then +if ! command -v "${PROTOC_CMD}" &> /dev/null; then echo "Error: protoc is not installed" echo "Install with: make proto-install" echo "Or install manually from: https://grpc.io/docs/protoc-installation/" exit 1 fi -echo "Generating proto descriptors with $PROTOC_CMD..." +echo "Generating proto descriptors with ${PROTOC_CMD}..." -"$PROTOC_CMD" \ - --descriptor_set_out="$DESCRIPTOR_DIR/stackrox.dsc" \ +"${PROTOC_CMD}" \ + --descriptor_set_out="${DESCRIPTOR_DIR}/stackrox.dsc" \ --include_imports \ - --proto_path="$ROX_PROTO_PATH" \ - --proto_path="$GOOGLEAPIS_PATH" \ + --proto_path="${ROX_PROTO_PATH}" \ + --proto_path="${GOOGLEAPIS_PATH}" \ api/v1/deployment_service.proto \ api/v1/image_service.proto \ api/v1/node_service.proto \ api/v1/cluster_service.proto -cp "$DESCRIPTOR_DIR/stackrox.dsc" "$GRPC_DIR/" +cp "${DESCRIPTOR_DIR}/stackrox.dsc" "${GRPC_DIR}/" -echo "✓ Proto descriptors generated at $DESCRIPTOR_DIR/stackrox.dsc" +echo "✓ Proto descriptors generated at ${DESCRIPTOR_DIR}/stackrox.dsc" diff --git a/scripts/setup-proto-files.sh b/scripts/setup-proto-files.sh index ff92e2c..9ce2978 100755 --- a/scripts/setup-proto-files.sh +++ b/scripts/setup-proto-files.sh @@ -9,27 +9,27 @@ go mod download # Discover rox module location using go list ROX_DIR=$(go list -f '{{.Dir}}' -m github.com/stackrox/rox) -if [ -z "$ROX_DIR" ]; then +if [[ -z "${ROX_DIR}" ]]; then echo "Error: github.com/stackrox/rox module not found" echo "Run: go mod download" exit 1 fi -echo "Using proto files from: $ROX_DIR" +echo "Using proto files from: ${ROX_DIR}" # Create target directories mkdir -p wiremock/proto/stackrox wiremock/proto/googleapis # Copy proto files from rox module # Note: Files from go mod cache are read-only, so we copy and chmod -cp -r "$ROX_DIR/proto/"* wiremock/proto/stackrox/ -cp -r "$ROX_DIR/third_party/googleapis/"* wiremock/proto/googleapis/ +cp -r "${ROX_DIR}/proto/"* wiremock/proto/stackrox/ +cp -r "${ROX_DIR}/third_party/googleapis/"* wiremock/proto/googleapis/ # Copy scanner protos from scanner module (following stackrox pattern) SCANNER_DIR=$(go list -f '{{.Dir}}' -m github.com/stackrox/scanner) -if [ -n "$SCANNER_DIR" ] && [ -d "$SCANNER_DIR/proto/scanner" ]; then - echo "Using scanner proto files from: $SCANNER_DIR" - cp -r "$SCANNER_DIR/proto/scanner" wiremock/proto/stackrox/ +if [[ -n "${SCANNER_DIR}" ]] && [[ -d "${SCANNER_DIR}/proto/scanner" ]]; then + echo "Using scanner proto files from: ${SCANNER_DIR}" + cp -r "${SCANNER_DIR}/proto/scanner" wiremock/proto/stackrox/ fi echo "✓ Proto files copied from go mod cache" diff --git a/scripts/smoke-test-wiremock.sh b/scripts/smoke-test-wiremock.sh index c0cc012..02f758c 100755 --- a/scripts/smoke-test-wiremock.sh +++ b/scripts/smoke-test-wiremock.sh @@ -2,7 +2,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")" echo "=== WireMock Smoke Test ===" @@ -15,13 +15,15 @@ TESTS_FAILED=0 # Create temp directory for test artifacts TEMP_DIR=$(mktemp -d) -echo "Using temp directory: $TEMP_DIR" +echo "Using temp directory: ${TEMP_DIR}" +# False positive: cleanup is invoked via trap, shellcheck doesn't detect indirect calls +# shellcheck disable=SC2317 cleanup() { echo "" echo "Cleaning up..." - "$PROJECT_ROOT/scripts/stop-mock-central.sh" 2>/dev/null || true - rm -rf "$TEMP_DIR" + "${PROJECT_ROOT}/scripts/stop-mock-central.sh" 2>/dev/null || true + rm -rf "${TEMP_DIR}" } trap cleanup EXIT @@ -30,8 +32,8 @@ run_test() { local test_name="$1" local test_command="$2" - echo -n "Testing: $test_name... " - if eval "$test_command" > /dev/null 2>&1; then + echo -n "Testing: ${test_name}... " + if eval "${test_command}" > /dev/null 2>&1; then echo -e "${GREEN}✓${NC}" TESTS_PASSED=$((TESTS_PASSED + 1)) else @@ -44,30 +46,36 @@ run_test() { echo "" echo "Setup..." -if [ ! -f "$PROJECT_ROOT/wiremock/lib/wiremock-standalone.jar" ]; then - "$PROJECT_ROOT/scripts/download-wiremock.sh" +if [[ ! -f "${PROJECT_ROOT}/wiremock/lib/wiremock-standalone.jar" ]]; then + "${PROJECT_ROOT}/scripts/download-wiremock.sh" fi -if [ ! -f "$PROJECT_ROOT/wiremock/proto/descriptors/stackrox.dsc" ]; then - "$PROJECT_ROOT/scripts/generate-proto-descriptors.sh" +if [[ ! -f "${PROJECT_ROOT}/wiremock/proto/descriptors/stackrox.dsc" ]]; then + "${PROJECT_ROOT}/scripts/generate-proto-descriptors.sh" fi echo "" echo "Starting WireMock..." -"$PROJECT_ROOT/scripts/start-mock-central.sh" +"${PROJECT_ROOT}/scripts/start-mock-central.sh" echo "" -run_test "WireMock is running" "make -C '$PROJECT_ROOT' mock-status | grep -q 'running'" || true +# False positive: || true is intentional for test runner to continue on failures +# shellcheck disable=SC2310 +run_test "WireMock is running" "make -C '${PROJECT_ROOT}' mock-status | grep -q 'running'" || true +# shellcheck disable=SC2310 run_test "Admin API responds" "curl -skf https://localhost:8081/__admin/mappings > /dev/null" || true +# shellcheck disable=SC2310 run_test "Rejects missing auth" "curl -sk -X POST -H 'Content-Type: application/json' -d '{}' https://localhost:8081/v1.DeploymentService/ListDeployments | grep -q '\"code\":16'" || true +# shellcheck disable=SC2310 run_test "Returns CVE-2021-44228 data" "curl -skf -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer test-token-admin' -d '{\"query\":\"CVE:\\\"CVE-2021-44228\\\"\"}' https://localhost:8081/v1.DeploymentService/ListDeployments | grep -q 'dep-004'" || true +# shellcheck disable=SC2310 run_test "Returns empty for unknown CVE" "curl -skf -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer test-token-admin' -d '{}' https://localhost:8081/v1.DeploymentService/ListDeployments | grep -q '\"deployments\": \[\]'" || true echo "" echo "Testing MCP integration..." -if [ ! -f "$PROJECT_ROOT/stackrox-mcp" ]; then - make -C "$PROJECT_ROOT" build > /dev/null 2>&1 +if [[ ! -f "${PROJECT_ROOT}/stackrox-mcp" ]]; then + make -C "${PROJECT_ROOT}" build > /dev/null 2>&1 fi export STACKROX_MCP__SERVER__TYPE=stdio @@ -77,18 +85,21 @@ export STACKROX_MCP__CENTRAL__API_TOKEN=test-token-admin export STACKROX_MCP__CENTRAL__INSECURE_SKIP_TLS_VERIFY=true export STACKROX_MCP__TOOLS__VULNERABILITY__ENABLED=true -echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' > "$TEMP_DIR/input.json" +echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' > "${TEMP_DIR}/input.json" -timeout 3 "$PROJECT_ROOT/stackrox-mcp" < "$TEMP_DIR/input.json" > "$TEMP_DIR/stdout.log" 2>"$TEMP_DIR/stderr.log" || true +timeout 3 "${PROJECT_ROOT}/stackrox-mcp" < "${TEMP_DIR}/input.json" > "${TEMP_DIR}/stdout.log" 2>"${TEMP_DIR}/stderr.log" || true -run_test "MCP starts with WireMock" "grep -q 'Starting StackRox MCP server' '$TEMP_DIR/stderr.log'" || true -run_test "MCP registers tools" "grep -q 'get_deployments_for_cve' '$TEMP_DIR/stderr.log'" || true +# False positive: || true is intentional for test runner to continue on failures +# shellcheck disable=SC2310 +run_test "MCP starts with WireMock" "grep -q 'Starting StackRox MCP server' '${TEMP_DIR}/stderr.log'" || true +# shellcheck disable=SC2310 +run_test "MCP registers tools" "grep -q 'get_deployments_for_cve' '${TEMP_DIR}/stderr.log'" || true echo "" -if [ $TESTS_FAILED -eq 0 ]; then - echo -e "${GREEN}✓ All $TESTS_PASSED tests passed${NC}" +if [[ "${TESTS_FAILED}" -eq 0 ]]; then + echo -e "${GREEN}✓ All ${TESTS_PASSED} tests passed${NC}" exit 0 else - echo -e "${RED}✗ $TESTS_FAILED/$((TESTS_PASSED + TESTS_FAILED)) tests failed${NC}" + echo -e "${RED}✗ ${TESTS_FAILED}/$((TESTS_PASSED + TESTS_FAILED)) tests failed${NC}" exit 1 fi diff --git a/scripts/start-mock-central.sh b/scripts/start-mock-central.sh index e8ab5c3..f68e4ef 100755 --- a/scripts/start-mock-central.sh +++ b/scripts/start-mock-central.sh @@ -2,41 +2,41 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")" -WIREMOCK_DIR="$PROJECT_ROOT/wiremock" -PID_FILE="$WIREMOCK_DIR/wiremock.pid" -LOG_FILE="$WIREMOCK_DIR/wiremock.log" +WIREMOCK_DIR="${PROJECT_ROOT}/wiremock" +PID_FILE="${WIREMOCK_DIR}/wiremock.pid" +LOG_FILE="${WIREMOCK_DIR}/wiremock.log" -if [ -f "$PID_FILE" ]; then - PID=$(cat "$PID_FILE") - if ps -p "$PID" > /dev/null 2>&1; then - echo "WireMock is already running (PID: $PID)" +if [[ -f "${PID_FILE}" ]]; then + PID=$(cat "${PID_FILE}") + if ps -p "${PID}" > /dev/null 2>&1; then + echo "WireMock is already running (PID: ${PID})" exit 0 fi - rm "$PID_FILE" + rm "${PID_FILE}" fi -if [ ! -f "$WIREMOCK_DIR/lib/wiremock-standalone.jar" ]; then - "$PROJECT_ROOT/scripts/download-wiremock.sh" +if [[ ! -f "${WIREMOCK_DIR}/lib/wiremock-standalone.jar" ]]; then + "${PROJECT_ROOT}/scripts/download-wiremock.sh" fi -if [ ! -f "$WIREMOCK_DIR/proto/descriptors/stackrox.dsc" ]; then - "$PROJECT_ROOT/scripts/generate-proto-descriptors.sh" +if [[ ! -f "${WIREMOCK_DIR}/proto/descriptors/stackrox.dsc" ]]; then + "${PROJECT_ROOT}/scripts/generate-proto-descriptors.sh" fi # Create __files symlink if needed (WireMock expects this) -if [ ! -L "$WIREMOCK_DIR/__files" ]; then - cd "$WIREMOCK_DIR" +if [[ ! -L "${WIREMOCK_DIR}/__files" ]]; then + cd "${WIREMOCK_DIR}" ln -s fixtures __files - cd "$PROJECT_ROOT" + cd "${PROJECT_ROOT}" fi echo "Starting WireMock with TLS..." # Use subshell to avoid having to cd back ( -cd "$WIREMOCK_DIR" +cd "${WIREMOCK_DIR}" java -cp "lib/wiremock-standalone.jar:lib/wiremock-grpc-extension.jar" \ wiremock.Run \ --port 8080 \ @@ -51,14 +51,14 @@ java -cp "lib/wiremock-standalone.jar:lib/wiremock-grpc-extension.jar" \ > wiremock.log 2>&1 & WIREMOCK_PID=$! -echo $WIREMOCK_PID > wiremock.pid +echo "${WIREMOCK_PID}" > wiremock.pid ) # Wait for WireMock to be ready echo "Waiting for WireMock to be ready..." MAX_WAIT=30 WAITED=0 -while [ $WAITED -lt $MAX_WAIT ]; do +while [[ "${WAITED}" -lt "${MAX_WAIT}" ]]; do if curl -skf https://localhost:8081/__admin/mappings > /dev/null 2>&1; then break fi @@ -66,22 +66,22 @@ while [ $WAITED -lt $MAX_WAIT ]; do WAITED=$((WAITED + 1)) done -if [ $WAITED -eq $MAX_WAIT ]; then - echo "✗ WireMock failed to start within ${MAX_WAIT}s. Check $LOG_FILE" +if [[ "${WAITED}" -eq "${MAX_WAIT}" ]]; then + echo "✗ WireMock failed to start within ${MAX_WAIT}s. Check ${LOG_FILE}" exit 1 fi # Read PID from file (written inside subshell) -if [ -f "$PID_FILE" ]; then - WIREMOCK_PID=$(cat "$PID_FILE") - if ps -p "$WIREMOCK_PID" > /dev/null 2>&1; then - echo "✓ WireMock started (PID: $WIREMOCK_PID) on https://localhost:8081" +if [[ -f "${PID_FILE}" ]]; then + WIREMOCK_PID=$(cat "${PID_FILE}") + if ps -p "${WIREMOCK_PID}" > /dev/null 2>&1; then + echo "✓ WireMock started (PID: ${WIREMOCK_PID}) on https://localhost:8081" else - echo "✗ Failed to start WireMock. Check $LOG_FILE" - rm "$PID_FILE" + echo "✗ Failed to start WireMock. Check ${LOG_FILE}" + rm "${PID_FILE}" exit 1 fi else - echo "✗ Failed to start WireMock. PID file not created. Check $LOG_FILE" + echo "✗ Failed to start WireMock. PID file not created. Check ${LOG_FILE}" exit 1 fi diff --git a/scripts/stop-mock-central.sh b/scripts/stop-mock-central.sh index 7cf5f8a..8b8fd00 100755 --- a/scripts/stop-mock-central.sh +++ b/scripts/stop-mock-central.sh @@ -2,27 +2,27 @@ PID_FILE="wiremock/wiremock.pid" -if [ ! -f "$PID_FILE" ]; then +if [[ ! -f "${PID_FILE}" ]]; then echo "WireMock is not running" exit 0 fi -PID=$(cat "$PID_FILE") +PID=$(cat "${PID_FILE}") -if ps -p "$PID" > /dev/null 2>&1; then - kill "$PID" - for i in {1..10}; do - if ! ps -p "$PID" > /dev/null 2>&1; then +if ps -p "${PID}" > /dev/null 2>&1; then + kill "${PID}" + for _ in {1..10}; do + if ! ps -p "${PID}" > /dev/null 2>&1; then break fi sleep 1 done - if ps -p "$PID" > /dev/null 2>&1; then - kill -9 "$PID" + if ps -p "${PID}" > /dev/null 2>&1; then + kill -9 "${PID}" fi echo "✓ WireMock stopped" else echo "WireMock process not found" fi -rm "$PID_FILE" +rm "${PID_FILE}"