From aa800988561c68362d65e3b79251c8890cb51861 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Mon, 3 Nov 2025 12:53:30 +0100 Subject: [PATCH] Setup GitHub repository --- .github/CODE_OF_CONDUCT.md | 3 + .github/CONTRIBUTING.md | 110 +++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 7 ++ .github/workflows/style.yml | 35 ++++++++++ .github/workflows/test.yml | 40 +++++++++++ .gitignore | 13 ++++ .golangci.yml | 23 +++++++ Makefile | 59 +++++++++++++++++ README.md | 40 +++++++++++ cmd/stackrox-mcp/main.go | 14 ++++ go.mod | 11 ++++ go.sum | 10 +++ internal/logging/logging.go | 39 +++++++++++ internal/logging/logging_test.go | 61 +++++++++++++++++ 14 files changed, 465 insertions(+) create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/style.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/stackrox-mcp/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/logging/logging.go create mode 100644 internal/logging/logging_test.go diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a26685e --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +The Stackrox code of conduct can be found [here](https://stackrox.io/code-conduct). diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..a4653d9 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# Contributing to StackRox MCP + +Thank you for your interest in contributing to StackRox MCP! This document provides guidelines and instructions for contributing to the project. + +## Getting Started + +Before contributing, get the project running locally: + +### Initial Setup + +Clone the repository: +```bash +git clone https://github.com/stackrox/stackrox-mcp.git +cd stackrox-mcp +``` + +Build the project: +```bash +make build +``` + +Run the server: +```bash +./stackrox-mcp +``` + +Once you have the project running, familiarize yourself with the development workflow below. + +## Development Guidelines + +### Code Quality Standards + +All code must pass the following checks before being merged: + +- **Formatting:** Run `make fmt` to format your code +- **Format Check:** Run `make fmt-check` to verify code is formatted +- **Linting:** Run `make lint` to check for style issues +- **Testing:** All tests must pass with `make test` + +These checks are automatically run in CI for all pull requests. + +### Available Make Targets + +View all available make commands: +```bash +make help +``` + +Common development commands: +- `make build` - Build the binary +- `make test` - Run unit tests with coverage +- `make coverage-html` - Generate and view HTML coverage report +- `make fmt` - Format code +- `make fmt-check` - Check code formatting (fails if not formatted) +- `make lint` - Run golangci-lint +- `make clean` - Clean build artifacts and coverage files + +### Testing + +- Write unit tests for all new functionality +- Aim for 80% code coverage +- All error paths should be tested +- Run tests with coverage: + ```bash + make test + ``` +- Generate and view detailed coverage report: + ```bash + make coverage-html + ``` + +## Pull Request Guidelines + +### Creating a PR + +- **Title:** + - The title of your PR should be clear and descriptive. + - It should be short enough to fit into the title box. + - **PR addresses JIRA ticket:** `ROX-1234: Add feature ABC` + - **Otherwise use conventional commit style:** `(): ` + - Types: `fix`, `docs`, `test`, `refactor`, `chore`, `ci` + - Example: `fix(builds): Fix builds for ABC architecture` + +- **Description:** + - Describe the motivation for this change, or why some things were done a certain way. + - Focus on what cannot be extracted from the code, e.g., alternatives considered and dismissed (and why), performance concerns, non-evident edge cases. + +- **Validation:** + - Provide information that can help the PR reviewer test changes and validate they are correct. + - In some cases, it will be sufficient to mention that unit tests are added and they cover the changes. + - In other cases, testing may be more complex, and providing steps on how to set up and test everything will be very valuable for reviewers. + +### Merging a PR + +- Make sure that **all CI statuses are green**. +- Always use `Squash and merge` as the merging mode (default). +- Double-check that the title of the commit ("subject line") is **your PR title**, followed by the PR number prefixed with a `#` in parentheses. +- Merge commit message example: `ROX-1234: Add feature ABC (#5678)`. +- The body of the commit message should be empty. If GitHub pre-populates it, delete it. + +## Code Review Process + +- All PRs require at least one approval before merging +- Address all reviewer comments and suggestions +- Keep PRs focused and reasonably sized +- Respond to feedback in a timely manner + +## License + +By contributing to StackRox MCP, you agree that your contributions will be licensed under the Apache License 2.0. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a09dd44 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +### Description + + + +### Validation + + diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 0000000..de123ad --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,35 @@ +name: Style + +on: + push: + tags: + - '*' + branches: + - main + pull_request: + types: + - opened + - reopened + - synchronize + +jobs: + style: + name: Code Style Checks + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Check code formatting + run: make fmt-check + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: v2.6 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..17c11ca --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: Test + +on: + push: + tags: + - '*' + branches: + - main + pull_request: + types: + - opened + - reopened + - synchronize + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Download dependencies + run: go mod download + + - name: Run tests with coverage + run: make test + +# - name: Upload coverage to Codecov +# uses: codecov/codecov-action@v4 +# with: +# file: ./coverage.out +# token: ${{ secrets.CODECOV_TOKEN }} +# fail_ci_if_error: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67e0a03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# IDE and editor files +.vscode/ +.idea/ +.DS_Store + +# Test coverage output +/*.out + +# Build output +/stackrox-mcp + +# Lint output +/report.xml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..825af29 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,23 @@ +version: "2" +run: + timeout: 240m + go: "1.24" + modules-download-mode: readonly + allow-parallel-runners: true +output: + formats: + text: + path: stdout + junit-xml: + path: report.xml +linters: + default: all + disable: + - wsl + - depguard + - exhaustruct + - paralleltest + - testpackage +issues: + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..de29de0 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +# Default target +.DEFAULT_GOAL := help + +# Binary name +BINARY_NAME=stackrox-mcp + +# Go parameters +GOCMD=go +GOBUILD=$(GOCMD) build +GOTEST=$(GOCMD) test +GOFMT=$(GOCMD) fmt +GOCLEAN=$(GOCMD) clean + +# Coverage files +COVERAGE_OUT=coverage.out + +# Lint files +LINT_OUT=report.xml + +.PHONY: help +help: ## Display this help message + @echo "Available targets:" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' + +.PHONY: build +build: ## Build the binary + $(GOBUILD) -o $(BINARY_NAME) ./cmd/stackrox-mcp + +.PHONY: test +test: ## Run unit tests with coverage + $(GOTEST) -v -cover -coverprofile=$(COVERAGE_OUT) ./... + +.PHONY: coverage-html +coverage-html: test ## Generate and open HTML coverage report + $(GOCMD) tool cover -html=$(COVERAGE_OUT) + +.PHONY: fmt +fmt: ## Format Go code + $(GOFMT) ./... + +.PHONY: fmt-check +fmt-check: ## Check if Go code is formatted (fails if not) + @if [ -n "$$(gofmt -l .)" ]; then \ + echo "The following files are not formatted:"; \ + gofmt -l .; \ + exit 1; \ + fi + +.PHONY: lint +lint: ## Run golangci-lint + go install -v "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6" + golangci-lint run + +.PHONY: clean +clean: ## Clean build artifacts and coverage files + $(GOCLEAN) + rm -f $(BINARY_NAME) + rm -f $(COVERAGE_OUT) + rm -f $(LINT_OUT) diff --git a/README.md b/README.md new file mode 100644 index 0000000..751c98e --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# StackRox MCP + +## Project Overview + +StackRox MCP is a Model Context Protocol (MCP) server that provides AI assistants with access to StackRox. + +## Quick Start + +Clone the repository: +```bash +git clone https://github.com/stackrox/stackrox-mcp.git +cd stackrox-mcp +``` + +Build the project: +```bash +make build +``` + +Run the server: +```bash +./stackrox-mcp +``` + +## Development + +For detailed development guidelines, testing standards, and contribution workflows, see [CONTRIBUTING.md](.github/CONTRIBUTING.md). + +### Quick Reference + +View all available commands: +```bash +make help +``` + +Common commands: +- `make build` - Build the binary +- `make test` - Run tests +- `make fmt` - Format code +- `make lint` - Run linter diff --git a/cmd/stackrox-mcp/main.go b/cmd/stackrox-mcp/main.go new file mode 100644 index 0000000..91e1870 --- /dev/null +++ b/cmd/stackrox-mcp/main.go @@ -0,0 +1,14 @@ +// Package main for stackrox-mcp command. +package main + +import ( + "log/slog" + + "github.com/stackrox/stackrox-mcp/internal/logging" +) + +func main() { + logging.SetupLogging() + + slog.Info("Starting Stackrox MCP server") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b470f12 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/stackrox/stackrox-mcp + +go 1.24 + +require github.com/stretchr/testify v1.11.1 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c4c1710 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 0000000..45a3182 --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,39 @@ +// Package logging provides setting up log level for structured logging. +package logging + +import ( + "log/slog" + "os" + "strings" +) + +// SetupLogging configures the global slog logger with JSON output. +// Log level can be configured via the LOG_LEVEL environment variable. +// Supported values: DEBUG, INFO, WARN, ERROR (case-insensitive). +// Default: INFO. +func SetupLogging() { + // Parse log level from environment variable, default to INFO + logLevel := slog.LevelInfo + + if levelStr := os.Getenv("LOG_LEVEL"); levelStr != "" { + switch strings.ToUpper(levelStr) { + case "DEBUG": + logLevel = slog.LevelDebug + case "INFO": + logLevel = slog.LevelInfo + case "WARN": + logLevel = slog.LevelWarn + case "ERROR": + logLevel = slog.LevelError + default: + slog.Warn("Invalid LOG_LEVEL, defaulting to INFO", "provided", levelStr) + } + } + + // Initialize slog with JSON handler + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: logLevel, + })) + + slog.SetDefault(logger) +} diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go new file mode 100644 index 0000000..6f0ef1d --- /dev/null +++ b/internal/logging/logging_test.go @@ -0,0 +1,61 @@ +package logging + +import ( + "context" + "log/slog" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSetupLogging(t *testing.T) { + // Clear any existing LOG_LEVEL environment variable + require.NoError(t, os.Unsetenv("LOG_LEVEL")) + + SetupLogging() + + // Verify default log level is INFO + handler := slog.Default().Handler() + assert.True(t, handler.Enabled(context.Background(), slog.LevelInfo)) + assert.False(t, handler.Enabled(context.Background(), slog.LevelDebug)) +} + +func TestSetupLoggingWithEnvVar(t *testing.T) { + tests := map[string]struct { + logLevel string + expectedLevel slog.Level + }{ + "DEBUG level": { + logLevel: "DEBUG", + expectedLevel: slog.LevelDebug, + }, + "INFO level": { + logLevel: "INFO", + expectedLevel: slog.LevelInfo, + }, + "WARN level": { + logLevel: "WARN", + expectedLevel: slog.LevelWarn, + }, + "ERROR level": { + logLevel: "ERROR", + expectedLevel: slog.LevelError, + }, + "Invalid level defaults to INFO": { + logLevel: "INVALID", + expectedLevel: slog.LevelInfo, + }, + } + for testName, testCase := range tests { + t.Run(testName, func(innerT *testing.T) { + innerT.Setenv("LOG_LEVEL", testCase.logLevel) + + SetupLogging() + + handler := slog.Default().Handler() + assert.True(innerT, handler.Enabled(context.Background(), testCase.expectedLevel)) + }) + } +}