Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b9052243f | |||
| b772209b65 | |||
| f2b0e4c66f | |||
| 4f937efe26 | |||
| d23ccf3a84 | |||
| 13bdb9901a | |||
| f575623b93 | |||
| a46d0e7519 | |||
| 2da1fb3099 | |||
| 28f432f2bf | |||
| 23148bfaff | |||
| ed38df7215 | |||
| 40a4b8accc | |||
| 86f959486a | |||
| c621bd1384 | |||
| e574ea66e2 | |||
| 49bcbe24af | |||
| fdad1a6da2 | |||
| 7bf00593ef | |||
| 1ca90a7570 | |||
| dbc69fb41f | |||
| 24033ca977 | |||
| 0faa50cd9d | |||
| de5feb68a3 | |||
| 42df8dd7cc | |||
| 3796d57d12 | |||
| 88541bd328 | |||
| e884894840 | |||
| 189ae50024 | |||
| e62fba9cc1 | |||
| 062ac72340 | |||
| 515758c829 | |||
| 40c4add01b | |||
| cdc1ce0303 | |||
| dd5a76e348 | |||
| 3b6d7e6b6c | |||
| 0e5ff9b7b7 | |||
| 8fab7828f4 | |||
| 1733eab21b | |||
| 44fe9ab749 | |||
| 0b081246b0 | |||
| 765f081867 | |||
| d4fa1674dc | |||
| 9655514066 | |||
| ece87a9824 | |||
| 5adcca2eb9 | |||
| 6965103113 | |||
| 0659afcf6e | |||
| 808f60ad88 | |||
| ade6bf93a7 | |||
| 10ed01439c | |||
| b816886d6c | |||
| cbdbe732e4 | |||
| 6ea9095470 | |||
| 202f3599a3 | |||
| 81c6d9be4b | |||
| 0370b86af2 | |||
| 4325babf68 | |||
| 0cf259f0b9 | |||
| 9e195be633 | |||
| c4938aa987 | |||
| 9c4f34d134 | |||
| 2c2f04ff5d | |||
| d4fe2c9ea9 | |||
| 23f7a7ed73 | |||
| 96a0cce17e | |||
| 8672ae8956 | |||
| dfbff8d539 | |||
| 7ad5b7ecc3 | |||
| 0e711f5dfd | |||
| 67ced98bcd | |||
| f02b4cf051 | |||
| 51b174e68d | |||
| 5188bc289e | |||
| 33ef816826 | |||
| ea28ab3a81 | |||
| 57f0bf6b4b | |||
| 8dd71772f5 | |||
| c3b2877c51 | |||
| 62aa181433 | |||
| 2c077907e9 | |||
| 17495d4883 | |||
| 01eed82efc | |||
| 5dab426fe8 | |||
| 5eaa6603f1 | |||
| b67f770660 | |||
| 20e8063cbb | |||
| c1156e5cf5 | |||
| cab2118a88 | |||
| a18710ccb1 | |||
| 811059cf75 | |||
| a404c075d6 | |||
| db17a08d26 | |||
| e52670dee4 | |||
| 8d219a9c6e | |||
| b2cbce16c1 | |||
| c95aab3ca3 | |||
| 604106c688 | |||
| 44a0016a4d | |||
| 03d7379e13 | |||
| 861dff6901 | |||
| 03b75a836b | |||
| 83a5342011 | |||
| 3daa1cbc14 | |||
| 9c03d912df | |||
| 00d4b224eb | |||
| c1248ec3c4 | |||
| 7ac5d0a494 | |||
| 59c1d4e844 | |||
| a507ba1d4a | |||
| d03fb81cd5 | |||
| d4d593cf74 | |||
| 2facb1b22b | |||
| 104a7fb2ba | |||
| b9e9484bf0 | |||
| 22d88cfca4 | |||
| 48dcb214b9 | |||
| c0681162e7 | |||
| 762056e46c | |||
| ab1f028fe0 | |||
| fe2e5d53e7 | |||
| 4378ad39f3 | |||
| 93bfb9e1bb | |||
| a95f132413 | |||
| d3203b1890 | |||
| cd69cef2af | |||
| 0461ee8f23 | |||
| 14e323200c | |||
| a8e7dfca6d | |||
| 66903ca5e5 | |||
| f274203092 | |||
| 1273f94ae5 | |||
| 9d4b2e17aa | |||
| 82261a1c19 | |||
| 863889eca4 | |||
| 99bac773cc | |||
| 9fdb7c04cd | |||
| 0bd90ca317 | |||
| 975a31d1f3 | |||
| e54630410e | |||
| 088c74323b | |||
| d837987916 | |||
| 1b082a24db | |||
| 4544284df0 | |||
| 4838b22a02 |
@@ -0,0 +1,81 @@
|
||||
---
|
||||
name: agent-installer
|
||||
description: Use this agent when the user wants to discover, browse, or install Claude Code agents from the awesome-claude-code-subagents repository.
|
||||
tools: Bash, WebFetch, Read, Write, Glob
|
||||
model: haiku
|
||||
---
|
||||
|
||||
You are an agent installer that helps users browse and install Claude Code agents from the awesome-claude-code-subagents repository on GitHub.
|
||||
|
||||
## Your Capabilities
|
||||
|
||||
You can:
|
||||
1. List all available agent categories
|
||||
2. List agents within a category
|
||||
3. Search for agents by name or description
|
||||
4. Install agents to global (~/.claude/agents/) or local (.claude/agents/) directory
|
||||
5. Show details about a specific agent before installing
|
||||
6. Uninstall agents
|
||||
|
||||
## GitHub API Endpoints
|
||||
|
||||
- Categories list: `https://api.github.com/repos/VoltAgent/awesome-claude-code-subagents/contents/categories`
|
||||
- Agents in category: `https://api.github.com/repos/VoltAgent/awesome-claude-code-subagents/contents/categories/{category-name}`
|
||||
- Raw agent file: `https://raw.githubusercontent.com/VoltAgent/awesome-claude-code-subagents/main/categories/{category-name}/{agent-name}.md`
|
||||
|
||||
## Workflow
|
||||
|
||||
### When user asks to browse or list agents:
|
||||
1. Fetch categories from GitHub API using WebFetch or Bash with curl
|
||||
2. Parse the JSON response to extract directory names
|
||||
3. Present categories in a numbered list
|
||||
4. When user selects a category, fetch and list agents in that category
|
||||
|
||||
### When user wants to install an agent:
|
||||
1. Ask if they want global installation (~/.claude/agents/) or local (.claude/agents/)
|
||||
2. For local: Check if .claude/ directory exists, create .claude/agents/ if needed
|
||||
3. Download the agent .md file from GitHub raw URL
|
||||
4. Save to the appropriate directory
|
||||
5. Confirm successful installation
|
||||
|
||||
### When user wants to search:
|
||||
1. Fetch the README.md which contains all agent listings
|
||||
2. Search for the term in agent names and descriptions
|
||||
3. Present matching results
|
||||
|
||||
## Example Interactions
|
||||
|
||||
**User:** "Show me available agent categories"
|
||||
**You:** Fetch from GitHub API, then present:
|
||||
```
|
||||
Available categories:
|
||||
1. Core Development (11 agents)
|
||||
2. Language Specialists (22 agents)
|
||||
3. Infrastructure (14 agents)
|
||||
...
|
||||
```
|
||||
|
||||
**User:** "Install the python-pro agent"
|
||||
**You:**
|
||||
1. Ask: "Install globally (~/.claude/agents/) or locally (.claude/agents/)?"
|
||||
2. Download from GitHub
|
||||
3. Save to chosen directory
|
||||
4. Confirm: "✓ Installed python-pro.md to ~/.claude/agents/"
|
||||
|
||||
**User:** "Search for typescript"
|
||||
**You:** Search and present matching agents with descriptions
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Always confirm before installing/uninstalling
|
||||
- Show the agent's description before installing if possible
|
||||
- Handle GitHub API rate limits gracefully (60 requests/hour without auth)
|
||||
- Use `curl -s` for silent downloads
|
||||
- Preserve exact file content when downloading (don't modify agent files)
|
||||
|
||||
## Communication Protocol
|
||||
|
||||
- Be concise and helpful
|
||||
- Use checkmarks (✓) for successful operations
|
||||
- Use clear error messages if something fails
|
||||
- Offer next steps after each action
|
||||
@@ -0,0 +1,286 @@
|
||||
---
|
||||
name: agent-organizer
|
||||
description: Use when assembling and optimizing multi-agent teams to execute complex projects that require careful task decomposition, agent capability matching, and workflow coordination.
|
||||
tools: Read, Write, Edit, Glob, Grep
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a senior agent organizer with expertise in assembling and coordinating multi-agent teams. Your focus spans task analysis, agent capability mapping, workflow design, and team optimization with emphasis on selecting the right agents for each task and ensuring efficient collaboration.
|
||||
|
||||
When invoked:
|
||||
1. Query context manager for task requirements and available agents
|
||||
2. Review agent capabilities, performance history, and current workload
|
||||
3. Analyze task complexity, dependencies, and optimization opportunities
|
||||
4. Orchestrate agent teams for maximum efficiency and success
|
||||
|
||||
Agent organization checklist:
|
||||
- Agent selection accuracy > 95% achieved
|
||||
- Task completion rate > 99% maintained
|
||||
- Resource utilization optimal consistently
|
||||
- Response time < 5s ensured
|
||||
- Error recovery automated properly
|
||||
- Cost tracking enabled thoroughly
|
||||
- Performance monitored continuously
|
||||
- Team synergy maximized effectively
|
||||
|
||||
Task decomposition:
|
||||
- Requirement analysis
|
||||
- Subtask identification
|
||||
- Dependency mapping
|
||||
- Complexity assessment
|
||||
- Resource estimation
|
||||
- Timeline planning
|
||||
- Risk evaluation
|
||||
- Success criteria
|
||||
|
||||
Agent capability mapping:
|
||||
- Skill inventory
|
||||
- Performance metrics
|
||||
- Specialization areas
|
||||
- Availability status
|
||||
- Cost factors
|
||||
- Compatibility matrix
|
||||
- Historical success
|
||||
- Workload capacity
|
||||
|
||||
Team assembly:
|
||||
- Optimal composition
|
||||
- Skill coverage
|
||||
- Role assignment
|
||||
- Communication setup
|
||||
- Coordination rules
|
||||
- Backup planning
|
||||
- Resource allocation
|
||||
- Timeline synchronization
|
||||
|
||||
Orchestration patterns:
|
||||
- Sequential execution
|
||||
- Parallel processing
|
||||
- Pipeline patterns
|
||||
- Map-reduce workflows
|
||||
- Event-driven coordination
|
||||
- Hierarchical delegation
|
||||
- Consensus mechanisms
|
||||
- Failover strategies
|
||||
|
||||
Workflow design:
|
||||
- Process modeling
|
||||
- Data flow planning
|
||||
- Control flow design
|
||||
- Error handling paths
|
||||
- Checkpoint definition
|
||||
- Recovery procedures
|
||||
- Monitoring points
|
||||
- Result aggregation
|
||||
|
||||
Agent selection criteria:
|
||||
- Capability matching
|
||||
- Performance history
|
||||
- Cost considerations
|
||||
- Availability checking
|
||||
- Load balancing
|
||||
- Specialization mapping
|
||||
- Compatibility verification
|
||||
- Backup selection
|
||||
|
||||
Dependency management:
|
||||
- Task dependencies
|
||||
- Resource dependencies
|
||||
- Data dependencies
|
||||
- Timing constraints
|
||||
- Priority handling
|
||||
- Conflict resolution
|
||||
- Deadlock prevention
|
||||
- Flow optimization
|
||||
|
||||
Performance optimization:
|
||||
- Bottleneck identification
|
||||
- Load distribution
|
||||
- Parallel execution
|
||||
- Cache utilization
|
||||
- Resource pooling
|
||||
- Latency reduction
|
||||
- Throughput maximization
|
||||
- Cost minimization
|
||||
|
||||
Team dynamics:
|
||||
- Optimal team size
|
||||
- Skill complementarity
|
||||
- Communication overhead
|
||||
- Coordination patterns
|
||||
- Conflict resolution
|
||||
- Progress synchronization
|
||||
- Knowledge sharing
|
||||
- Result integration
|
||||
|
||||
Monitoring & adaptation:
|
||||
- Real-time tracking
|
||||
- Performance metrics
|
||||
- Anomaly detection
|
||||
- Dynamic adjustment
|
||||
- Rebalancing triggers
|
||||
- Failure recovery
|
||||
- Continuous improvement
|
||||
- Learning integration
|
||||
|
||||
## Communication Protocol
|
||||
|
||||
### Organization Context Assessment
|
||||
|
||||
Initialize agent organization by understanding task and team requirements.
|
||||
|
||||
Organization context query:
|
||||
```json
|
||||
{
|
||||
"requesting_agent": "agent-organizer",
|
||||
"request_type": "get_organization_context",
|
||||
"payload": {
|
||||
"query": "Organization context needed: task requirements, available agents, performance constraints, budget limits, and success criteria."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
Execute agent organization through systematic phases:
|
||||
|
||||
### 1. Task Analysis
|
||||
|
||||
Decompose and understand task requirements.
|
||||
|
||||
Analysis priorities:
|
||||
- Task breakdown
|
||||
- Complexity assessment
|
||||
- Dependency identification
|
||||
- Resource requirements
|
||||
- Timeline constraints
|
||||
- Risk factors
|
||||
- Success metrics
|
||||
- Quality standards
|
||||
|
||||
Task evaluation:
|
||||
- Parse requirements
|
||||
- Identify subtasks
|
||||
- Map dependencies
|
||||
- Estimate complexity
|
||||
- Assess resources
|
||||
- Define milestones
|
||||
- Plan workflow
|
||||
- Set checkpoints
|
||||
|
||||
### 2. Implementation Phase
|
||||
|
||||
Assemble and coordinate agent teams.
|
||||
|
||||
Implementation approach:
|
||||
- Select agents
|
||||
- Assign roles
|
||||
- Setup communication
|
||||
- Configure workflow
|
||||
- Monitor execution
|
||||
- Handle exceptions
|
||||
- Coordinate results
|
||||
- Optimize performance
|
||||
|
||||
Organization patterns:
|
||||
- Capability-based selection
|
||||
- Load-balanced assignment
|
||||
- Redundant coverage
|
||||
- Efficient communication
|
||||
- Clear accountability
|
||||
- Flexible adaptation
|
||||
- Continuous monitoring
|
||||
- Result validation
|
||||
|
||||
Progress tracking:
|
||||
```json
|
||||
{
|
||||
"agent": "agent-organizer",
|
||||
"status": "orchestrating",
|
||||
"progress": {
|
||||
"agents_assigned": 12,
|
||||
"tasks_distributed": 47,
|
||||
"completion_rate": "94%",
|
||||
"avg_response_time": "3.2s"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Orchestration Excellence
|
||||
|
||||
Achieve optimal multi-agent coordination.
|
||||
|
||||
Excellence checklist:
|
||||
- Tasks completed
|
||||
- Performance optimal
|
||||
- Resources efficient
|
||||
- Errors minimal
|
||||
- Adaptation smooth
|
||||
- Results integrated
|
||||
- Learning captured
|
||||
- Value delivered
|
||||
|
||||
Delivery notification:
|
||||
"Agent orchestration completed. Coordinated 12 agents across 47 tasks with 94% first-pass success rate. Average response time 3.2s with 67% resource utilization. Achieved 23% performance improvement through optimal team composition and workflow design."
|
||||
|
||||
Team composition strategies:
|
||||
- Skill diversity
|
||||
- Redundancy planning
|
||||
- Communication efficiency
|
||||
- Workload balance
|
||||
- Cost optimization
|
||||
- Performance history
|
||||
- Compatibility factors
|
||||
- Scalability design
|
||||
|
||||
Workflow optimization:
|
||||
- Parallel execution
|
||||
- Pipeline efficiency
|
||||
- Resource sharing
|
||||
- Cache utilization
|
||||
- Checkpoint optimization
|
||||
- Recovery planning
|
||||
- Monitoring integration
|
||||
- Result synthesis
|
||||
|
||||
Dynamic adaptation:
|
||||
- Performance monitoring
|
||||
- Bottleneck detection
|
||||
- Agent reallocation
|
||||
- Workflow adjustment
|
||||
- Failure recovery
|
||||
- Load rebalancing
|
||||
- Priority shifting
|
||||
- Resource scaling
|
||||
|
||||
Coordination excellence:
|
||||
- Clear communication
|
||||
- Efficient handoffs
|
||||
- Synchronized execution
|
||||
- Conflict prevention
|
||||
- Progress tracking
|
||||
- Result validation
|
||||
- Knowledge transfer
|
||||
- Continuous improvement
|
||||
|
||||
Learning & improvement:
|
||||
- Performance analysis
|
||||
- Pattern recognition
|
||||
- Best practice extraction
|
||||
- Failure analysis
|
||||
- Optimization opportunities
|
||||
- Team effectiveness
|
||||
- Workflow refinement
|
||||
- Knowledge base update
|
||||
|
||||
Integration with other agents:
|
||||
- Collaborate with context-manager on information sharing
|
||||
- Support multi-agent-coordinator on execution
|
||||
- Work with task-distributor on load balancing
|
||||
- Guide workflow-orchestrator on process design
|
||||
- Help performance-monitor on metrics
|
||||
- Assist error-coordinator on recovery
|
||||
- Partner with knowledge-synthesizer on learning
|
||||
- Coordinate with all agents on task execution
|
||||
|
||||
Always prioritize optimal agent selection, efficient coordination, and continuous improvement while orchestrating multi-agent teams that deliver exceptional results through synergistic collaboration.
|
||||
@@ -0,0 +1,286 @@
|
||||
---
|
||||
name: multi-agent-coordinator
|
||||
description: Use when coordinating multiple concurrent agents that need to communicate, share state, synchronize work, and handle distributed failures across a system.
|
||||
tools: Read, Write, Edit, Glob, Grep
|
||||
model: opus
|
||||
---
|
||||
|
||||
You are a senior multi-agent coordinator with expertise in orchestrating complex distributed workflows. Your focus spans inter-agent communication, task dependency management, parallel execution control, and fault tolerance with emphasis on ensuring efficient, reliable coordination across large agent teams.
|
||||
|
||||
When invoked:
|
||||
1. Query context manager for workflow requirements and agent states
|
||||
2. Review communication patterns, dependencies, and resource constraints
|
||||
3. Analyze coordination bottlenecks, deadlock risks, and optimization opportunities
|
||||
4. Implement robust multi-agent coordination strategies
|
||||
|
||||
Multi-agent coordination checklist:
|
||||
- Coordination overhead < 5% maintained
|
||||
- Deadlock prevention 100% ensured
|
||||
- Message delivery guaranteed thoroughly
|
||||
- Scalability to 100+ agents verified
|
||||
- Fault tolerance built-in properly
|
||||
- Monitoring comprehensive continuously
|
||||
- Recovery automated effectively
|
||||
- Performance optimal consistently
|
||||
|
||||
Workflow orchestration:
|
||||
- Process design
|
||||
- Flow control
|
||||
- State management
|
||||
- Checkpoint handling
|
||||
- Rollback procedures
|
||||
- Compensation logic
|
||||
- Event coordination
|
||||
- Result aggregation
|
||||
|
||||
Inter-agent communication:
|
||||
- Protocol design
|
||||
- Message routing
|
||||
- Channel management
|
||||
- Broadcast strategies
|
||||
- Request-reply patterns
|
||||
- Event streaming
|
||||
- Queue management
|
||||
- Backpressure handling
|
||||
|
||||
Dependency management:
|
||||
- Dependency graphs
|
||||
- Topological sorting
|
||||
- Circular detection
|
||||
- Resource locking
|
||||
- Priority scheduling
|
||||
- Constraint solving
|
||||
- Deadlock prevention
|
||||
- Race condition handling
|
||||
|
||||
Coordination patterns:
|
||||
- Master-worker
|
||||
- Peer-to-peer
|
||||
- Hierarchical
|
||||
- Publish-subscribe
|
||||
- Request-reply
|
||||
- Pipeline
|
||||
- Scatter-gather
|
||||
- Consensus-based
|
||||
|
||||
Parallel execution:
|
||||
- Task partitioning
|
||||
- Work distribution
|
||||
- Load balancing
|
||||
- Synchronization points
|
||||
- Barrier coordination
|
||||
- Fork-join patterns
|
||||
- Map-reduce workflows
|
||||
- Result merging
|
||||
|
||||
Communication mechanisms:
|
||||
- Message passing
|
||||
- Shared memory
|
||||
- Event streams
|
||||
- RPC calls
|
||||
- WebSocket connections
|
||||
- REST APIs
|
||||
- GraphQL subscriptions
|
||||
- Queue systems
|
||||
|
||||
Resource coordination:
|
||||
- Resource allocation
|
||||
- Lock management
|
||||
- Semaphore control
|
||||
- Quota enforcement
|
||||
- Priority handling
|
||||
- Fair scheduling
|
||||
- Starvation prevention
|
||||
- Efficiency optimization
|
||||
|
||||
Fault tolerance:
|
||||
- Failure detection
|
||||
- Timeout handling
|
||||
- Retry mechanisms
|
||||
- Circuit breakers
|
||||
- Fallback strategies
|
||||
- State recovery
|
||||
- Checkpoint restoration
|
||||
- Graceful degradation
|
||||
|
||||
Workflow management:
|
||||
- DAG execution
|
||||
- State machines
|
||||
- Saga patterns
|
||||
- Compensation logic
|
||||
- Checkpoint/restart
|
||||
- Dynamic workflows
|
||||
- Conditional branching
|
||||
- Loop handling
|
||||
|
||||
Performance optimization:
|
||||
- Bottleneck analysis
|
||||
- Pipeline optimization
|
||||
- Batch processing
|
||||
- Caching strategies
|
||||
- Connection pooling
|
||||
- Message compression
|
||||
- Latency reduction
|
||||
- Throughput maximization
|
||||
|
||||
## Communication Protocol
|
||||
|
||||
### Coordination Context Assessment
|
||||
|
||||
Initialize multi-agent coordination by understanding workflow needs.
|
||||
|
||||
Coordination context query:
|
||||
```json
|
||||
{
|
||||
"requesting_agent": "multi-agent-coordinator",
|
||||
"request_type": "get_coordination_context",
|
||||
"payload": {
|
||||
"query": "Coordination context needed: workflow complexity, agent count, communication patterns, performance requirements, and fault tolerance needs."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
Execute multi-agent coordination through systematic phases:
|
||||
|
||||
### 1. Workflow Analysis
|
||||
|
||||
Design efficient coordination strategies.
|
||||
|
||||
Analysis priorities:
|
||||
- Workflow mapping
|
||||
- Agent capabilities
|
||||
- Communication needs
|
||||
- Dependency analysis
|
||||
- Resource requirements
|
||||
- Performance targets
|
||||
- Risk assessment
|
||||
- Optimization opportunities
|
||||
|
||||
Workflow evaluation:
|
||||
- Map processes
|
||||
- Identify dependencies
|
||||
- Analyze communication
|
||||
- Assess parallelism
|
||||
- Plan synchronization
|
||||
- Design recovery
|
||||
- Document patterns
|
||||
- Validate approach
|
||||
|
||||
### 2. Implementation Phase
|
||||
|
||||
Orchestrate complex multi-agent workflows.
|
||||
|
||||
Implementation approach:
|
||||
- Setup communication
|
||||
- Configure workflows
|
||||
- Manage dependencies
|
||||
- Control execution
|
||||
- Monitor progress
|
||||
- Handle failures
|
||||
- Coordinate results
|
||||
- Optimize performance
|
||||
|
||||
Coordination patterns:
|
||||
- Efficient messaging
|
||||
- Clear dependencies
|
||||
- Parallel execution
|
||||
- Fault tolerance
|
||||
- Resource efficiency
|
||||
- Progress tracking
|
||||
- Result validation
|
||||
- Continuous optimization
|
||||
|
||||
Progress tracking:
|
||||
```json
|
||||
{
|
||||
"agent": "multi-agent-coordinator",
|
||||
"status": "coordinating",
|
||||
"progress": {
|
||||
"active_agents": 87,
|
||||
"messages_processed": "234K/min",
|
||||
"workflow_completion": "94%",
|
||||
"coordination_efficiency": "96%"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Coordination Excellence
|
||||
|
||||
Achieve seamless multi-agent collaboration.
|
||||
|
||||
Excellence checklist:
|
||||
- Workflows smooth
|
||||
- Communication efficient
|
||||
- Dependencies resolved
|
||||
- Failures handled
|
||||
- Performance optimal
|
||||
- Scaling proven
|
||||
- Monitoring active
|
||||
- Value delivered
|
||||
|
||||
Delivery notification:
|
||||
"Multi-agent coordination completed. Orchestrated 87 agents processing 234K messages/minute with 94% workflow completion rate. Achieved 96% coordination efficiency with zero deadlocks and 99.9% message delivery guarantee."
|
||||
|
||||
Communication optimization:
|
||||
- Protocol efficiency
|
||||
- Message batching
|
||||
- Compression strategies
|
||||
- Route optimization
|
||||
- Connection pooling
|
||||
- Async patterns
|
||||
- Event streaming
|
||||
- Queue management
|
||||
|
||||
Dependency resolution:
|
||||
- Graph algorithms
|
||||
- Priority scheduling
|
||||
- Resource allocation
|
||||
- Lock optimization
|
||||
- Conflict resolution
|
||||
- Parallel planning
|
||||
- Critical path analysis
|
||||
- Bottleneck removal
|
||||
|
||||
Fault handling:
|
||||
- Failure detection
|
||||
- Isolation strategies
|
||||
- Recovery procedures
|
||||
- State restoration
|
||||
- Compensation execution
|
||||
- Retry policies
|
||||
- Timeout management
|
||||
- Graceful degradation
|
||||
|
||||
Scalability patterns:
|
||||
- Horizontal scaling
|
||||
- Vertical partitioning
|
||||
- Load distribution
|
||||
- Connection management
|
||||
- Resource pooling
|
||||
- Batch optimization
|
||||
- Pipeline design
|
||||
- Cluster coordination
|
||||
|
||||
Performance tuning:
|
||||
- Latency analysis
|
||||
- Throughput optimization
|
||||
- Resource utilization
|
||||
- Cache effectiveness
|
||||
- Network efficiency
|
||||
- CPU optimization
|
||||
- Memory management
|
||||
- I/O optimization
|
||||
|
||||
Integration with other agents:
|
||||
- Collaborate with agent-organizer on team assembly
|
||||
- Support context-manager on state synchronization
|
||||
- Work with workflow-orchestrator on process execution
|
||||
- Guide task-distributor on work allocation
|
||||
- Help performance-monitor on metrics collection
|
||||
- Assist error-coordinator on failure handling
|
||||
- Partner with knowledge-synthesizer on patterns
|
||||
- Coordinate with all agents on communication
|
||||
|
||||
Always prioritize efficiency, reliability, and scalability while coordinating multi-agent systems that deliver exceptional performance through seamless collaboration.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"enabledMcpjsonServers": [
|
||||
"github",
|
||||
"kubernetes",
|
||||
"flux",
|
||||
"playwright"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Headlamp E2E Test Configuration
|
||||
|
||||
# Headlamp instance URL
|
||||
HEADLAMP_URL=https://headlamp.animaniacs.farh.net
|
||||
|
||||
# Authentication: Choose ONE of the following methods
|
||||
|
||||
# Option 1: OIDC Authentication (Authentik)
|
||||
# AUTHENTIK_USERNAME=your-username
|
||||
# AUTHENTIK_PASSWORD=your-password
|
||||
|
||||
# Option 2: Token Authentication
|
||||
# HEADLAMP_TOKEN=your-headlamp-token
|
||||
@@ -1,36 +0,0 @@
|
||||
name: AI Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
ai-review:
|
||||
name: AI Code Review
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: AI Review
|
||||
uses: Nikita-Filonov/ai-review@v0.56.0
|
||||
with:
|
||||
review-command: run
|
||||
env:
|
||||
LLM__PROVIDER: "OPENAI"
|
||||
LLM__META__MODEL: ${{ vars.AI_REVIEW_MODEL }}
|
||||
LLM__META__MAX_TOKENS: "15000"
|
||||
LLM__META__TEMPERATURE: "0.3"
|
||||
LLM__HTTP_CLIENT__API_URL: "https://api.openai.com/v1"
|
||||
LLM__HTTP_CLIENT__API_TOKEN: ${{ secrets.OPENAI_API_KEY }}
|
||||
VCS__PROVIDER: "GITEA"
|
||||
VCS__PIPELINE__OWNER: ${{ github.repository_owner }}
|
||||
VCS__PIPELINE__REPO: ${{ github.event.repository.name }}
|
||||
VCS__PIPELINE__PULL_NUMBER: ${{ github.event.pull_request.number }}
|
||||
VCS__HTTP_CLIENT__API_URL: ${{ github.server_url }}/api/v1
|
||||
VCS__HTTP_CLIENT__API_TOKEN: ${{ secrets.AI_REVIEW_GITEA_TOKEN }}
|
||||
@@ -1,30 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npx @kinvolk/headlamp-plugin build
|
||||
|
||||
- name: Lint
|
||||
run: npx eslint --ext .ts,.tsx src/
|
||||
|
||||
- name: Type-check
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Format check
|
||||
run: npx prettier --check src/
|
||||
@@ -1,28 +0,0 @@
|
||||
name: E2E
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Chromium
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E smoke tests
|
||||
env:
|
||||
HEADLAMP_URL: https://headlamp.animaniacs.farh.net
|
||||
AUTHENTIK_USERNAME: ${{ secrets.AUTHENTIK_USERNAME }}
|
||||
AUTHENTIK_PASSWORD: ${{ secrets.AUTHENTIK_PASSWORD }}
|
||||
run: npx playwright test
|
||||
@@ -1,201 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check if release is already finalized
|
||||
run: |
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
TARBALL_URL="https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/${GITHUB_REF_NAME}/headlamp-polaris-plugin-${VERSION}.tar.gz"
|
||||
HTTP_CODE=$(curl -sL -o /tmp/release.tar.gz -w "%{http_code}" "$TARBALL_URL" 2>/dev/null)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
ACTUAL="sha256:$(sha256sum /tmp/release.tar.gz | awk '{print $1}')"
|
||||
EXPECTED=$(grep 'archive-checksum' artifacthub-pkg.yml | awk '{print $2}')
|
||||
echo "Release tarball checksum: $ACTUAL"
|
||||
echo "Metadata checksum: $EXPECTED"
|
||||
if [ "$ACTUAL" = "$EXPECTED" ]; then
|
||||
echo "SKIP_BUILD=true" >> $GITHUB_ENV
|
||||
echo "Checksums match - release is finalized, nothing to do"
|
||||
fi
|
||||
else
|
||||
echo "No existing release (HTTP $HTTP_CODE) - will build"
|
||||
fi
|
||||
rm -f /tmp/release.tar.gz
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
npm ci
|
||||
|
||||
- name: Build plugin
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
npx @kinvolk/headlamp-plugin build
|
||||
|
||||
- name: Package tarball
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
npx @kinvolk/headlamp-plugin package
|
||||
|
||||
- name: Compute tarball checksum
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
TARBALL=$(ls *.tar.gz)
|
||||
CHECKSUM=$(sha256sum "$TARBALL" | awk '{print $1}')
|
||||
echo "TARBALL=$TARBALL" >> $GITHUB_ENV
|
||||
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
|
||||
echo "Tarball: $TARBALL"
|
||||
echo "Checksum: sha256:$CHECKSUM"
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
apt-get update && apt-get install -y docker.io
|
||||
|
||||
- name: Build and push Docker image
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
docker build -t git.farh.net/${{ github.repository }}:${{ github.ref_name }} -t git.farh.net/${{ github.repository }}:latest .
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.farh.net -u ${{ github.actor }} --password-stdin
|
||||
docker push git.farh.net/${{ github.repository }}:${{ github.ref_name }}
|
||||
docker push git.farh.net/${{ github.repository }}:latest
|
||||
|
||||
- name: Create Gitea release
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
API_URL="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||
# Create release (or get existing)
|
||||
RELEASE=$(curl -s -X POST \
|
||||
-H "Authorization: token ${{ github.token }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_URL}/releases" \
|
||||
-d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\"}")
|
||||
RELEASE_ID=$(echo "$RELEASE" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).id))")
|
||||
if [ "$RELEASE_ID" = "undefined" ]; then
|
||||
RELEASE=$(curl -sf \
|
||||
-H "Authorization: token ${{ github.token }}" \
|
||||
"${API_URL}/releases/tags/${GITHUB_REF_NAME}")
|
||||
RELEASE_ID=$(echo "$RELEASE" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).id))")
|
||||
fi
|
||||
echo "Gitea Release ID: $RELEASE_ID"
|
||||
# Delete existing assets
|
||||
ASSETS=$(curl -sf \
|
||||
-H "Authorization: token ${{ github.token }}" \
|
||||
"${API_URL}/releases/${RELEASE_ID}/assets")
|
||||
echo "$ASSETS" | node -e "
|
||||
process.stdin.resume();let d='';
|
||||
process.stdin.on('data',c=>d+=c);
|
||||
process.stdin.on('end',()=>{
|
||||
JSON.parse(d).forEach(a=>console.log(a.id));
|
||||
})" | while read -r ASSET_ID; do
|
||||
curl -sf -X DELETE \
|
||||
-H "Authorization: token ${{ github.token }}" \
|
||||
"${API_URL}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
||||
done
|
||||
# Upload tarball
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${{ github.token }}" \
|
||||
-F "attachment=@${TARBALL}" \
|
||||
"${API_URL}/releases/${RELEASE_ID}/assets?name=${TARBALL}"
|
||||
echo "Gitea release updated"
|
||||
|
||||
- name: Create GitHub release
|
||||
continue-on-error: true
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
# Push tag to GitHub first so it exists before creating the release
|
||||
git remote add github-release https://x-access-token:${{ secrets.GH_PAT }}@github.com/cpfarhood/headlamp-polaris-plugin.git 2>/dev/null || true
|
||||
git push -f github-release ${GITHUB_REF_NAME} 2>/dev/null || true
|
||||
GH_API="https://api.github.com/repos/cpfarhood/headlamp-polaris-plugin"
|
||||
# Create release or fetch existing one
|
||||
BODY=$(curl -s -X POST \
|
||||
-H "Authorization: token ${{ secrets.GH_PAT }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"${GH_API}/releases" \
|
||||
-d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\",\"generate_release_notes\":true}")
|
||||
RELEASE_ID=$(echo "$BODY" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).id))")
|
||||
if [ "$RELEASE_ID" = "undefined" ]; then
|
||||
echo "Release already exists, fetching it..."
|
||||
BODY=$(curl -sf \
|
||||
-H "Authorization: token ${{ secrets.GH_PAT }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"${GH_API}/releases/tags/${GITHUB_REF_NAME}")
|
||||
RELEASE_ID=$(echo "$BODY" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).id))")
|
||||
fi
|
||||
echo "GitHub Release ID: $RELEASE_ID"
|
||||
# Delete existing assets with the same name
|
||||
ASSETS=$(curl -sf \
|
||||
-H "Authorization: token ${{ secrets.GH_PAT }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"${GH_API}/releases/${RELEASE_ID}/assets")
|
||||
echo "$ASSETS" | node -e "
|
||||
process.stdin.resume();let d='';
|
||||
process.stdin.on('data',c=>d+=c);
|
||||
process.stdin.on('end',()=>{
|
||||
const assets=JSON.parse(d);
|
||||
assets.filter(a=>a.name==='${TARBALL}').forEach(a=>console.log(a.id));
|
||||
})" | while read -r ASSET_ID; do
|
||||
echo "Deleting existing asset $ASSET_ID..."
|
||||
curl -sf -X DELETE \
|
||||
-H "Authorization: token ${{ secrets.GH_PAT }}" \
|
||||
"${GH_API}/releases/assets/${ASSET_ID}"
|
||||
done
|
||||
# Upload tarball
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${{ secrets.GH_PAT }}" \
|
||||
-H "Content-Type: application/gzip" \
|
||||
"https://uploads.github.com/repos/cpfarhood/headlamp-polaris-plugin/releases/${RELEASE_ID}/assets?name=${TARBALL}" \
|
||||
--data-binary "@${TARBALL}"
|
||||
echo "GitHub release updated with same tarball"
|
||||
|
||||
- name: Update metadata and align tag
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
git config user.name "gitea-actions[bot]"
|
||||
git config user.email "gitea-actions[bot]@git.farh.net"
|
||||
# Determine which Gitea branch to update based on version suffix
|
||||
if [[ "$VERSION" == *"-dev."* ]]; then
|
||||
GITEA_BRANCH="dev"
|
||||
else
|
||||
GITEA_BRANCH="main"
|
||||
fi
|
||||
git fetch origin ${GITEA_BRANCH}
|
||||
git checkout origin/${GITEA_BRANCH} -B temp-update
|
||||
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
|
||||
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/${GITHUB_REF_NAME}/headlamp-polaris-plugin-${VERSION}.tar.gz\"|" artifacthub-pkg.yml
|
||||
sed -i "s|^version:.*|version: ${VERSION}|" artifacthub-pkg.yml
|
||||
git add artifacthub-pkg.yml
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "ci: update artifact hub metadata for ${GITHUB_REF_NAME}"
|
||||
git push origin temp-update:${GITEA_BRANCH}
|
||||
}
|
||||
# Force-move tag to the commit with correct checksum.
|
||||
# This triggers a new CI run, but the guard step will detect
|
||||
# that the release checksum already matches and skip the build.
|
||||
git tag -f ${GITHUB_REF_NAME}
|
||||
git push -f origin ${GITHUB_REF_NAME}
|
||||
# Only push to GitHub main branch for STABLE releases
|
||||
# Dev releases only create GitHub releases, don't update main branch
|
||||
# This keeps GitHub main branch at latest stable for ArtifactHub
|
||||
git remote add github https://x-access-token:${{ secrets.GH_PAT }}@github.com/cpfarhood/headlamp-polaris-plugin.git 2>/dev/null || true
|
||||
if [[ "$VERSION" != *"-dev."* ]]; then
|
||||
echo "Stable release detected - pushing to GitHub main branch"
|
||||
git push github temp-update:main 2>/dev/null || true
|
||||
else
|
||||
echo "Dev release detected - skipping GitHub main branch update"
|
||||
fi
|
||||
git push -f github ${GITHUB_REF_NAME} 2>/dev/null || true
|
||||
echo "Tag ${GITHUB_REF_NAME} aligned with updated metadata"
|
||||
@@ -0,0 +1,41 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: local-ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build plugin
|
||||
run: npx @kinvolk/headlamp-plugin build
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Type-check
|
||||
run: npm run tsc
|
||||
|
||||
- name: Format check
|
||||
run: npm run format:check
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
@@ -0,0 +1,53 @@
|
||||
name: E2E Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: local-ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npm run e2e
|
||||
env:
|
||||
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
|
||||
HEADLAMP_TOKEN: ${{ secrets.HEADLAMP_TOKEN }}
|
||||
AUTHENTIK_USERNAME: ${{ secrets.AUTHENTIK_USERNAME }}
|
||||
AUTHENTIK_PASSWORD: ${{ secrets.AUTHENTIK_PASSWORD }}
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results/
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,106 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version (e.g. 1.0.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
uses: ./.github/workflows/ci.yaml
|
||||
|
||||
release:
|
||||
needs: ci
|
||||
runs-on: local-ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Validate version format
|
||||
run: |
|
||||
if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Version must be in X.Y.Z format"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Update version in package.json
|
||||
run: npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version
|
||||
|
||||
- name: Update artifacthub-pkg.yml
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
PKG_NAME=$(jq -r .name package.json)
|
||||
RELEASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${PKG_NAME}-${VERSION}.tar.gz"
|
||||
sed -i "s/^version:.*/version: \"${VERSION}\"/" artifacthub-pkg.yml
|
||||
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"${RELEASE_URL}\"|" artifacthub-pkg.yml
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build plugin
|
||||
run: npx @kinvolk/headlamp-plugin build
|
||||
|
||||
- name: Package plugin
|
||||
run: npx @kinvolk/headlamp-plugin package
|
||||
|
||||
- name: Prepare release tarball
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
PKG_NAME=$(jq -r .name package.json)
|
||||
TARBALL="${PKG_NAME}-${VERSION}.tar.gz"
|
||||
echo "TARBALL=$TARBALL" >> $GITHUB_ENV
|
||||
echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Validate tarball
|
||||
run: |
|
||||
echo "Tarball: ${{ env.TARBALL }}"
|
||||
ls -lh "${{ env.TARBALL }}"
|
||||
tar -tzf "${{ env.TARBALL }}" | head -20
|
||||
tar -tzf "${{ env.TARBALL }}" | grep -q "main.js" || { echo "Error: main.js not found in tarball"; exit 1; }
|
||||
|
||||
- name: Compute checksum
|
||||
run: |
|
||||
CHECKSUM=$(sha256sum "${{ env.TARBALL }}" | awk '{print $1}')
|
||||
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
|
||||
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
|
||||
|
||||
- name: Commit and tag
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
git add package.json package-lock.json artifacthub-pkg.yml
|
||||
git commit -m "release: v${VERSION}"
|
||||
git tag "v${VERSION}"
|
||||
git push origin main --tags
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: "v${{ inputs.version }}"
|
||||
files: ${{ env.TARBALL }}
|
||||
fail_on_unmatched_files: true
|
||||
generate_release_notes: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.headlamp-plugin/
|
||||
.mcp.json
|
||||
*.tar.gz
|
||||
e2e/.auth/
|
||||
test-results/
|
||||
.playwright-mcp/
|
||||
.env
|
||||
.env.local
|
||||
.eslintcache
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${GITHUB_TOKEN}"
|
||||
}
|
||||
},
|
||||
"kubernetes": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8080/sse"
|
||||
},
|
||||
"flux": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8081/sse"
|
||||
},
|
||||
"playwright": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8086/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the Headlamp Polaris Plugin will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.5] - 2026-02-12
|
||||
|
||||
### Fixed
|
||||
- Fixed drawer background remaining white in dark mode by using correct CSS variable (`--mui-palette-background-default`)
|
||||
|
||||
### Documentation
|
||||
- Added comprehensive Priority 2 documentation (ARCHITECTURE.md, DEPLOYMENT.md, SECURITY.md)
|
||||
- Added CONTRIBUTING.md with development workflow, branching strategy, and code style guidelines
|
||||
- Added complete CHANGELOG.md documenting all releases from v0.0.1 to current
|
||||
|
||||
## [0.3.4] - 2026-02-12
|
||||
|
||||
### Fixed
|
||||
- Removed all `@mui/material` and `@mui/icons-material` imports causing plugin load failure
|
||||
- Fixed plugin settings page registration (changed name from 'polaris' to 'headlamp-polaris-plugin')
|
||||
- Added dark mode support using MUI CSS variables for proper theme adaptation
|
||||
- Resolved TypeScript compilation errors in plugin registration calls
|
||||
|
||||
### Changed
|
||||
- Replaced all MUI components with standard HTML elements and inline styles
|
||||
- Updated `registerDetailsViewSection` and `registerAppBarAction` to match Headlamp plugin API v0.13.0
|
||||
- App bar badge, settings buttons, and UI elements now use theme-aware CSS variables
|
||||
|
||||
### Infrastructure
|
||||
- Added CI workflow for lint, type-check, build, and test
|
||||
- Enhanced E2E testing documentation with comprehensive guides
|
||||
- Added documentation-engineer subagent
|
||||
|
||||
## [0.3.3] - 2026-02-12
|
||||
|
||||
### Fixed
|
||||
- Corrected plugin settings registration name to match package.json
|
||||
- Added displaySaveButton parameter to settings registration
|
||||
|
||||
## [0.3.2] - 2026-02-12
|
||||
|
||||
### Fixed
|
||||
- Removed all MUI dependencies to fix plugin loading in Headlamp v0.39.0+
|
||||
- Plugin now loads correctly in sidebar and routes
|
||||
|
||||
## [0.3.1] - 2026-02-12
|
||||
|
||||
### Fixed
|
||||
- TypeScript compilation errors in `registerDetailsViewSection` and `registerAppBarAction` calls
|
||||
- Test failures in DashboardView (added missing SimpleTable mock)
|
||||
|
||||
## [0.3.0] - 2026-02-11
|
||||
|
||||
### Added
|
||||
- App bar badge displaying cluster Polaris score
|
||||
- Inline audit sections in resource detail views (Deployment, StatefulSet, DaemonSet, Job, CronJob)
|
||||
- Exemption management UI (view/add exemptions via annotations)
|
||||
- Connection testing button in plugin settings
|
||||
- Top issues dashboard with severity-based filtering
|
||||
- Namespace drawer navigation with URL hash support
|
||||
|
||||
### Changed
|
||||
- Migrated namespace detail to right-side drawer panel
|
||||
- Improved drawer keyboard navigation (Escape to close)
|
||||
- Enhanced settings page with connection testing
|
||||
|
||||
### Fixed
|
||||
- Empty namespace crash handling
|
||||
- Drawer navigation pattern for better UX
|
||||
|
||||
## [0.2.5] - 2025-12-XX
|
||||
|
||||
### Fixed
|
||||
- Improved theming and settings visibility
|
||||
|
||||
## [0.2.4] - 2025-12-XX
|
||||
|
||||
### Changed
|
||||
- Increased namespace detail panel width to 1000px for better readability
|
||||
|
||||
## [0.2.3] - 2025-12-XX
|
||||
|
||||
### Added
|
||||
- Full URL support for custom Polaris dashboards
|
||||
- Support for external Polaris instances (not just service proxy)
|
||||
|
||||
## [0.2.2] - 2025-12-XX
|
||||
|
||||
### Added
|
||||
- Configurable Polaris dashboard URL setting
|
||||
- Settings page for plugin configuration
|
||||
- Refresh interval configuration
|
||||
|
||||
## [0.2.1] - 2025-12-XX
|
||||
|
||||
### Infrastructure
|
||||
- Migrated to GitHub as primary repository
|
||||
- Fixed v0.2.0 checksum in ArtifactHub metadata
|
||||
|
||||
## [0.2.0] - 2025-12-XX
|
||||
|
||||
### Added
|
||||
- Namespace drawer navigation
|
||||
- URL hash-based routing for namespaces
|
||||
- Keyboard shortcuts (Escape to close drawer)
|
||||
|
||||
### Infrastructure
|
||||
- GitHub release automation
|
||||
- Improved CI/CD workflow
|
||||
|
||||
## [0.1.7] - 2025-11-XX
|
||||
|
||||
### Documentation
|
||||
- Removed incorrect development installation instructions
|
||||
|
||||
## [0.1.6] - 2025-11-XX
|
||||
|
||||
### Fixed
|
||||
- Plugin settings display name changed to "Polaris"
|
||||
|
||||
### Documentation
|
||||
- Added tooltip to skipped count explaining limitation
|
||||
- Documented skipped count limitation in README
|
||||
|
||||
## [0.1.5] - 2025-11-XX
|
||||
|
||||
### Fixed
|
||||
- Restored `:80` port in service proxy URL for correct dashboard access
|
||||
|
||||
## [0.1.4] - 2025-11-XX
|
||||
|
||||
### Added
|
||||
- Playwright E2E smoke tests
|
||||
- Test coverage for sidebar, overview, namespaces, and detail views
|
||||
|
||||
### Fixed
|
||||
- Empty namespace crash (graceful handling)
|
||||
- Removed `:80` port suffix from service proxy URL for RBAC compatibility
|
||||
|
||||
## [0.1.3] - 2025-11-XX
|
||||
|
||||
### Fixed
|
||||
- Service proxy URL format for consistent RBAC requirements
|
||||
|
||||
## [0.1.2] - 2025-11-XX
|
||||
|
||||
### Added
|
||||
- Namespace filtering and sorting
|
||||
- Enhanced resource table in namespace detail view
|
||||
|
||||
## [0.1.1] - 2025-11-XX
|
||||
|
||||
### Fixed
|
||||
- Score calculation for resources with mixed results
|
||||
- Percentage display formatting
|
||||
|
||||
## [0.1.0] - 2025-11-XX
|
||||
|
||||
### Added
|
||||
- Namespace detail view with resource-level audit results
|
||||
- Drill-down navigation from namespace list
|
||||
|
||||
### Changed
|
||||
- Improved data fetching with error handling
|
||||
- Better loading states
|
||||
|
||||
## [0.0.10] - 2025-11-XX
|
||||
|
||||
### Fixed
|
||||
- **RBAC Documentation:** Corrected to use `services/proxy` permission instead of ConfigMap access
|
||||
|
||||
### Documentation
|
||||
- Updated README with accurate RBAC requirements
|
||||
- Added minimal Role example
|
||||
|
||||
## [0.0.9] - 2025-11-XX
|
||||
|
||||
### Added
|
||||
- Refresh button for manual data reload
|
||||
- Last updated timestamp display
|
||||
|
||||
## [0.0.8] - 2025-11-XX
|
||||
|
||||
### Added
|
||||
- Skipped checks display in check summary
|
||||
- Improved check categorization (pass/warning/danger/skipped)
|
||||
|
||||
## [0.0.7] - 2025-11-XX
|
||||
|
||||
### Changed
|
||||
- Enhanced overview dashboard layout
|
||||
- Better visual hierarchy for cluster score
|
||||
|
||||
## [0.0.6] - 2025-11-XX
|
||||
|
||||
### Added
|
||||
- Namespace list view with per-namespace scores
|
||||
- Navigation between overview and namespace views
|
||||
|
||||
## [0.0.5] - 2025-11-XX
|
||||
|
||||
### Fixed
|
||||
- Data fetching error handling
|
||||
- API proxy path configuration
|
||||
|
||||
## [0.0.4] - 2025-11-XX
|
||||
|
||||
### Added
|
||||
- Check distribution visualization
|
||||
- Pass/Warning/Danger count display
|
||||
|
||||
## [0.0.3] - 2025-11-XX
|
||||
|
||||
### Changed
|
||||
- Improved cluster score calculation
|
||||
- Better result aggregation logic
|
||||
|
||||
## [0.0.2] - 2025-11-XX
|
||||
|
||||
### Added
|
||||
- Cluster score display
|
||||
- Basic check summary table
|
||||
|
||||
## [0.0.1] - 2025-10-XX
|
||||
|
||||
### Added
|
||||
- Initial release
|
||||
- Basic Polaris plugin structure
|
||||
- Sidebar entry "Polaris"
|
||||
- Overview page with cluster info
|
||||
- Data fetching from Polaris dashboard via Kubernetes service proxy
|
||||
- TypeScript support with strict mode
|
||||
- React components using Headlamp CommonComponents
|
||||
|
||||
### Infrastructure
|
||||
- GitHub repository setup
|
||||
- ArtifactHub package registration
|
||||
- Automated release workflow
|
||||
- Basic CI/CD pipeline
|
||||
|
||||
[Unreleased]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v0.3.5...HEAD
|
||||
[0.3.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.5
|
||||
[0.3.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.4
|
||||
[0.3.3]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.3
|
||||
[0.3.2]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.2
|
||||
[0.3.1]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.1
|
||||
[0.3.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.0
|
||||
[0.2.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.2.5
|
||||
[0.2.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.2.4
|
||||
[0.2.3]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.2.3
|
||||
[0.2.2]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.2.2
|
||||
[0.2.1]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.2.1
|
||||
[0.2.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.2.0
|
||||
[0.1.7]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.1.7
|
||||
[0.1.6]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.1.6
|
||||
[0.1.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.1.5
|
||||
[0.1.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.1.4
|
||||
[0.1.3]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.1.3
|
||||
[0.1.2]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.1.2
|
||||
[0.1.1]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.1.1
|
||||
[0.1.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.1.0
|
||||
[0.0.10]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.10
|
||||
[0.0.9]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.9
|
||||
[0.0.8]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.8
|
||||
[0.0.7]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.7
|
||||
[0.0.6]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.6
|
||||
[0.0.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.5
|
||||
[0.0.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.4
|
||||
[0.0.3]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.3
|
||||
[0.0.2]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.2
|
||||
[0.0.1]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.0.1
|
||||
@@ -0,0 +1,79 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project
|
||||
|
||||
Headlamp plugin surfacing Fairwinds Polaris audit results. Queries the Polaris dashboard API via Kubernetes service proxy (`/api/v1/namespaces/polaris/services/polaris-dashboard/proxy/results.json`). Read-only — no cluster write operations except exemption annotation patches.
|
||||
|
||||
- **Plugin name**: `polaris`
|
||||
- **Target**: Headlamp >= v0.26
|
||||
- **Data source**: Polaris dashboard service in `polaris` namespace
|
||||
- **RBAC**: `get` on `services/proxy` resource `polaris-dashboard` in `polaris` namespace
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm start # dev server with hot reload
|
||||
npm run build # production build
|
||||
npm run package # package for headlamp
|
||||
npm run tsc # TypeScript type check (no emit)
|
||||
npm run lint # ESLint
|
||||
npm run lint:fix # ESLint with auto-fix
|
||||
npm run format # Prettier write
|
||||
npm run format:check # Prettier check
|
||||
npm test # vitest run
|
||||
npm run test:watch # vitest watch mode
|
||||
npx vitest run src/api/polaris.test.ts # run a single test file
|
||||
npm run e2e # Playwright E2E tests
|
||||
npm run e2e:headed # Playwright headed mode
|
||||
```
|
||||
|
||||
All tests and `tsc` must pass before committing.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.tsx # Plugin entry: registerRoute, registerSidebarEntry, registerDetailsViewSection, registerAppBarAction, registerPluginSettings
|
||||
├── test-utils.tsx # Shared test utilities
|
||||
├── api/
|
||||
│ ├── polaris.ts # Types (AuditData schema), countResults utilities, refresh settings
|
||||
│ ├── checkMapping.ts # Polaris check ID → human-readable name mapping
|
||||
│ ├── topIssues.ts # Top failing checks aggregation logic
|
||||
│ └── PolarisDataContext.tsx # Shared React context provider (ApiProxy.request + configurable refresh)
|
||||
└── components/
|
||||
├── DashboardView.tsx # Overview page (score gauge, check distribution, top failing checks)
|
||||
├── NamespacesListView.tsx # Namespace list with per-namespace scores
|
||||
├── NamespaceDetailView.tsx # Per-namespace drill-down with resource table
|
||||
├── InlineAuditSection.tsx # Injected into Deployment/StatefulSet/DaemonSet/Job/CronJob detail views
|
||||
├── ExemptionManager.tsx # Polaris exemption annotation management
|
||||
├── AppBarScoreBadge.tsx # App bar cluster score chip
|
||||
└── PolarisSettings.tsx # Plugin settings (refresh interval, dashboard URL)
|
||||
```
|
||||
|
||||
## Data flow
|
||||
|
||||
Data is fetched via `ApiProxy.request` to the Polaris dashboard service proxy and refreshed on a user-configurable interval (stored in localStorage under `polaris-plugin-refresh-interval`, default 5 minutes). Score is computed from result counts (pass/total). `PolarisDataProvider` wraps each route component and detail-section registration in `index.tsx`.
|
||||
|
||||
**Sidebar limitation**: Headlamp's sidebar only supports 2-level nesting (parent → children). Namespace navigation is handled via the in-content table on the Namespaces page instead.
|
||||
|
||||
## Code conventions
|
||||
|
||||
- Functional React components only — no class components
|
||||
- All imports from `@kinvolk/headlamp-plugin/lib` and `@kinvolk/headlamp-plugin/lib/CommonComponents`
|
||||
- No additional UI libraries (no MUI direct imports, no Ant Design, etc.)
|
||||
- TypeScript strict mode — no `any`, use `unknown` + type guards at API boundaries
|
||||
- Context provider (`PolarisDataProvider`) wraps each route component in `index.tsx`
|
||||
- Tests: vitest + @testing-library/react, mock with `vi.mock('@kinvolk/headlamp-plugin/lib', ...)`
|
||||
- `vitest.setup.ts` provides a spec-compliant `localStorage` shim for Node 22+ compatibility
|
||||
|
||||
## Testing
|
||||
|
||||
Mock pattern for headlamp APIs:
|
||||
```typescript
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: vi.fn().mockResolvedValue({}) },
|
||||
K8s: { ResourceClasses: {} },
|
||||
}));
|
||||
```
|
||||
+575
@@ -0,0 +1,575 @@
|
||||
# CONTEXT.md - Headlamp Polaris Plugin
|
||||
|
||||
**Purpose**: Comprehensive reverse prompt for AI assistants working on this project.
|
||||
|
||||
---
|
||||
|
||||
## Project Overview
|
||||
|
||||
The Headlamp Polaris Plugin surfaces [Fairwinds Polaris](https://www.fairwinds.com/polaris) audit results directly inside the [Headlamp](https://headlamp.dev) Kubernetes UI. It provides a read-only dashboard showing cluster-wide security, reliability, and efficiency scores derived from Polaris policy checks.
|
||||
|
||||
- **Stack**: React + TypeScript plugin for Headlamp (v0.26+)
|
||||
- **Data Source**: Polaris dashboard API via Kubernetes service proxy (read-only)
|
||||
- **Current Version**: v0.4.1
|
||||
- **Key Constraint**: No direct Kubernetes resource access - all data fetched through service proxy
|
||||
|
||||
## Architecture & Data Flow
|
||||
|
||||
### Component Hierarchy
|
||||
|
||||
```
|
||||
src/index.tsx # Entry point: registers routes, sidebar, settings
|
||||
├── PolarisDataContext.tsx # Shared data fetch with auto-refresh
|
||||
├── components/
|
||||
│ ├── DashboardView.tsx # Overview (score, checks, top issues)
|
||||
│ ├── NamespacesListView.tsx # Namespace list with scores
|
||||
│ ├── NamespaceDetailView.tsx # Per-namespace drill-down (drawer)
|
||||
│ ├── PolarisSettings.tsx # Settings (refresh interval, URL, test)
|
||||
│ ├── AppBarScoreBadge.tsx # Cluster score badge in top nav
|
||||
│ └── InlineAuditSection.tsx # Injected into workload detail views
|
||||
└── api/
|
||||
└── polaris.ts # Types, hooks, utilities
|
||||
```
|
||||
|
||||
### Data Source
|
||||
|
||||
- **Service Proxy Path**: `/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
|
||||
- **Schema**: `AuditData` with `ClusterInfo`, `Results[]` containing nested `PodResult` and `ContainerResults`
|
||||
- **Method**: `ApiProxy.request()` from Headlamp plugin SDK (handles K8s API auth automatically)
|
||||
|
||||
### State Management
|
||||
|
||||
- **Pattern**: React Context (see `src/api/PolarisDataContext.tsx`)
|
||||
- **Rationale**: ADR-001 - Prevents duplicate API calls when multiple components need same data
|
||||
- **Auto-refresh**: User-configurable interval (1/5/10/30 min, default 5 min)
|
||||
- **Storage**: Refresh interval and dashboard URL stored in `localStorage`
|
||||
|
||||
### Score Computation
|
||||
|
||||
```typescript
|
||||
// Formula: (pass / total) * 100, rounded to nearest integer
|
||||
function computeScore(counts: ResultCounts): number {
|
||||
if (counts.total === 0) return 0;
|
||||
return Math.round((counts.pass / counts.total) * 100);
|
||||
}
|
||||
```
|
||||
|
||||
## Technology Constraints
|
||||
|
||||
### ⚠️ CRITICAL: Headlamp Components Only
|
||||
|
||||
**MUST** use `@kinvolk/headlamp-plugin/lib/CommonComponents`
|
||||
**NEVER** import from `@mui/material` or `@mui/icons-material`
|
||||
|
||||
**Why**: Historical issue (v0.3.2) - MUI imports caused plugin load failures. Headlamp provides all needed components as re-exports.
|
||||
|
||||
```typescript
|
||||
// ✅ Correct
|
||||
import { SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
|
||||
// ❌ Wrong - will break plugin
|
||||
import { Box, Chip } from '@mui/material';
|
||||
```
|
||||
|
||||
### Other Constraints
|
||||
|
||||
- **TypeScript Strictness**: No `any`, explicit types, strict mode enabled
|
||||
- **Packaging**: `@kinvolk/headlamp-plugin` is peer dependency - don't bundle React/MUI
|
||||
- **Theme Handling**: Use CSS variables (`--mui-palette-*`), not theme imports
|
||||
- **Sidebar Limitation**: Headlamp only supports 2-level nesting (parent → children)
|
||||
|
||||
## Component Patterns & Gotchas
|
||||
|
||||
### Headlamp Component Issues
|
||||
|
||||
1. **StatusLabel with empty status**
|
||||
```typescript
|
||||
// ❌ Renders near-invisible (muted background)
|
||||
<StatusLabel status="">{value}</StatusLabel>
|
||||
|
||||
// ✅ Use plain String() for neutral values
|
||||
<span>{String(value)}</span>
|
||||
```
|
||||
|
||||
2. **Link component crashes on plugin routes**
|
||||
```typescript
|
||||
// ❌ Headlamp Link crashes on plugin-registered routes
|
||||
import { Link } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
|
||||
// ✅ Use react-router-dom Link with Router.createRouteURL
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Router } from '@kinvolk/headlamp-plugin/lib';
|
||||
|
||||
<Link to={Router.createRouteURL('/polaris/namespaces')}>View</Link>
|
||||
```
|
||||
|
||||
3. **Visual components that work well**
|
||||
- `PercentageCircle` - Great for score display
|
||||
- `PercentageBar` - Great for check distribution
|
||||
- `SimpleTable` - Fast, clean tables
|
||||
- `NameValueTable` - Key-value pairs
|
||||
- `SectionBox` - Card containers with titles
|
||||
|
||||
### Code Conventions
|
||||
|
||||
- **Functional Components**: Always use function components with hooks
|
||||
- **Named Exports**: Prefer named exports over default exports
|
||||
- **Props Interfaces**: Define as TypeScript interfaces, not inline types
|
||||
- **Import Order**: React → third-party → Headlamp → local (auto-sorted by eslint)
|
||||
|
||||
## RBAC & Security
|
||||
|
||||
### Minimal Permission Required
|
||||
|
||||
The plugin requires **only** this RBAC permission:
|
||||
|
||||
| Verb | API Group | Resource | Resource Name | Namespace |
|
||||
|------|-----------|----------|---------------|-----------|
|
||||
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
|
||||
|
||||
### Example Role
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### Security Notes
|
||||
|
||||
- **Namespaced Role**: MUST be namespaced Role, NOT ClusterRole
|
||||
- **ResourceNames Required**: Always specify `resourceNames: ["polaris-dashboard"]`
|
||||
- **No Write Operations**: Plugin only performs GET, never create/update/delete
|
||||
- **Token-Auth Mode**: When Headlamp uses user tokens, each user needs the RoleBinding
|
||||
- **Network Policy**: If enforced, allow API server → `polaris-dashboard:80` ingress
|
||||
- **Audit Logging**: Every proxy request logged as K8s API audit event
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development mode (hot reload at localhost:4466)
|
||||
npm start
|
||||
|
||||
# Build plugin
|
||||
npm run build
|
||||
|
||||
# Create tarball for distribution
|
||||
npm run package
|
||||
|
||||
# Type-check without emitting
|
||||
npm run tsc
|
||||
|
||||
# Lint
|
||||
npm run lint
|
||||
|
||||
# Run unit tests
|
||||
npm test
|
||||
|
||||
# Run E2E tests (requires cluster access)
|
||||
npm run e2e
|
||||
|
||||
# Format code
|
||||
npm run format
|
||||
|
||||
# Check formatting (CI)
|
||||
npm run format:check
|
||||
```
|
||||
|
||||
### Branching Strategy
|
||||
|
||||
- ✅ **ALWAYS use feature branches** for code changes (`feat/*`, `fix/*`, `docs/*`)
|
||||
- ✅ **MAY push directly to main** for: documentation-only changes, version bump commits
|
||||
- ❌ **NEVER push code changes directly to main**
|
||||
|
||||
### Commit Convention
|
||||
|
||||
Use Conventional Commits:
|
||||
- `feat:` - New feature
|
||||
- `fix:` - Bug fix
|
||||
- `docs:` - Documentation only
|
||||
- `chore:` - Maintenance (deps, config)
|
||||
- `test:` - Test changes
|
||||
- `ci:` - CI/CD changes
|
||||
|
||||
### PR Process
|
||||
|
||||
All PRs must pass:
|
||||
1. Build (`npm run build`)
|
||||
2. Lint (`npm run lint`)
|
||||
3. Type-check (`npm run tsc`)
|
||||
4. Unit tests (`npm test`)
|
||||
5. Format check (`npm run format:check`)
|
||||
|
||||
**Before committing**: Always run `npx prettier --write src/`
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (Vitest)
|
||||
|
||||
```bash
|
||||
npm test # Run once
|
||||
npm run test:watch # Watch mode
|
||||
```
|
||||
|
||||
- **Framework**: Vitest with jsdom environment
|
||||
- **Test files**: `*.test.ts`, `*.test.tsx` in `src/`
|
||||
- **Setup**: `vitest.setup.ts` with `@testing-library/jest-dom`
|
||||
- **Coverage**: Focus on meaningful tests, not just numbers
|
||||
- **Test utilities**: `src/test-utils.tsx` provides test wrapper with context
|
||||
|
||||
### E2E Tests (Playwright)
|
||||
|
||||
```bash
|
||||
npm run e2e # Headless
|
||||
npm run e2e:headed # With browser UI
|
||||
```
|
||||
|
||||
- **Framework**: Playwright
|
||||
- **Test files**: `e2e/*.spec.ts`
|
||||
- `polaris.spec.ts` - Sidebar, overview, namespaces, detail drawer
|
||||
- `settings.spec.ts` - Plugin settings page
|
||||
- `appbar.spec.ts` - App bar score badge
|
||||
- **Auth**: Supports both OIDC (Authentik) and token-based auth (see `e2e/auth.setup.ts`)
|
||||
- **CI**: Runs on GitHub Actions with `k3s-animaniacs` runner
|
||||
|
||||
### Local E2E Setup
|
||||
|
||||
```bash
|
||||
# Token-based auth
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
npm run e2e
|
||||
|
||||
# OIDC auth (Authentik)
|
||||
export AUTHENTIK_USERNAME=your-username
|
||||
export AUTHENTIK_PASSWORD=your-password
|
||||
npm run e2e
|
||||
```
|
||||
|
||||
## CI/CD & Release
|
||||
|
||||
### CI Workflow (`.github/workflows/ci.yaml`)
|
||||
|
||||
Runs on push to main and all PRs:
|
||||
1. Checkout
|
||||
2. `npm ci`
|
||||
3. `npm run build`
|
||||
4. `npm run lint`
|
||||
5. `npm run tsc`
|
||||
6. `npm run format:check`
|
||||
7. `npm test`
|
||||
|
||||
Runner: `local-ubuntu-latest`
|
||||
|
||||
### E2E Workflow (`.github/workflows/e2e.yaml`)
|
||||
|
||||
Runs on push, PR, and manual trigger:
|
||||
1. Checkout
|
||||
2. `npm ci`
|
||||
3. `npm run e2e`
|
||||
|
||||
Runner: `k3s-animaniacs` (has cluster access)
|
||||
Requires: `HEADLAMP_URL`, `HEADLAMP_TOKEN` or `AUTHENTIK_USERNAME`/`AUTHENTIK_PASSWORD`
|
||||
|
||||
### Release Workflow (`.github/workflows/release.yaml`)
|
||||
|
||||
**Manual trigger** via workflow_dispatch with version input:
|
||||
|
||||
```bash
|
||||
# Via GitHub UI or CLI
|
||||
gh workflow run release.yaml -f version=0.4.2
|
||||
```
|
||||
|
||||
Steps:
|
||||
1. Validate version format (semver)
|
||||
2. Bump `package.json` + `artifacthub-pkg.yml`
|
||||
3. Build plugin
|
||||
4. Package tarball
|
||||
5. Compute SHA256 checksum
|
||||
6. Commit version bump
|
||||
7. Create git tag
|
||||
8. Create GitHub release
|
||||
9. Upload tarball to release
|
||||
|
||||
**Guard**: Skips if checksum already matches (prevents infinite loop)
|
||||
|
||||
**Post-release**: ArtifactHub pulls metadata every 30 min (no webhook, pull-based)
|
||||
|
||||
### Version Bump Requirements
|
||||
|
||||
**ALWAYS bump both files in the same commit**:
|
||||
- `package.json` - `version` field
|
||||
- `artifacthub-pkg.yml` - `version` field + `digest` (checksum) + `archive.url`
|
||||
|
||||
## Known Issues & Workarounds
|
||||
|
||||
### ⚠️ Headlamp v0.39.0 Known Issues
|
||||
|
||||
**AutoSizer JavaScript Error**
|
||||
- **Symptom**: Console shows `TypeError: undefined is not an object (evaluating 'io.AutoSizer')`
|
||||
- **Impact**: Cosmetic error in Settings page, doesn't break functionality
|
||||
- **Root Cause**: Headlamp core bug, not plugin-related
|
||||
- **Workaround**: None needed, can be ignored
|
||||
|
||||
**Plugin Loading (RESOLVED)**
|
||||
- **Old Issue**: Previously thought `config.watchPlugins: false` was required
|
||||
- **Resolution**: Plugins load correctly with default `watchPlugins: true`
|
||||
- **Note**: If you see old docs mentioning `watchPlugins: false`, ignore them
|
||||
|
||||
### Polaris Dashboard Behavior
|
||||
|
||||
**Stale Audit Data**
|
||||
- **Symptom**: Plugin shows old audit timestamp
|
||||
- **Root Cause**: Polaris dashboard runs audit once at pod startup, then caches results
|
||||
- **Does NOT**: Continuously re-audit in real-time
|
||||
- **Workaround**: Restart Polaris pods for fresh data
|
||||
```bash
|
||||
kubectl rollout restart deployment -n polaris polaris-dashboard
|
||||
```
|
||||
- **Load Balancing**: Service balances across multiple pods - each may have different audit timestamps
|
||||
- **Plugin Auto-Refresh**: Works correctly - just fetches whatever Polaris currently has cached
|
||||
|
||||
### Skipped Count Limitation
|
||||
|
||||
**What It Shows**:
|
||||
- Only checks with `Severity: "ignore"` in Polaris API response
|
||||
- Does NOT include annotation-based exemptions (`polaris.fairwinds.com/*-exempt`)
|
||||
|
||||
**Why**:
|
||||
- Polaris omits exempted checks from `results.json`
|
||||
- Plugin has no access to raw K8s resources to compute exemptions
|
||||
- By design: service proxy limitation
|
||||
|
||||
**Workaround**:
|
||||
- Link to native Polaris dashboard for full exemption count
|
||||
- UI tooltip explains this limitation
|
||||
|
||||
## Deployment Patterns
|
||||
|
||||
### Plugin Manager (Recommended)
|
||||
|
||||
Install via Headlamp UI (Settings → Plugins → Catalog) or Helm values:
|
||||
|
||||
```yaml
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
configContent: |
|
||||
plugins:
|
||||
- name: polaris
|
||||
source: https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin
|
||||
```
|
||||
|
||||
### Sidecar Container (Alternative)
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: headlamp
|
||||
# ... main container
|
||||
- name: headlamp-plugin
|
||||
image: node:lts-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
npx @headlamp-k8s/pluginctl@latest install \
|
||||
--config /config/plugin.yml \
|
||||
--folderName /headlamp/plugins \
|
||||
--watch
|
||||
volumeMounts:
|
||||
- name: plugins-dir
|
||||
mountPath: /headlamp/plugins
|
||||
- name: plugin-config
|
||||
mountPath: /config
|
||||
volumes:
|
||||
- name: plugins-dir
|
||||
emptyDir: {}
|
||||
- name: plugin-config
|
||||
configMap:
|
||||
name: headlamp-plugin-config
|
||||
```
|
||||
|
||||
### Manual Tarball
|
||||
|
||||
```bash
|
||||
# Download release
|
||||
wget https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.4.1/headlamp-polaris-plugin-0.4.1.tgz
|
||||
|
||||
# Extract to plugin directory
|
||||
tar -xzf headlamp-polaris-plugin-0.4.1.tgz -C /headlamp/plugins/
|
||||
|
||||
# Restart Headlamp
|
||||
kubectl rollout restart deployment headlamp -n kube-system
|
||||
```
|
||||
|
||||
## Project Files Reference
|
||||
|
||||
```
|
||||
src/
|
||||
index.tsx # Entry point: registers sidebar, routes, settings, etc.
|
||||
api/
|
||||
polaris.ts # Core types, usePolarisData hook, utilities
|
||||
PolarisDataContext.tsx # React Context provider for shared data
|
||||
components/
|
||||
DashboardView.tsx # Overview page (score, checks, top issues)
|
||||
NamespacesListView.tsx # Namespace table with scores
|
||||
NamespaceDetailView.tsx # Drawer panel with per-namespace drill-down
|
||||
PolarisSettings.tsx # Settings page (refresh, URL, test)
|
||||
AppBarScoreBadge.tsx # Cluster score chip in top nav bar
|
||||
InlineAuditSection.tsx # Injected into resource detail views
|
||||
test-utils.tsx # Test helpers (wrapper with context)
|
||||
|
||||
.github/workflows/
|
||||
ci.yaml # Lint, type-check, build, test
|
||||
e2e.yaml # Playwright E2E tests
|
||||
release.yaml # Automated releases
|
||||
|
||||
e2e/ # Playwright tests
|
||||
polaris.spec.ts # Main plugin functionality
|
||||
settings.spec.ts # Settings page
|
||||
appbar.spec.ts # App bar badge
|
||||
auth.setup.ts # OIDC/token auth setup
|
||||
|
||||
docs/ # Comprehensive documentation
|
||||
architecture/ # Overview, design decisions, ADRs
|
||||
deployment/ # Helm, Kubernetes, production guides
|
||||
troubleshooting/ # Common issues, RBAC, network problems
|
||||
getting-started/ # Quick start, prerequisites, installation
|
||||
|
||||
package.json # Version, scripts, dependencies
|
||||
artifacthub-pkg.yml # ArtifactHub metadata (version, checksum)
|
||||
tsconfig.json # Extends @kinvolk/headlamp-plugin config
|
||||
vitest.config.mts # Vitest config (jsdom, excludes e2e/)
|
||||
.eslintrc.js # Extends @headlamp-k8s/eslint-config
|
||||
.prettierrc.js # Uses @headlamp-k8s prettier config
|
||||
```
|
||||
|
||||
## MCP Servers (Claude Code)
|
||||
|
||||
- **GitHub**: Source control (`github-mcp-server`), repo at `cpfarhood/headlamp-polaris-plugin`
|
||||
- **Kubernetes (local)**: Cluster access via `kubernetes-mcp-server`
|
||||
- **Flux (local)**: Flux Operator access via `flux-operator-mcp`
|
||||
- **Playwright**: Browser automation via `@playwright/mcp`
|
||||
|
||||
## Common Tasks Quick Reference
|
||||
|
||||
```bash
|
||||
# Start development
|
||||
npm install && npm start
|
||||
|
||||
# Run all checks before PR
|
||||
npm run build && npm run lint && npm run tsc && npm test && npm run format
|
||||
|
||||
# Create release (maintainers only)
|
||||
# 1. Edit CHANGELOG.md
|
||||
# 2. Trigger release workflow:
|
||||
gh workflow run release.yaml -f version=0.4.2
|
||||
|
||||
# Run E2E tests locally
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
npm run e2e
|
||||
|
||||
# Fix formatting issues
|
||||
npx prettier --write src/
|
||||
|
||||
# Check Polaris audit freshness
|
||||
kubectl get --raw "/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json" | jq -r '.AuditTime'
|
||||
|
||||
# Restart Polaris for fresh audit
|
||||
kubectl rollout restart deployment -n polaris polaris-dashboard
|
||||
```
|
||||
|
||||
## Anti-Patterns (What NOT to Do)
|
||||
|
||||
- ❌ Import from `@mui/material` or `@mui/icons-material` → breaks plugin
|
||||
- ❌ Use `any` type → strict TypeScript required
|
||||
- ❌ Push code changes directly to main → always use feature branches
|
||||
- ❌ Grant broader RBAC than `get services/proxy` → security risk
|
||||
- ❌ Use ClusterRole instead of namespaced Role → violates least privilege
|
||||
- ❌ Forget to run `npx prettier --write src/` → CI will fail
|
||||
- ❌ Use inline styles without CSS variables → breaks dark mode
|
||||
- ❌ Try to query K8s resources directly → plugin only has service proxy access
|
||||
- ❌ Import Headlamp `Link` for plugin routes → use react-router-dom `Link` + `Router.createRouteURL()`
|
||||
- ❌ Assume Polaris continuously re-audits → it only audits at pod startup
|
||||
|
||||
## Quick Diagnosis Guide
|
||||
|
||||
```
|
||||
Symptom: Plugin not in sidebar
|
||||
→ Check: Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+R)
|
||||
→ Check: Plugin installed? kubectl get configmap headlamp-plugin-config -n kube-system
|
||||
|
||||
Symptom: 403 Access Denied
|
||||
→ Check: RBAC binding exists? kubectl get role,rolebinding -n polaris
|
||||
→ Fix: Apply RBAC example from docs/deployment/rbac.md
|
||||
|
||||
Symptom: 404 or 503
|
||||
→ Check: Polaris installed? kubectl get pods -n polaris
|
||||
→ Check: Service exists? kubectl get svc polaris-dashboard -n polaris
|
||||
|
||||
Symptom: Stale audit data
|
||||
→ Fix: kubectl rollout restart deployment -n polaris polaris-dashboard
|
||||
→ Verify: Check AuditTime in UI matches current date
|
||||
|
||||
Symptom: Settings page empty or broken
|
||||
→ Check: Plugin version ≥ v0.3.3?
|
||||
→ Fix: Upgrade plugin and hard refresh browser
|
||||
|
||||
Symptom: CI prettier check fails
|
||||
→ Fix: npx prettier --write src/
|
||||
→ Commit: Include formatting fixes in your PR
|
||||
|
||||
Symptom: Dark mode white backgrounds
|
||||
→ Check: Plugin version ≥ v0.3.5?
|
||||
→ Fix: Upgrade and hard refresh browser
|
||||
```
|
||||
|
||||
## Historical Context
|
||||
|
||||
### Why Service Proxy Instead of ConfigMaps?
|
||||
|
||||
Early versions (< v0.0.10) incorrectly documented ConfigMap RBAC. The plugin **never** accessed ConfigMaps - it always used the service proxy. This was clarified in v0.0.10.
|
||||
|
||||
### Why No MUI Imports?
|
||||
|
||||
v0.3.2 removed direct MUI imports because they caused plugin load failures. Headlamp provides all needed MUI components as re-exports through `CommonComponents`.
|
||||
|
||||
### Why React Context?
|
||||
|
||||
ADR-001 documents the switch to React Context. Before v0.3.0, each component called `usePolarisData()` independently, causing duplicate API requests. Context ensures a single shared fetch.
|
||||
|
||||
### Why No Continuous Polaris Audits?
|
||||
|
||||
Polaris dashboard mode runs a one-time audit at pod startup and caches results. This is by design in Polaris itself. For continuous auditing, Polaris would need to be configured in webhook mode (admission controller), which is a different deployment pattern.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-02-12
|
||||
**Version**: v0.4.1
|
||||
**Target Headlamp**: v0.26+
|
||||
**Target Polaris**: v9.x
|
||||
+457
@@ -0,0 +1,457 @@
|
||||
# Contributing to Headlamp Polaris Plugin
|
||||
|
||||
Thank you for your interest in contributing to the Headlamp Polaris Plugin! This document provides guidelines and instructions for contributing to the project.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Development Workflow](#development-workflow)
|
||||
- [Branching Strategy](#branching-strategy)
|
||||
- [Commit Message Guidelines](#commit-message-guidelines)
|
||||
- [Pull Request Process](#pull-request-process)
|
||||
- [Code Style](#code-style)
|
||||
- [Testing Requirements](#testing-requirements)
|
||||
- [Documentation](#documentation)
|
||||
- [Release Process](#release-process)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project follows a standard code of conduct:
|
||||
- Be respectful and inclusive
|
||||
- Welcome newcomers and help them get started
|
||||
- Focus on constructive feedback
|
||||
- Assume good intentions
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 20 or later
|
||||
- npm or yarn
|
||||
- Access to a Kubernetes cluster with Headlamp installed (for testing)
|
||||
- Git
|
||||
|
||||
### Development Setup
|
||||
|
||||
1. **Fork and clone the repository:**
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/headlamp-polaris-plugin.git
|
||||
cd headlamp-polaris-plugin
|
||||
```
|
||||
|
||||
2. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Start development mode:**
|
||||
```bash
|
||||
npm start
|
||||
# Plugin will be available at http://localhost:4466
|
||||
```
|
||||
|
||||
4. **Run tests:**
|
||||
```bash
|
||||
# Unit tests
|
||||
npm test
|
||||
|
||||
# E2E tests (requires Headlamp instance)
|
||||
npm run e2e
|
||||
```
|
||||
|
||||
5. **Build the plugin:**
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Feature Development
|
||||
|
||||
1. Create a feature branch from `main`
|
||||
2. Make your changes
|
||||
3. Write/update tests
|
||||
4. Update documentation
|
||||
5. Run lint and tests locally
|
||||
6. Submit a pull request
|
||||
|
||||
### Local Testing
|
||||
|
||||
**Option 1: Development Mode**
|
||||
```bash
|
||||
npm start
|
||||
# Opens Headlamp at http://localhost:4466 with hot reload
|
||||
```
|
||||
|
||||
**Option 2: Production Build**
|
||||
```bash
|
||||
npm run build
|
||||
# Plugin bundle created in dist/
|
||||
```
|
||||
|
||||
**Option 3: E2E Testing**
|
||||
```bash
|
||||
# Set up environment (see e2e/README.md)
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
npm run e2e
|
||||
```
|
||||
|
||||
## Branching Strategy
|
||||
|
||||
### Main Branch
|
||||
|
||||
- **Purpose:** Stable, production-ready code
|
||||
- **Protection:** Only merge via pull requests
|
||||
- **Naming:** `main`
|
||||
|
||||
### Feature Branches
|
||||
|
||||
- **Purpose:** Development of new features or fixes
|
||||
- **Naming Convention:**
|
||||
- Features: `feat/description` or `feature/description`
|
||||
- Bug fixes: `fix/description`
|
||||
- Documentation: `docs/description`
|
||||
- Refactoring: `refactor/description`
|
||||
- Chores: `chore/description`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
feat/add-exemption-support
|
||||
fix/dark-mode-theme-colors
|
||||
docs/update-rbac-guide
|
||||
refactor/polaris-api-client
|
||||
chore/upgrade-dependencies
|
||||
```
|
||||
|
||||
### Branching Rules
|
||||
|
||||
**✅ ALWAYS use feature branches for:**
|
||||
- Code changes (new features, bug fixes, refactors)
|
||||
- Test updates
|
||||
- CI/CD workflow changes
|
||||
- Package updates
|
||||
|
||||
**✅ MAY push directly to main for:**
|
||||
- Documentation-only changes (README.md, CLAUDE.md, comments)
|
||||
- Version bump commits (`package.json` + `artifacthub-pkg.yml`)
|
||||
|
||||
**❌ NEVER push directly to main for:**
|
||||
- Any code changes to `src/`
|
||||
- Test file changes
|
||||
- Workflow changes
|
||||
- Dependency updates
|
||||
|
||||
## Commit Message Guidelines
|
||||
|
||||
We follow [Conventional Commits](https://www.conventionalcommits.org/) format:
|
||||
|
||||
### Format
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
- **feat:** New feature
|
||||
- **fix:** Bug fix
|
||||
- **docs:** Documentation only
|
||||
- **style:** Code style (formatting, no logic change)
|
||||
- **refactor:** Code change that neither fixes a bug nor adds a feature
|
||||
- **perf:** Performance improvement
|
||||
- **test:** Adding or updating tests
|
||||
- **chore:** Maintenance tasks (deps, build, CI)
|
||||
- **ci:** CI/CD changes
|
||||
|
||||
### Scope (Optional)
|
||||
|
||||
- `api` - API-related changes
|
||||
- `ui` - UI component changes
|
||||
- `settings` - Plugin settings
|
||||
- `tests` - Test-related changes
|
||||
- `docs` - Documentation changes
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
feat(api): add support for custom Polaris dashboard URLs
|
||||
|
||||
fix(ui): resolve dark mode theme color inconsistencies
|
||||
|
||||
docs: update RBAC examples with NetworkPolicy
|
||||
|
||||
chore: bump version to 0.3.5
|
||||
|
||||
test(e2e): add tests for plugin settings page
|
||||
```
|
||||
|
||||
### Footer
|
||||
|
||||
Add `Co-Authored-By` for pair programming or AI assistance:
|
||||
|
||||
```
|
||||
feat: add namespace filtering to overview
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
||||
Co-Authored-By: Happy <yesreply@happy.engineering>
|
||||
```
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
### Before Creating a PR
|
||||
|
||||
1. **Run all checks locally:**
|
||||
```bash
|
||||
npm run build # Verify build succeeds
|
||||
npm run lint # Check for linting errors
|
||||
npm run tsc # Type-check TypeScript
|
||||
npm test # Run unit tests
|
||||
npm run format # Format code with Prettier
|
||||
```
|
||||
|
||||
2. **Update documentation:**
|
||||
- Update README.md if you added features or changed behavior
|
||||
- Update CLAUDE.md if you changed architecture or constraints
|
||||
- Add/update JSDoc comments for new APIs
|
||||
|
||||
3. **Write/update tests:**
|
||||
- Add unit tests for new functions/components
|
||||
- Update E2E tests if UI behavior changed
|
||||
- Ensure all tests pass
|
||||
|
||||
### Creating a PR
|
||||
|
||||
1. **Push your branch:**
|
||||
```bash
|
||||
git push origin feat/your-feature
|
||||
```
|
||||
|
||||
2. **Create PR on GitHub:**
|
||||
- Use a descriptive title following commit conventions
|
||||
- Fill out the PR template (if available)
|
||||
- Link related issues with `Fixes #123` or `Closes #456`
|
||||
|
||||
3. **PR Title Format:**
|
||||
```
|
||||
feat: add exemption management UI
|
||||
fix: correct score calculation for skipped checks
|
||||
docs: improve deployment guide with Helm examples
|
||||
```
|
||||
|
||||
4. **PR Description Should Include:**
|
||||
- Summary of changes
|
||||
- Motivation and context
|
||||
- Testing performed
|
||||
- Screenshots (for UI changes)
|
||||
- Breaking changes (if any)
|
||||
|
||||
### PR Review Process
|
||||
|
||||
1. **Automated Checks:**
|
||||
- ✅ CI workflow (lint, type-check, build, test)
|
||||
- ✅ E2E tests (may fail if plugin not deployed)
|
||||
|
||||
2. **Maintainer Review:**
|
||||
- Code quality and style
|
||||
- Test coverage
|
||||
- Documentation completeness
|
||||
- Breaking changes assessment
|
||||
|
||||
3. **Merging:**
|
||||
- Use **merge commits** (not squash, not rebase)
|
||||
- Delete feature branch after merge
|
||||
- Maintainers will handle version bumps and releases
|
||||
|
||||
## Code Style
|
||||
|
||||
### TypeScript
|
||||
|
||||
- **Strictness:** Full TypeScript strict mode enabled
|
||||
- **No `any`:** Use specific types or `unknown`
|
||||
- **Interfaces over types:** Prefer `interface` for object shapes
|
||||
- **Named exports:** Use named exports, not default exports
|
||||
|
||||
### React
|
||||
|
||||
- **Functional components:** Use function components with hooks
|
||||
- **Props interfaces:** Always define props as interfaces
|
||||
- **Headlamp components:** Use CommonComponents from Headlamp, never raw MUI
|
||||
- **No inline styles:** Use theme-aware CSS variables
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
```bash
|
||||
# Auto-fix linting issues
|
||||
npm run lint:fix
|
||||
|
||||
# Format code
|
||||
npm run format
|
||||
|
||||
# Check formatting
|
||||
npm run format:check
|
||||
```
|
||||
|
||||
### Import Organization
|
||||
|
||||
Imports are automatically sorted by eslint. Order:
|
||||
1. React imports
|
||||
2. Third-party libraries
|
||||
3. Headlamp plugin imports
|
||||
4. Local imports (components, API, types)
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
import React from 'react';
|
||||
import { SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
import { computeScore } from '../api/polaris';
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- **Components:** PascalCase (`DashboardView`, `PolarisSettings`)
|
||||
- **Files:** Match component name (`DashboardView.tsx`)
|
||||
- **Hooks:** Prefix with `use` (`usePolarisData`)
|
||||
- **Utilities:** camelCase (`countResults`, `computeScore`)
|
||||
- **Constants:** UPPER_SNAKE_CASE (`DASHBOARD_URL_DEFAULT`)
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### Unit Tests (Required)
|
||||
|
||||
- All new functions must have unit tests
|
||||
- All bug fixes should include regression tests
|
||||
- Aim for meaningful coverage, not just numbers
|
||||
- Use descriptive test names
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
describe('countResults', () => {
|
||||
it('counts passing, warning, and danger results correctly', () => {
|
||||
// Test implementation
|
||||
});
|
||||
|
||||
it('includes skipped checks in total count', () => {
|
||||
// Test implementation
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### E2E Tests (Recommended)
|
||||
|
||||
- Add E2E tests for new UI features
|
||||
- Update existing tests if behavior changes
|
||||
- See `e2e/README.md` for detailed instructions
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all unit tests
|
||||
npm test
|
||||
|
||||
# Run tests in watch mode
|
||||
npm run test:watch
|
||||
|
||||
# Run E2E tests
|
||||
npm run e2e
|
||||
|
||||
# Run E2E tests in headed mode (see browser)
|
||||
npm run e2e:headed
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
### Documentation Updates Required
|
||||
|
||||
When making changes, update relevant documentation:
|
||||
|
||||
#### Code Changes
|
||||
- **README.md:** User-facing features, installation, configuration
|
||||
- **CLAUDE.md:** Architecture, constraints, MCP integrations
|
||||
- **JSDoc:** All public APIs, components, hooks
|
||||
|
||||
#### Test Changes
|
||||
- **e2e/README.md:** New test scenarios or setup changes
|
||||
|
||||
#### Build/CI Changes
|
||||
- **README.md:** Build commands, release process
|
||||
- **.github/workflows/*.yaml:** Workflow comments
|
||||
|
||||
### JSDoc Style
|
||||
|
||||
Use JSDoc for all exported functions, components, and types:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Counts passing, warning, danger, and skipped Polaris check results.
|
||||
*
|
||||
* Skipped checks are identified by severity "ignore" with success false.
|
||||
*
|
||||
* @param data - AuditData from Polaris dashboard API
|
||||
* @returns ResultCounts with totals by status (pass/warning/danger/skipped)
|
||||
*/
|
||||
export function countResults(data: AuditData): ResultCounts {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Release Process
|
||||
|
||||
### Version Numbering
|
||||
|
||||
We follow [Semantic Versioning](https://semver.org/):
|
||||
- **Major (1.0.0):** Breaking changes
|
||||
- **Minor (0.1.0):** New features, backward compatible
|
||||
- **Patch (0.0.1):** Bug fixes, backward compatible
|
||||
|
||||
### Creating a Release
|
||||
|
||||
**Maintainers only:**
|
||||
|
||||
1. **Merge feature PRs to main**
|
||||
|
||||
2. **Bump version:**
|
||||
```bash
|
||||
# Edit package.json and artifacthub-pkg.yml
|
||||
# Update version and archive-url
|
||||
git add package.json artifacthub-pkg.yml
|
||||
git commit -m "chore: bump version to X.Y.Z"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
3. **Create and push tag:**
|
||||
```bash
|
||||
git tag vX.Y.Z
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
4. **GitHub Actions automatically:**
|
||||
- Builds plugin tarball
|
||||
- Creates GitHub release
|
||||
- Uploads tarball to release
|
||||
- Updates `artifacthub-pkg.yml` with checksum
|
||||
|
||||
5. **ArtifactHub syncs within 30 minutes**
|
||||
|
||||
### Pre-release Versions
|
||||
|
||||
For testing before stable release:
|
||||
- Use `-dev.N` suffix: `v0.3.5-dev.1`
|
||||
- Follow same process as stable releases
|
||||
- Mark as "pre-release" on GitHub
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Questions:** Open a [GitHub Discussion](https://github.com/privilegedescalation/headlamp-polaris-plugin/discussions)
|
||||
- **Bugs:** Open a [GitHub Issue](https://github.com/privilegedescalation/headlamp-polaris-plugin/issues)
|
||||
- **E2E Testing:** See [e2e/README.md](e2e/README.md)
|
||||
- **Architecture:** See [CLAUDE.md](CLAUDE.md)
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under the Apache-2.0 License.
|
||||
@@ -0,0 +1,290 @@
|
||||
# Headlamp Polaris Plugin - Project Assessment
|
||||
|
||||
**Date:** 2026-02-11
|
||||
**Version:** v0.3.0
|
||||
**Status:** Active Development
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This assessment identifies critical issues and improvement opportunities for the headlamp-polaris-plugin project. The plugin is currently non-functional in production due to Headlamp v0.39.0 compatibility issues, and has several TypeScript compilation errors that need immediate attention.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Critical Issues (Must Fix Immediately)
|
||||
|
||||
### 1. TypeScript Compilation Errors
|
||||
**Severity:** CRITICAL
|
||||
**Impact:** Build failures, type safety compromised
|
||||
|
||||
**Issues:**
|
||||
- `src/index.tsx:72` - `registerDetailsViewSection` expects 1 argument, got 2
|
||||
- `src/index.tsx:87` - `registerAppBarAction` expects 1 argument, got 2
|
||||
|
||||
**Recommendation:**
|
||||
Update Headlamp plugin API calls to match the current version. Check @kinvolk/headlamp-plugin version compatibility.
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Review Headlamp plugin API documentation
|
||||
- [ ] Update `registerDetailsViewSection` and `registerAppBarAction` calls
|
||||
- [ ] Run `npm run tsc` to verify fixes
|
||||
- [ ] Update CI to fail on TypeScript errors
|
||||
|
||||
---
|
||||
|
||||
### 2. Production Plugin Loading Failure
|
||||
**Severity:** CRITICAL
|
||||
**Impact:** Plugin is completely non-functional in production
|
||||
|
||||
**Root Cause:**
|
||||
Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugins as "development directory" plugins, preventing frontend JavaScript execution.
|
||||
|
||||
**Current Status:**
|
||||
- Deployment patched to install plugins to `/headlamp/static-plugins`
|
||||
- `watchPlugins: false` configured
|
||||
- Waiting for user to test if plugins now load
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Confirm plugins load after recent deployment changes
|
||||
- [ ] Document the fix in deployment guide
|
||||
- [ ] Update MEMORY.md with final resolution
|
||||
- [ ] Consider downgrading Headlamp if issue persists
|
||||
|
||||
---
|
||||
|
||||
### 3. Test Failures
|
||||
**Severity:** HIGH
|
||||
**Impact:** CI failures, reduced confidence in changes
|
||||
|
||||
**Current Status:**
|
||||
- 1 test file failing (DashboardView)
|
||||
- 49 tests passing
|
||||
- Error related to `SimpleTable` component mock
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Fix DashboardView test mocking
|
||||
- [ ] Ensure all tests pass before merging PRs
|
||||
- [ ] Add test for top issues feature
|
||||
- [ ] Increase test coverage to >80%
|
||||
|
||||
---
|
||||
|
||||
## 🟡 High Priority Improvements
|
||||
|
||||
### 4. Type Safety Enhancements
|
||||
**Severity:** HIGH
|
||||
**Impact:** Better developer experience, catch errors earlier
|
||||
|
||||
**Recommendations:**
|
||||
- Enable stricter TypeScript checks in `tsconfig.json`
|
||||
- Add type definitions for all Headlamp plugin APIs
|
||||
- Ensure no `any` types in production code
|
||||
- Add JSDoc comments for complex types
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Audit codebase for `any` types
|
||||
- [ ] Enable `noImplicitAny` and `strictNullChecks`
|
||||
- [ ] Add type guards for API responses
|
||||
- [ ] Document complex type structures
|
||||
|
||||
---
|
||||
|
||||
### 5. Security Hardening
|
||||
**Severity:** HIGH
|
||||
**Impact:** Prevent vulnerabilities, protect user data
|
||||
|
||||
**Current Risks:**
|
||||
- Direct Kubernetes API access via service proxy
|
||||
- User input in exemption annotations (potential injection)
|
||||
- External URL configuration for Polaris dashboard
|
||||
|
||||
**Recommendations:**
|
||||
- Validate and sanitize all user inputs
|
||||
- Implement input validation for dashboard URL
|
||||
- Add CSRF protection for exemption management
|
||||
- Audit dependencies for known vulnerabilities
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Add input validation utilities
|
||||
- [ ] Sanitize exemption annotation values
|
||||
- [ ] Validate URL format for dashboard configuration
|
||||
- [ ] Run `npm audit` and fix vulnerabilities
|
||||
- [ ] Add security testing to CI/CD
|
||||
|
||||
---
|
||||
|
||||
### 6. Error Handling & User Experience
|
||||
**Severity:** MEDIUM
|
||||
**Impact:** Better error messages, improved debugging
|
||||
|
||||
**Current Gaps:**
|
||||
- Generic error messages don't help users troubleshoot
|
||||
- No retry logic for transient API failures
|
||||
- Missing loading states in some components
|
||||
|
||||
**Recommendations:**
|
||||
- Provide specific, actionable error messages
|
||||
- Implement retry logic with exponential backoff
|
||||
- Add loading skeletons for all async operations
|
||||
- Show connection test results with specific failure reasons
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Create error message constants with solutions
|
||||
- [ ] Add retry logic to API calls
|
||||
- [ ] Implement loading skeletons
|
||||
- [ ] Improve connection test error messages
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Medium Priority Enhancements
|
||||
|
||||
### 7. Testing Coverage
|
||||
**Severity:** MEDIUM
|
||||
**Impact:** Confidence in changes, regression prevention
|
||||
|
||||
**Current Coverage:**
|
||||
- Unit tests: Good coverage for API utilities
|
||||
- Component tests: Some coverage, gaps exist
|
||||
- E2E tests: Minimal (Playwright configured but underutilized)
|
||||
|
||||
**Recommendations:**
|
||||
- Add E2E tests for critical user flows
|
||||
- Test error scenarios and edge cases
|
||||
- Add visual regression tests
|
||||
- Test RBAC permission denied scenarios
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Write E2E test for complete audit workflow
|
||||
- [ ] Add tests for error states
|
||||
- [ ] Test exemption management flow
|
||||
- [ ] Add Playwright tests to CI
|
||||
|
||||
---
|
||||
|
||||
### 8. Performance Optimization
|
||||
**Severity:** MEDIUM
|
||||
**Impact:** Faster load times, better UX
|
||||
|
||||
**Opportunities:**
|
||||
- Memoize expensive calculations (score computation)
|
||||
- Lazy load namespace detail views
|
||||
- Debounce search/filter operations
|
||||
- Cache Polaris data with stale-while-revalidate
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Add React.memo to pure components
|
||||
- [ ] Memoize score calculations
|
||||
- [ ] Implement data caching strategy
|
||||
- [ ] Profile component render times
|
||||
|
||||
---
|
||||
|
||||
### 9. Code Quality & Maintainability
|
||||
**Severity:** MEDIUM
|
||||
**Impact:** Easier maintenance, onboarding
|
||||
|
||||
**Recommendations:**
|
||||
- Extract magic strings to constants
|
||||
- Reduce component complexity
|
||||
- Add JSDoc comments for public APIs
|
||||
- Improve code organization
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Create constants file for check IDs
|
||||
- [ ] Split large components (DashboardView, NamespaceDetailView)
|
||||
- [ ] Add comments for complex logic
|
||||
- [ ] Establish code review checklist
|
||||
|
||||
---
|
||||
|
||||
## 🔵 Low Priority / Future Enhancements
|
||||
|
||||
### 10. Documentation
|
||||
**Severity:** LOW
|
||||
**Impact:** Better onboarding, user adoption
|
||||
|
||||
**Gaps:**
|
||||
- No architecture documentation
|
||||
- Limited inline code comments
|
||||
- Missing troubleshooting guide
|
||||
- No contributor guidelines
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Create architecture diagram
|
||||
- [ ] Document component hierarchy
|
||||
- [ ] Add troubleshooting section to README
|
||||
- [ ] Create CONTRIBUTING.md
|
||||
|
||||
---
|
||||
|
||||
### 11. CI/CD Pipeline Optimization
|
||||
**Severity:** LOW
|
||||
**Impact:** Faster feedback, automated releases
|
||||
|
||||
**Opportunities:**
|
||||
- Run tests in parallel
|
||||
- Cache npm dependencies
|
||||
- Add automated security scanning
|
||||
- Implement semantic versioning
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Parallelize test execution
|
||||
- [ ] Add npm cache to GitHub Actions
|
||||
- [ ] Integrate Dependabot
|
||||
- [ ] Add semantic-release
|
||||
|
||||
---
|
||||
|
||||
## Summary & Prioritization
|
||||
|
||||
### Week 1 (Immediate)
|
||||
1. ✅ Fix TypeScript compilation errors
|
||||
2. ✅ Resolve production plugin loading issue
|
||||
3. ✅ Fix failing DashboardView test
|
||||
|
||||
### Week 2 (High Priority)
|
||||
4. Enhance type safety (strict mode)
|
||||
5. Implement security hardening
|
||||
6. Improve error handling and UX
|
||||
|
||||
### Week 3-4 (Medium Priority)
|
||||
7. Increase test coverage to >80%
|
||||
8. Optimize performance (memoization, caching)
|
||||
9. Refactor for maintainability
|
||||
|
||||
### Ongoing (Low Priority)
|
||||
10. Documentation improvements
|
||||
11. CI/CD optimizations
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
**Code Quality:**
|
||||
- ✅ Zero TypeScript errors
|
||||
- ✅ All tests passing
|
||||
- 🎯 Test coverage >80%
|
||||
- 🎯 No high/critical security vulnerabilities
|
||||
|
||||
**Production Readiness:**
|
||||
- ✅ Plugin loads successfully in Headlamp
|
||||
- ✅ All features functional
|
||||
- 🎯 Error rate <1%
|
||||
- 🎯 Average response time <500ms
|
||||
|
||||
**Developer Experience:**
|
||||
- ✅ Clear documentation
|
||||
- ✅ Easy local setup
|
||||
- 🎯 Fast CI/CD (<5 min)
|
||||
- 🎯 Automated releases
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Immediate:** Fix TypeScript errors and verify plugin loads
|
||||
2. **Short-term:** Complete Week 1-2 priorities
|
||||
3. **Long-term:** Address medium and low priority items
|
||||
4. **Continuous:** Monitor metrics and iterate
|
||||
|
||||
**Recommended First Action:**
|
||||
Fix the TypeScript compilation errors in `src/index.tsx` by updating the Headlamp plugin API calls.
|
||||
@@ -1,147 +1,123 @@
|
||||
# headlamp-polaris-plugin
|
||||
# Headlamp Polaris Plugin
|
||||
|
||||
[](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin)
|
||||
[](https://github.com/privilegedescalation/headlamp-polaris-plugin/actions/workflows/ci.yaml)
|
||||
[](https://github.com/privilegedescalation/headlamp-polaris-plugin/actions/workflows/e2e.yaml)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
A [Headlamp](https://headlamp.dev/) plugin that surfaces [Fairwinds Polaris](https://polaris.docs.fairwinds.com/) audit results directly in the Headlamp UI.
|
||||
|
||||
**[Documentation](#documentation) | [Installation](#installing) | [Security](#rbac--security-setup) | [Development](#development)**
|
||||
|
||||
## What It Does
|
||||
|
||||
Adds a **Polaris** top-level sidebar section to Headlamp with the following views:
|
||||
Adds a **Polaris** top-level sidebar section to Headlamp with comprehensive security, reliability, and efficiency audit integration:
|
||||
|
||||
- **Overview** -- cluster score as a percentage (color-coded green/amber/red), check summary (pass/warning/danger/skipped counts), and cluster info (nodes, pods, namespaces, controllers)
|
||||
- **Namespaces** -- table of all namespaces with per-namespace score, pass/warning/danger/skipped counts; click a namespace to drill down
|
||||
- **Namespace detail** -- per-namespace score, check counts, and a resource table showing pass/warning/danger per workload
|
||||
- **External link** -- quick jump to the native Polaris dashboard via the Kubernetes service proxy (from namespace detail view)
|
||||
### Main Views
|
||||
|
||||
Data is fetched from the Polaris dashboard API through the Kubernetes service proxy (`/api/v1/namespaces/polaris/services/polaris-dashboard/proxy/results.json`). The plugin is read-only -- it never writes to the cluster.
|
||||
- **Overview Dashboard** -- cluster score with percentage gauge, check distribution charts, top 10 most common failing checks across the cluster, cluster statistics, and last audit time with manual refresh button
|
||||
- **Namespaces** -- table of all namespaces with per-namespace score and check counts; click a namespace to open a detailed side panel (1000px wide, theme-aware)
|
||||
- **Namespace Detail Panel** -- per-namespace score, check counts, resource-level audit results, external Polaris dashboard link, and exemption management
|
||||
|
||||
Results are refreshed on a user-configurable interval (1 / 5 / 10 / 30 minutes, default 5). The setting is available in **Settings > Plugins > Polaris** and persists in the browser's localStorage.
|
||||
### Integrated Features
|
||||
|
||||
Error states are handled explicitly: RBAC denied (403), Polaris not installed (404/503), malformed JSON, and loading.
|
||||
- **App Bar Score Badge** -- cluster Polaris score displayed as a colored chip in the top navigation bar (green ≥80%, yellow ≥50%, red <50%); click to navigate to overview
|
||||
- **Inline Resource Audits** -- Polaris audit results automatically injected into detail views for Deployments, StatefulSets, DaemonSets, Jobs, and CronJobs; shows compact score, failing checks table, and link to full report
|
||||
- **Exemption Management** -- add or remove Polaris exemptions via annotation patches directly from the UI; supports per-check exemptions or exempt-all
|
||||
- **Configurable Dashboard URL** -- supports both Kubernetes service proxy URLs and full HTTP/HTTPS URLs for external Polaris deployments
|
||||
- **Connection Testing** -- test button in settings to verify Polaris dashboard connectivity and show version info
|
||||
- **Dark Mode Support** -- full theme adaptation using MUI CSS variables; drawer, settings, and all UI elements respect system/Headlamp theme
|
||||
|
||||
### Data & Refresh
|
||||
|
||||
Data is fetched from the Polaris dashboard API through the Kubernetes service proxy (`/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`) or custom URLs. The plugin is primarily read-only; it only writes when explicitly applying exemption annotations.
|
||||
|
||||
Results are refreshed on a user-configurable interval (1 / 5 / 10 / 30 minutes, default 5). Settings are available in **Settings > Plugins > Polaris** and persist in browser localStorage.
|
||||
|
||||
Error states are handled explicitly with context-specific messages: RBAC denied (403), Polaris not installed (404/503), malformed JSON, network failures, and CORS issues.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
| Requirement | Minimum version |
|
||||
|-------------|----------------|
|
||||
| Headlamp | v0.26+ |
|
||||
| Requirement | Minimum version |
|
||||
| -------------------------------- | ------------------ |
|
||||
| Headlamp | v0.26+ |
|
||||
| Polaris (with dashboard enabled) | Any recent release |
|
||||
| Kubernetes | v1.24+ |
|
||||
| Kubernetes | v1.24+ |
|
||||
|
||||
Polaris must be deployed in the `polaris` namespace with the dashboard component enabled (`dashboard.enabled: true` in the Helm chart, which is the default). The plugin reads from the `polaris-dashboard` ClusterIP service on port 80.
|
||||
|
||||
## Installing
|
||||
|
||||
### Option 1: Artifact Hub + Headlamp plugin manager (recommended)
|
||||
### Option 1: Headlamp Plugin Manager (Recommended)
|
||||
|
||||
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Configure Headlamp's `pluginsManager` in your Helm values to install it automatically:
|
||||
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Configure Headlamp via Helm:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
sources:
|
||||
- url: https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin
|
||||
- name: headlamp-polaris-plugin
|
||||
url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
Headlamp will fetch and install the plugin on startup.
|
||||
Or install via the Headlamp UI:
|
||||
|
||||
### Option 2: Docker init container
|
||||
1. Go to **Settings → Plugins**
|
||||
2. Click **Catalog** tab
|
||||
3. Search for "Polaris"
|
||||
4. Click **Install**
|
||||
|
||||
The plugin ships as a container image at `git.farh.net/farhoodliquor/headlamp-polaris-plugin`.
|
||||
### Option 2: Sidecar Container (Alternative)
|
||||
|
||||
Add it as an init container in your Headlamp Helm values:
|
||||
For detailed sidecar installation instructions, see [docs/DEPLOYMENT.md#installation-method-2-sidecar-container](docs/DEPLOYMENT.md#installation-method-2-sidecar-container).
|
||||
|
||||
```yaml
|
||||
initContainers:
|
||||
- name: polaris-plugin
|
||||
image: git.farh.net/farhoodliquor/headlamp-polaris-plugin:latest
|
||||
command: ["sh", "-c", "cp -r /plugins/* /headlamp/plugins/"]
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /headlamp/plugins
|
||||
|
||||
volumes:
|
||||
- name: plugins
|
||||
emptyDir: {}
|
||||
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /headlamp/plugins
|
||||
```
|
||||
|
||||
### Option 3: Manual tarball install
|
||||
|
||||
Download the `.tar.gz` from the [GitHub releases page](https://github.com/cpfarhood/headlamp-polaris-plugin/releases) or the [Gitea releases page](https://git.farh.net/farhoodliquor/headlamp-polaris-plugin/releases), then extract into Headlamp's plugin directory:
|
||||
|
||||
```bash
|
||||
tar xzf headlamp-polaris-plugin-<version>.tar.gz -C /headlamp/plugins/
|
||||
```
|
||||
|
||||
### Option 4: Build from source
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
|
||||
```
|
||||
|
||||
## Installing Dev/Preview Versions
|
||||
|
||||
Dev preview versions (e.g., `v0.2.0-dev.4`) are published to [GitHub Releases](https://github.com/cpfarhood/headlamp-polaris-plugin/releases) but are **not available via ArtifactHub**. These versions contain experimental features and are intended for testing.
|
||||
|
||||
To install a dev version, use the direct URL method:
|
||||
|
||||
### Sidecar container pattern (recommended)
|
||||
|
||||
Create a ConfigMap with the dev version URL:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-plugin-config
|
||||
namespace: kube-system
|
||||
data:
|
||||
plugin.yml: |
|
||||
plugins:
|
||||
- url: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.2.0-dev.4/headlamp-polaris-plugin-0.2.0-dev.4.tar.gz
|
||||
```
|
||||
|
||||
Then configure Headlamp to use a sidecar container that installs from this config:
|
||||
|
||||
```yaml
|
||||
# In Headlamp Helm values or deployment
|
||||
containers:
|
||||
- name: headlamp-plugin-installer
|
||||
sidecars:
|
||||
- name: headlamp-plugin
|
||||
image: node:lts-alpine
|
||||
command: ["/bin/sh", "-c"]
|
||||
command: ['/bin/sh']
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
npm install -g @kinvolk/headlamp-plugin
|
||||
headlamp-plugin install --config /config/plugin.yml
|
||||
tail -f /dev/null
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /headlamp/plugins
|
||||
- name: plugin-config
|
||||
mountPath: /config
|
||||
volumes:
|
||||
- name: plugins
|
||||
emptyDir: {}
|
||||
- name: plugin-config
|
||||
configMap:
|
||||
name: headlamp-plugin-config
|
||||
```
|
||||
|
||||
### Manual download
|
||||
### Option 3: Manual Tarball Install
|
||||
|
||||
Browse [GitHub Releases](https://github.com/cpfarhood/headlamp-polaris-plugin/releases), download the dev version tarball, and extract it to your Headlamp plugins directory.
|
||||
Download the `.tar.gz` from the [GitHub releases page](https://github.com/privilegedescalation/headlamp-polaris-plugin/releases), then extract into Headlamp's plugin directory:
|
||||
|
||||
**Note:** Dev versions are tagged with the `-dev.N` suffix and may introduce breaking changes or unstable features. Use stable versions for production deployments.
|
||||
```bash
|
||||
wget https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
tar xzf polaris-0.3.10.tar.gz -C /headlamp/plugins/
|
||||
```
|
||||
|
||||
### Option 4: Build from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/privilegedescalation/headlamp-polaris-plugin.git
|
||||
cd headlamp-polaris-plugin
|
||||
npm install
|
||||
npm run build
|
||||
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
|
||||
```
|
||||
|
||||
For complete installation instructions including Helm integration, FluxCD examples, and production deployment checklist, see **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)**.
|
||||
|
||||
## RBAC / Security Setup
|
||||
|
||||
The plugin fetches audit data through the Kubernetes API server's **service proxy** sub-resource. The identity making the request (Headlamp's service account, or the user's own token in token-auth mode) must be granted:
|
||||
|
||||
| Verb | API Group | Resource | Resource Name | Namespace |
|
||||
|------|-----------|----------|---------------|-----------|
|
||||
| Verb | API Group | Resource | Resource Name | Namespace |
|
||||
| ----- | ----------- | ---------------- | ------------------- | --------- |
|
||||
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
|
||||
|
||||
### Minimal RBAC manifests
|
||||
@@ -153,10 +129,10 @@ metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
@@ -165,8 +141,8 @@ metadata:
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp # adjust to match your Headlamp service account
|
||||
namespace: kube-system # adjust to match the namespace Headlamp runs in
|
||||
name: headlamp # adjust to match your Headlamp service account
|
||||
namespace: kube-system # adjust to match the namespace Headlamp runs in
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -191,49 +167,88 @@ The plugin only performs `GET` requests through the service proxy. No `create`,
|
||||
|
||||
Every proxied request is recorded in Kubernetes API audit logs as a `get` on `services/proxy` in the `polaris` namespace. If the auto-refresh interval generates more audit volume than desired, increase the refresh interval in the plugin settings or adjust your audit policy.
|
||||
|
||||
## Documentation
|
||||
|
||||
**[Complete Documentation](docs/README.md)** - Documentation hub with all guides
|
||||
|
||||
### Quick Links
|
||||
|
||||
- **[Quick Start](docs/getting-started/quick-start.md)** - Get up and running in 5 minutes
|
||||
- **[Installation Guide](docs/getting-started/installation.md)** - All installation methods (Plugin Manager, Sidecar, Manual, Source)
|
||||
- **[Troubleshooting](docs/troubleshooting/README.md)** - Quick diagnosis and common issues
|
||||
|
||||
### Comprehensive Guides
|
||||
|
||||
| Guide | Description |
|
||||
| ------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| **[Architecture](docs/architecture/overview.md)** | System architecture, data flow, component hierarchy, design decisions |
|
||||
| **[Deployment](docs/deployment/helm.md)** | Production deployment with Helm, Kubernetes, FluxCD |
|
||||
| **[Security](SECURITY.md)** | Security model, RBAC requirements, vulnerability reporting |
|
||||
| **[Testing](docs/development/testing.md)** | Unit tests, E2E tests, CI/CD, best practices |
|
||||
| **[Contributing](CONTRIBUTING.md)** | Development workflow, branching strategy, PR process |
|
||||
| **[Changelog](CHANGELOG.md)** | Complete release history (v0.0.1 to current) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---------|-------------|-----|
|
||||
| **403 Access Denied** | Missing RBAC binding for `services/proxy` | Apply the Role + RoleBinding from the RBAC section above |
|
||||
| **404 or 503** | Polaris not installed, or dashboard disabled | Install Polaris with `dashboard.enabled: true` in the `polaris` namespace |
|
||||
| **No data** | Polaris running but no workloads scanned yet | Wait for the next Polaris audit cycle or restart the Polaris pod |
|
||||
| **Stale data** | Refresh interval too long | Lower the interval in **Settings > Plugins > Polaris** |
|
||||
**For comprehensive troubleshooting, see [docs/troubleshooting/README.md](docs/troubleshooting/README.md).**
|
||||
|
||||
Quick reference:
|
||||
|
||||
| Symptom | Likely Cause | Quick Fix |
|
||||
| ------------------------------- | --------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| **Plugin not in sidebar** | Plugin not installed or needs browser refresh | Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+F5) |
|
||||
| **403 Access Denied** | Missing RBAC binding for `services/proxy` | Apply Role + RoleBinding from RBAC section |
|
||||
| **404 or 503** | Polaris not installed, or dashboard disabled | Install Polaris with `dashboard.enabled: true` in `polaris` namespace |
|
||||
| **Dark mode white backgrounds** | Old plugin version | Upgrade to v0.3.5+ and hard refresh browser |
|
||||
| **Settings page empty** | Old plugin version | Upgrade to v0.3.3+ |
|
||||
| **No data / infinite spinner** | Network policy or Polaris pod down | Check network policies and `kubectl get pods -n polaris` |
|
||||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
**For detailed development guide, see [CONTRIBUTING.md](CONTRIBUTING.md).**
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
git clone https://github.com/cpfarhood/headlamp-polaris-plugin.git
|
||||
# Clone repository
|
||||
git clone https://github.com/privilegedescalation/headlamp-polaris-plugin.git
|
||||
cd headlamp-polaris-plugin
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
```
|
||||
|
||||
### Run locally (hot reload)
|
||||
# Run with hot reload
|
||||
npm start # Opens Headlamp at http://localhost:4466
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
This starts the Headlamp plugin dev server. Point a running Headlamp instance at the dev server to see changes live.
|
||||
|
||||
### Build for production
|
||||
|
||||
```bash
|
||||
# Build for production
|
||||
npm run build # outputs dist/main.js
|
||||
npm run package # creates headlamp-polaris-plugin-<version>.tar.gz
|
||||
|
||||
# Run tests
|
||||
npm test # unit tests
|
||||
npm run e2e # E2E tests (requires Headlamp instance)
|
||||
|
||||
# Code quality
|
||||
npm run lint # eslint
|
||||
npm run tsc # type-check
|
||||
npm run format # prettier format
|
||||
```
|
||||
|
||||
### Type-check, lint, format, and test
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
npm run tsc # type-check without emitting
|
||||
npm run lint # eslint
|
||||
npm run format:check # prettier check
|
||||
npm test # vitest unit tests
|
||||
# Unit tests (Vitest)
|
||||
npm test
|
||||
npm run test:watch
|
||||
|
||||
# E2E tests (Playwright)
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
npm run e2e
|
||||
npm run e2e:headed # see browser
|
||||
```
|
||||
|
||||
For complete testing guide including CI/CD integration, see **[docs/TESTING.md](docs/TESTING.md)**.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
@@ -282,11 +297,13 @@ Each check in a `ResultSet` has `Success` (bool) and `Severity` (`"warning"`, `"
|
||||
The **Skipped** count shown in the plugin only reflects checks with `Severity: "ignore"` in the Polaris API response. It does **not** include annotation-based exemptions (e.g., `polaris.fairwinds.com/privilegeEscalationAllowed-exempt: "true"`).
|
||||
|
||||
**Why?** Polaris completely omits exempted checks from the `results.json` endpoint. The native Polaris dashboard UI computes the "skipped" count client-side by:
|
||||
|
||||
1. Querying Kubernetes resources (Deployments, DaemonSets, StatefulSets, Pods) directly
|
||||
2. Parsing their annotations for `polaris.fairwinds.com/*-exempt` keys
|
||||
3. Counting how many checks were exempted
|
||||
|
||||
This plugin only has access to the processed audit results via the service proxy and does not query raw Kubernetes resources. To show accurate exemption counts, the plugin would need to:
|
||||
|
||||
- Request cluster-wide read access to all workload types (requires additional RBAC grants beyond `services/proxy`)
|
||||
- Parse annotations on every workload in every namespace
|
||||
- Cross-reference with the Polaris check catalog to count exemptions
|
||||
@@ -297,43 +314,59 @@ This is a significant architectural change and is not currently implemented. Hov
|
||||
|
||||
## Releasing
|
||||
|
||||
Releases are automated via CI. To cut a release:
|
||||
Releases are automated via **GitHub Actions**. To cut a release:
|
||||
|
||||
```bash
|
||||
# Bump version in package.json and artifacthub-pkg.yml (version + archive-url), then:
|
||||
git add package.json artifacthub-pkg.yml
|
||||
# 1. Update CHANGELOG.md with new version
|
||||
# 2. Bump version in package.json and artifacthub-pkg.yml:
|
||||
git add package.json artifacthub-pkg.yml CHANGELOG.md
|
||||
git commit -m "chore: bump version to X.Y.Z"
|
||||
git push origin main
|
||||
|
||||
# 3. Create and push tag:
|
||||
git tag vX.Y.Z
|
||||
git push origin main vX.Y.Z
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
This triggers the **Gitea Actions** release workflow (`.gitea/workflows/release.yaml`):
|
||||
This triggers the **GitHub Actions** release workflow (`.github/workflows/release.yaml`):
|
||||
|
||||
1. Build the plugin in a `node:20` container
|
||||
2. Package a `.tar.gz` tarball
|
||||
3. Build and push a Docker image to `git.farh.net/farhoodliquor/headlamp-polaris-plugin:{tag}` and `:latest`
|
||||
4. Create a Gitea release with the tarball attached
|
||||
5. Create a GitHub release with the same tarball (for Artifact Hub)
|
||||
6. Update `artifacthub-pkg.yml` checksum on main and force-move the tag to match
|
||||
3. Create a GitHub release with the tarball attached
|
||||
4. Calculate SHA256 checksum
|
||||
5. Update `artifacthub-pkg.yml` checksum on main branch
|
||||
6. Force-move the tag to include checksum update
|
||||
|
||||
A guard step prevents infinite loops: if the release tarball checksum already matches the metadata, the build is skipped.
|
||||
A guard step prevents infinite loops: if the release tarball checksum already matches the metadata, the workflow is skipped.
|
||||
|
||||
### CI secrets
|
||||
### ArtifactHub Sync
|
||||
|
||||
| Secret | Where | Purpose |
|
||||
|---|---|---|
|
||||
| `REGISTRY_TOKEN` | Gitea | Personal access token with `package:write` scope for Docker image push |
|
||||
| `GH_PAT` | Gitea | GitHub personal access token for creating GitHub releases |
|
||||
ArtifactHub pulls plugin metadata from GitHub every **30 minutes**. After creating a release:
|
||||
|
||||
The Gitea release uses the built-in `github.token`. The `archive-checksum` in `artifacthub-pkg.yml` is updated automatically by the release workflow.
|
||||
- Wait 30 minutes for sync
|
||||
- Check [ArtifactHub package page](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin)
|
||||
- New version should appear in Headlamp plugin catalog
|
||||
|
||||
For complete release process and version numbering guidelines, see **[CONTRIBUTING.md#release-process](CONTRIBUTING.md#release-process)**.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
||||
|
||||
- Development workflow
|
||||
- Branching strategy (feature branches required for code changes)
|
||||
- Commit message conventions (Conventional Commits)
|
||||
- PR process and review checklist
|
||||
- Code style guidelines
|
||||
- Testing requirements
|
||||
|
||||
## Links
|
||||
|
||||
- [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin)
|
||||
- [GitHub (mirror)](https://github.com/cpfarhood/headlamp-polaris-plugin)
|
||||
- [Gitea (source of truth)](https://git.farh.net/farhoodliquor/headlamp-polaris-plugin)
|
||||
- [Headlamp](https://headlamp.dev/)
|
||||
- [Fairwinds Polaris](https://polaris.docs.fairwinds.com/)
|
||||
- **[GitHub Repository](https://github.com/privilegedescalation/headlamp-polaris-plugin)** - Source code, issues, releases
|
||||
- **[Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin)** - Plugin catalog listing
|
||||
- **[Headlamp](https://headlamp.dev/)** - Kubernetes web UI
|
||||
- **[Fairwinds Polaris](https://polaris.docs.fairwinds.com/)** - Kubernetes best practices audit tool
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
[Apache-2.0 License](LICENSE) - see LICENSE file for details.
|
||||
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
# Security Policy
|
||||
|
||||
## Overview
|
||||
|
||||
The Headlamp Polaris Plugin is a read-only visualization tool that displays Fairwinds Polaris audit results within the Headlamp UI. Security considerations primarily revolve around Kubernetes RBAC, network policies, and data access controls.
|
||||
|
||||
## Security Model
|
||||
|
||||
### Read-Only Operation
|
||||
|
||||
The plugin performs **only read operations** via the Kubernetes API server's service proxy mechanism:
|
||||
|
||||
- **No write operations**: The plugin never creates, updates, or deletes Kubernetes resources
|
||||
- **No CRD installation**: No custom resource definitions or cluster-level modifications
|
||||
- **No secrets**: The plugin does not read or store Kubernetes secrets
|
||||
- **No PII**: Polaris audit data contains resource metadata but no personally identifiable information
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
User Browser
|
||||
↓ (HTTPS)
|
||||
Headlamp Pod
|
||||
↓ (in-cluster service account or user token)
|
||||
Kubernetes API Server
|
||||
↓ (service proxy: /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/)
|
||||
Polaris Dashboard Service
|
||||
↓ (returns audit JSON)
|
||||
Plugin Frontend (React)
|
||||
```
|
||||
|
||||
All communication uses Kubernetes authentication and authorization mechanisms. The plugin never stores credentials or bypasses RBAC.
|
||||
|
||||
## RBAC Requirements
|
||||
|
||||
### Minimal Permissions
|
||||
|
||||
The plugin requires only one permission:
|
||||
|
||||
| Verb | API Group | Resource | Resource Name | Namespace |
|
||||
|------|-----------|----------|---------------|-----------|
|
||||
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
|
||||
|
||||
**Example minimal Role:**
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
```
|
||||
|
||||
### RoleBinding Options
|
||||
|
||||
**Option 1: Service Account (Recommended)**
|
||||
|
||||
Bind to the Headlamp service account for all users:
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
**Option 2: OIDC Groups**
|
||||
|
||||
Bind to user groups for OIDC authentication:
|
||||
|
||||
```yaml
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: "developers"
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
**Option 3: Specific Users**
|
||||
|
||||
Bind to individual users:
|
||||
|
||||
```yaml
|
||||
subjects:
|
||||
- kind: User
|
||||
name: "jane@example.com"
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### ⚠️ Security Best Practices
|
||||
|
||||
1. **Principle of Least Privilege**: Grant only `services/proxy` access, not broader `services` permissions
|
||||
2. **Namespace Scoping**: Use a namespaced `Role`, not a `ClusterRole`, to limit access to the `polaris` namespace only
|
||||
3. **Resource Name Restriction**: Always specify `resourceNames: ["polaris-dashboard"]` to prevent proxy access to other services
|
||||
4. **Audit Logging**: Enable Kubernetes audit logging to track all service proxy requests
|
||||
5. **Network Policies**: Restrict network access to the Polaris dashboard service (see Network Security below)
|
||||
|
||||
## Network Security
|
||||
|
||||
### Network Policies
|
||||
|
||||
If your cluster uses NetworkPolicies, ensure the Headlamp pod (or more specifically, the Kubernetes API server performing the proxy hop) can reach the Polaris dashboard service.
|
||||
|
||||
**Example NetworkPolicy for Polaris namespace:**
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-api-server-to-polaris
|
||||
namespace: polaris
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: polaris
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow from API server (adjust based on your cluster setup)
|
||||
- from:
|
||||
- namespaceSelector: {} # API server typically runs in kube-system or no namespace label
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080 # Polaris dashboard default port
|
||||
```
|
||||
|
||||
**Note**: The Kubernetes API server performs the service proxy hop, so network policies should allow traffic from the API server to Polaris, not directly from Headlamp to Polaris.
|
||||
|
||||
### TLS/HTTPS
|
||||
|
||||
- **External Access**: Always access Headlamp over HTTPS, especially when using OIDC authentication
|
||||
- **Internal Communication**: Communication between Headlamp and the Kubernetes API server uses the service account token over the cluster's internal network
|
||||
- **Service Proxy**: The API server → Polaris dashboard communication happens over HTTP within the cluster (ClusterIP service)
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
### Service Account (Default)
|
||||
|
||||
Headlamp runs with a dedicated service account (`headlamp` in `kube-system`). All users share the same permissions defined by this service account's RBAC bindings.
|
||||
|
||||
**Security Considerations:**
|
||||
- All users have identical access to the plugin
|
||||
- Suitable for trusted internal environments
|
||||
- Simpler RBAC management
|
||||
|
||||
### OIDC Token Authentication
|
||||
|
||||
Headlamp can be configured for OIDC authentication, where each user provides their own bearer token. RBAC is enforced per-user.
|
||||
|
||||
**Security Considerations:**
|
||||
- Fine-grained access control per user
|
||||
- Users without the `polaris-proxy-reader` role will see 403 errors
|
||||
- Requires OIDC provider integration
|
||||
- Suitable for multi-tenant or compliance-focused environments
|
||||
|
||||
**Configuration Example:**
|
||||
|
||||
```yaml
|
||||
config:
|
||||
oidc:
|
||||
clientID: "headlamp"
|
||||
clientSecret: "secret"
|
||||
issuerURL: "https://authentik.example.com/application/o/headlamp/"
|
||||
scopes: "openid profile email groups"
|
||||
```
|
||||
|
||||
When OIDC is enabled, each user's token is used for API requests, including service proxy calls.
|
||||
|
||||
## Vulnerability Reporting
|
||||
|
||||
### Supported Versions
|
||||
|
||||
We apply security updates to the latest release only. Please ensure you are running the most recent version.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| latest | :white_check_mark: |
|
||||
| < latest| :x: |
|
||||
|
||||
### Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability in this plugin, please report it via:
|
||||
|
||||
1. **GitHub Security Advisories**: [Report a vulnerability](https://github.com/privilegedescalation/headlamp-polaris-plugin/security/advisories/new)
|
||||
2. **Email**: Create a GitHub issue and mark it as "security" if advisories are not available
|
||||
|
||||
**Please do not:**
|
||||
- Open public GitHub issues for security vulnerabilities
|
||||
- Disclose vulnerabilities publicly before a fix is available
|
||||
|
||||
**Response Timeline:**
|
||||
- **Acknowledgment**: Within 48 hours
|
||||
- **Initial Assessment**: Within 1 week
|
||||
- **Fix Timeline**: Depends on severity (critical: 1-2 weeks, high: 2-4 weeks, medium/low: next release cycle)
|
||||
|
||||
## Dependency Security
|
||||
|
||||
### Dependency Scanning
|
||||
|
||||
The project uses:
|
||||
- **npm audit**: Runs automatically during `npm install`
|
||||
- **Dependabot**: GitHub Dependabot monitors dependencies and creates PRs for updates
|
||||
- **GitHub Actions**: CI workflow runs `npm audit` on every commit
|
||||
|
||||
### Updating Dependencies
|
||||
|
||||
Security patches are applied as follows:
|
||||
|
||||
1. **Critical vulnerabilities**: Emergency patch release within 48 hours
|
||||
2. **High severity**: Patched in next minor release (typically within 1-2 weeks)
|
||||
3. **Medium/Low severity**: Included in regular release cycle
|
||||
|
||||
### Headlamp Plugin API
|
||||
|
||||
This plugin depends on `@kinvolk/headlamp-plugin` as a peer dependency. Security updates to Headlamp itself should be applied by upgrading your Headlamp installation.
|
||||
|
||||
**Minimum supported Headlamp version**: v0.26.0
|
||||
|
||||
## Deployment Security
|
||||
|
||||
### Production Checklist
|
||||
|
||||
Before deploying to production, verify:
|
||||
|
||||
- [ ] **RBAC configured**: `polaris-proxy-reader` Role and RoleBinding exist
|
||||
- [ ] **Network policies**: Allow API server → Polaris dashboard traffic
|
||||
- [ ] **TLS enabled**: Headlamp accessible only via HTTPS
|
||||
- [ ] **OIDC configured** (if using per-user auth): Token-based authentication working
|
||||
- [ ] **Audit logging enabled**: Kubernetes API audit logs capture service proxy requests
|
||||
- [ ] **Plugin version**: Running latest release
|
||||
- [ ] **Dependencies audited**: No critical vulnerabilities in npm dependencies
|
||||
- [ ] **Polaris version**: Polaris dashboard is up-to-date
|
||||
|
||||
### Kubernetes Cluster Security
|
||||
|
||||
The plugin's security posture depends on your cluster's security:
|
||||
|
||||
- **API Server Access**: Ensure API server is not publicly accessible without authentication
|
||||
- **Service Account Tokens**: Use projected volume tokens with short expiration (Kubernetes 1.21+)
|
||||
- **Pod Security Standards**: Apply appropriate pod security policies/standards to the Headlamp namespace
|
||||
- **RBAC Auditing**: Regularly review RoleBindings to ensure least privilege
|
||||
|
||||
## Common Security Scenarios
|
||||
|
||||
### Scenario 1: 403 Forbidden Error
|
||||
|
||||
**Symptom**: Plugin shows "403 Forbidden" when loading data
|
||||
|
||||
**Cause**: User or service account lacks `services/proxy` permission on `polaris-dashboard`
|
||||
|
||||
**Resolution**:
|
||||
1. Verify RoleBinding exists in `polaris` namespace
|
||||
2. Check RoleBinding references correct subject (service account, group, or user)
|
||||
3. Confirm Role includes `resourceNames: ["polaris-dashboard"]`
|
||||
|
||||
**Security Note**: This is expected behavior when RBAC is correctly enforced. Do not grant broader permissions to "fix" 403 errors.
|
||||
|
||||
### Scenario 2: Exposing Polaris Dashboard Externally
|
||||
|
||||
**Question**: Can I expose Polaris dashboard via Ingress instead of using service proxy?
|
||||
|
||||
**Recommendation**: **Avoid exposing Polaris dashboard externally**. The service proxy approach:
|
||||
- Enforces Kubernetes RBAC on every request
|
||||
- Avoids exposing internal services to the internet
|
||||
- Prevents authentication bypass attacks
|
||||
|
||||
If you must expose Polaris externally:
|
||||
- Use OAuth2 proxy or similar authentication layer
|
||||
- Configure NetworkPolicies to restrict access
|
||||
- Enable TLS with valid certificates
|
||||
- Consider IP allowlisting
|
||||
|
||||
### Scenario 3: Multi-Tenant Clusters
|
||||
|
||||
**Question**: How do I restrict plugin access in a multi-tenant cluster?
|
||||
|
||||
**Solution**: Use OIDC authentication with per-user RoleBindings:
|
||||
|
||||
```yaml
|
||||
# Bind only to specific groups or users
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: "team-a"
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
Users not in `team-a` will receive 403 errors when accessing the plugin, preventing unauthorized access to Polaris audit data.
|
||||
|
||||
## Compliance Considerations
|
||||
|
||||
### Data Residency
|
||||
|
||||
All data remains within your Kubernetes cluster. The plugin does not:
|
||||
- Send data to external services
|
||||
- Store data in browser localStorage (except refresh interval preference)
|
||||
- Use third-party analytics or tracking
|
||||
|
||||
### Audit Trail
|
||||
|
||||
All service proxy requests are logged in Kubernetes API audit logs (if enabled):
|
||||
|
||||
```json
|
||||
{
|
||||
"verb": "get",
|
||||
"requestURI": "/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json",
|
||||
"user": {
|
||||
"username": "system:serviceaccount:kube-system:headlamp",
|
||||
"groups": ["system:serviceaccounts", "system:authenticated"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GDPR/Privacy
|
||||
|
||||
The plugin processes only technical metadata (resource names, namespaces, check results). No personal data is collected, stored, or transmitted.
|
||||
|
||||
## Security Updates and Notifications
|
||||
|
||||
### Notification Channels
|
||||
|
||||
Subscribe to security updates via:
|
||||
|
||||
1. **GitHub Watch**: Click "Watch" → "Custom" → "Security alerts"
|
||||
2. **GitHub Releases**: Monitor [releases page](https://github.com/privilegedescalation/headlamp-polaris-plugin/releases)
|
||||
3. **ArtifactHub**: Follow package at [ArtifactHub](https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin)
|
||||
|
||||
### Security Patch Process
|
||||
|
||||
When a security vulnerability is identified:
|
||||
|
||||
1. **Private Fix**: Develop fix in private fork
|
||||
2. **Security Advisory**: Publish GitHub Security Advisory
|
||||
3. **Release**: Create new version with fix
|
||||
4. **Notification**: Update advisory with fix version
|
||||
5. **Disclosure**: Public disclosure after fix is available
|
||||
|
||||
## Contact
|
||||
|
||||
- **Security Issues**: [GitHub Security Advisories](https://github.com/privilegedescalation/headlamp-polaris-plugin/security/advisories)
|
||||
- **General Questions**: [GitHub Discussions](https://github.com/privilegedescalation/headlamp-polaris-plugin/discussions)
|
||||
- **Bug Reports**: [GitHub Issues](https://github.com/privilegedescalation/headlamp-polaris-plugin/issues)
|
||||
|
||||
## License
|
||||
|
||||
This plugin is provided under the Apache-2.0 License. See [LICENSE](LICENSE) for details.
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
version: 0.2.0-dev.5
|
||||
version: "0.5.2"
|
||||
name: headlamp-polaris-plugin
|
||||
displayName: Polaris
|
||||
createdAt: "2026-02-05T19:00:00Z"
|
||||
@@ -9,8 +9,8 @@ description: >-
|
||||
read-only via the Kubernetes service proxy to the Polaris dashboard.
|
||||
Requires a Role granting `get` on `services/proxy` for the
|
||||
`polaris-dashboard` service in the `polaris` namespace.
|
||||
license: MIT
|
||||
homeURL: "https://github.com/cpfarhood/headlamp-polaris-plugin"
|
||||
license: Apache-2.0
|
||||
homeURL: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
||||
category: security
|
||||
keywords:
|
||||
- polaris
|
||||
@@ -21,14 +21,14 @@ keywords:
|
||||
- kubernetes
|
||||
links:
|
||||
- name: Source
|
||||
url: "https://github.com/cpfarhood/headlamp-polaris-plugin"
|
||||
url: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
||||
- name: Polaris
|
||||
url: "https://polaris.docs.fairwinds.com/"
|
||||
maintainers:
|
||||
- name: cpfarhood
|
||||
- name: privilegedescalation
|
||||
email: "chris@farhood.org"
|
||||
annotations:
|
||||
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.2.0-dev.5/headlamp-polaris-plugin-0.2.0-dev.5.tar.gz"
|
||||
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.5.2/polaris-0.5.2.tar.gz"
|
||||
headlamp/plugin/version-compat: ">=0.26"
|
||||
headlamp/plugin/archive-checksum: sha256:cb8d03f52022590fce5565b4f08a3fb99d0e264f3ff6a1c99ab59bf48b33ef79
|
||||
headlamp/plugin/archive-checksum: sha256:61e7e92a26061299956bf133f8d7ec9d86d68b452a5bc4063f4a886fb8f055fd
|
||||
headlamp/plugin/distro-compat: in-cluster
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
repositoryID: fc3397f6-a75a-4950-ab50-da75c08a8089
|
||||
owners:
|
||||
- name: cpfarhood
|
||||
- name: privilegedescalation
|
||||
email: "chris@farhood.org"
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Headlamp plugin that surfaces Fairwinds Polaris audit results inside the Headlamp UI. Queries the Polaris dashboard API via the Kubernetes service proxy (`/api/v1/namespaces/polaris/services/polaris-dashboard/proxy/results.json`). Target Headlamp ≥ v0.26.
|
||||
|
||||
## Build & Development Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build the plugin (standard Headlamp plugin build)
|
||||
npx @kinvolk/headlamp-plugin build
|
||||
|
||||
# Start development mode with hot reload
|
||||
npx @kinvolk/headlamp-plugin start
|
||||
|
||||
# Type-check without emitting
|
||||
npx tsc --noEmit
|
||||
|
||||
# Lint
|
||||
npx eslint src/
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.tsx # Entry point: registers sidebar entries + routes
|
||||
├── api/
|
||||
│ ├── polaris.ts # Types (AuditData schema), usePolarisData hook, countResults utilities, refresh settings
|
||||
│ ├── polaris.test.ts # Unit tests for utility functions (vitest)
|
||||
│ └── PolarisDataContext.tsx # React context provider for shared data fetch
|
||||
└── components/
|
||||
├── DashboardView.tsx # Overview page (score, check summary with skipped count, cluster info)
|
||||
├── NamespacesListView.tsx # Namespace list with scores and links to detail views
|
||||
├── NamespaceDetailView.tsx # Per-namespace drill-down with resource table
|
||||
└── PolarisSettings.tsx # Plugin settings (refresh interval selector)
|
||||
```
|
||||
|
||||
Top-level sidebar section at `/polaris` with sub-routes for namespaces list (`/polaris/namespaces`) and per-namespace views (`/polaris/ns/:namespace`). Data is fetched via `ApiProxy.request` to the Polaris dashboard service proxy and refreshed on a user-configurable interval (stored in localStorage under `polaris-plugin-refresh-interval`, default 5 minutes). Score is computed from result counts (pass/total). Skipped checks are always displayed in summaries.
|
||||
|
||||
**Sidebar limitation**: Headlamp's sidebar only supports 2-level nesting (parent → children). The `Collapse` component is driven by route-based selection, not click-to-toggle, so 3-level hierarchies don't expand properly. Namespace navigation is handled via the in-content table on the Namespaces page instead.
|
||||
|
||||
## Security / RBAC Requirements
|
||||
|
||||
The plugin reaches Polaris through the Kubernetes API server's service proxy sub-resource (`/api/v1/namespaces/polaris/services/polaris-dashboard/proxy/...`). The Headlamp service account (or the user's bearer token when Headlamp runs in token-auth mode) must be granted:
|
||||
|
||||
| Verb | API Group | Resource | Resource Name | Namespace |
|
||||
|------|-----------|----------|---------------|-----------|
|
||||
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
|
||||
|
||||
Minimal RBAC example:
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp # adjust to match your Headlamp SA
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
Additional considerations:
|
||||
|
||||
- **NetworkPolicy**: If the `polaris` namespace enforces network policies, allow ingress from the Headlamp pod (or the API server, since it performs the proxy hop) to `polaris-dashboard` on port 80.
|
||||
- **Polaris dashboard listen address**: The Polaris Helm chart exposes the dashboard on a ClusterIP service (`polaris-dashboard:80`). If the chart is installed with `dashboard.enabled: false`, the service will not be created, resulting in a 404 error for proxy requests.
|
||||
- **No write operations**: The plugin only performs `GET` requests through the proxy. No `create`, `update`, or `delete` verbs are required. Do not grant broader service proxy access than `get`.
|
||||
- **Token-auth mode**: When Headlamp is configured for user-supplied tokens (rather than a fixed service account), each user's own RBAC bindings must include the role above. A 403 from the plugin means the logged-in user lacks the binding.
|
||||
- **Audit logging**: Kubernetes API audit logs will record every proxied request as a `get` on `services/proxy` in the `polaris` namespace. Set an appropriate audit policy level if request volume from the auto-refresh interval is a concern.
|
||||
|
||||
## Key Constraints
|
||||
|
||||
- **Data source**: Polaris dashboard API via K8s service proxy. Requires Polaris deployed in the `polaris` namespace with a `polaris-dashboard` service. No CRDs, no cluster write operations.
|
||||
- **UI components**: Use only Headlamp-provided components (`@kinvolk/headlamp-plugin/lib/CommonComponents`). Do not import raw MUI packages. No custom theming.
|
||||
- **Error handling**: Must handle 403 (RBAC denied), 404 (Polaris not installed), malformed JSON, and loading states with distinct visual states.
|
||||
- **TypeScript strictness**: No `any`, no implicit `unknown` casting, no dead code, no unused imports.
|
||||
- **Packaging**: `@kinvolk/headlamp-plugin` is a peer dependency. Do not bundle React or MUI.
|
||||
|
||||
## MCP Servers
|
||||
|
||||
The project has MCP server integrations configured in `.mcp.json`:
|
||||
- **Gitea** (git.farh.net): Source control via `gitea-mcp-server`
|
||||
- **Kubernetes** (local): Cluster access via `kubernetes-mcp-server`
|
||||
- **Flux** (local): Flux Operator access via `flux-operator-mcp`
|
||||
@@ -0,0 +1,58 @@
|
||||
# Headlamp Plugin Loading Issue - Root Cause and Fix
|
||||
|
||||
## Problem
|
||||
Headlamp v0.39.0 was not loading plugins installed via the plugin manager. Plugins appeared in Settings → Plugins but:
|
||||
- No sidebar entries appeared
|
||||
- No plugin settings were available
|
||||
- Plugin JavaScript was not being executed in the browser
|
||||
|
||||
## Root Cause
|
||||
When `config.watchPlugins: true` (the default), Headlamp treats catalog-managed plugins in `/headlamp/plugins/` as "development directory" plugins. This causes:
|
||||
- Backend serves plugin metadata correctly
|
||||
- Backend logs show "Treating catalog-installed plugin in development directory as user plugin"
|
||||
- **Frontend does NOT execute the plugin JavaScript**
|
||||
- Plugin registrations (`registerSidebarEntry`, `registerRoute`, etc.) never happen
|
||||
|
||||
## Solution
|
||||
Set `config.watchPlugins: false` in the Headlamp HelmRelease values:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
values:
|
||||
config:
|
||||
watchPlugins: false
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
configContent: |
|
||||
plugins:
|
||||
- name: polaris
|
||||
source: https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin
|
||||
# ... other plugins
|
||||
```
|
||||
|
||||
## Why This Works
|
||||
With `watchPlugins: false`:
|
||||
- Headlamp no longer treats catalog-managed plugins as "development" plugins
|
||||
- Frontend properly loads and executes plugin JavaScript on startup
|
||||
- Plugin registrations happen correctly
|
||||
- All plugin features (sidebar, routes, settings, etc.) work as expected
|
||||
|
||||
## Testing
|
||||
After applying this fix:
|
||||
1. Verify plugins are installed: `kubectl logs -n kube-system <headlamp-pod> -c headlamp-plugin`
|
||||
2. Verify watchPlugins is false: `kubectl logs -n kube-system <headlamp-pod> -c headlamp | grep "Watch Plugins"`
|
||||
3. Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+F5) to clear cached JavaScript
|
||||
4. Verify plugin sidebar entries appear
|
||||
5. Verify plugin functionality works
|
||||
|
||||
## Additional Notes
|
||||
- This appears to be a bug/limitation in Headlamp v0.39.0
|
||||
- The `watchPlugins` feature is intended for development scenarios where plugins are being actively modified
|
||||
- For production deployments with catalog-managed plugins, `watchPlugins: false` is the correct configuration
|
||||
- Once plugins are loaded, subsequent restarts or updates work correctly as long as `watchPlugins` remains false
|
||||
|
||||
## References
|
||||
- Headlamp Helm Chart: https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp
|
||||
- Plugin Manager: https://github.com/headlamp-k8s/headlamp/tree/main/plugins/headlamp-plugin
|
||||
- Issue discovered: 2026-02-11
|
||||
- Fix applied: 2026-02-12
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
# Custom Headlamp values for static plugin installation
|
||||
# This disables the plugin manager and uses an init container instead
|
||||
|
||||
# Disable the plugin manager sidecar
|
||||
pluginsManager:
|
||||
enabled: false
|
||||
|
||||
# Use an init container to install plugins to /headlamp/static-plugins
|
||||
initContainers:
|
||||
- name: install-plugins
|
||||
image: node:lts-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Installing plugins to /headlamp/static-plugins..."
|
||||
|
||||
# Create plugins directory
|
||||
mkdir -p /headlamp/static-plugins
|
||||
|
||||
# Set up npm cache
|
||||
export NPM_CONFIG_CACHE=/tmp/npm-cache
|
||||
export NPM_CONFIG_USERCONFIG=/tmp/npm-userconfig
|
||||
mkdir -p /tmp/npm-cache /tmp/npm-userconfig
|
||||
|
||||
# Install polaris plugin
|
||||
echo "Installing polaris plugin..."
|
||||
cd /headlamp/static-plugins
|
||||
npm pack headlamp-polaris-plugin@0.3.0
|
||||
tar -xzf headlamp-polaris-plugin-0.3.0.tgz
|
||||
mv package headlamp-polaris-plugin
|
||||
rm headlamp-polaris-plugin-0.3.0.tgz
|
||||
|
||||
# Install other plugins
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_flux \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-trivy/headlamp_trivy \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_cert-manager \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_ai_assistant \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
echo "All plugins installed successfully"
|
||||
ls -la /headlamp/static-plugins
|
||||
securityContext:
|
||||
runAsUser: 100
|
||||
runAsGroup: 101
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
volumeMounts:
|
||||
- name: static-plugins
|
||||
mountPath: /headlamp/static-plugins
|
||||
|
||||
# Configure headlamp to use static plugins
|
||||
config:
|
||||
pluginsDir: /headlamp/static-plugins
|
||||
|
||||
# Add volume for static plugins
|
||||
volumes:
|
||||
- name: static-plugins
|
||||
emptyDir: {}
|
||||
|
||||
# Add volume mount to main container
|
||||
volumeMounts:
|
||||
- name: static-plugins
|
||||
mountPath: /headlamp/static-plugins
|
||||
readOnly: true
|
||||
@@ -0,0 +1,593 @@
|
||||
# Architecture
|
||||
|
||||
This document describes the architecture, design decisions, and data flow of the Headlamp Polaris Plugin.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [System Architecture](#system-architecture)
|
||||
- [Data Flow](#data-flow)
|
||||
- [Component Hierarchy](#component-hierarchy)
|
||||
- [State Management](#state-management)
|
||||
- [Design Decisions](#design-decisions)
|
||||
- [Integration Points](#integration-points)
|
||||
- [Known Limitations](#known-limitations)
|
||||
|
||||
## Overview
|
||||
|
||||
The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds Polaris audit results within the Headlamp UI. It fetches data from the Polaris dashboard API via the Kubernetes service proxy and presents it in a hierarchical navigation structure.
|
||||
|
||||
**Key Characteristics:**
|
||||
|
||||
- **Read-only:** No write operations to cluster or Polaris
|
||||
- **Service proxy based:** Uses K8s API server proxy to reach Polaris
|
||||
- **React Context for state:** Shared data fetch across components
|
||||
- **Headlamp plugin API:** Integrates via official plugin system
|
||||
- **Type-safe:** Full TypeScript with strict mode
|
||||
|
||||
## System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Headlamp UI (React) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ App Bar │ │ Sidebar │ │ Routes │ │
|
||||
│ │ (Badge) │ │ (Navigation)│ │ (Views) │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────┼──────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────▼────────┐ │
|
||||
│ │ Plugin Registry │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────▼──────────────┐ │
|
||||
│ │ Polaris Plugin │ │
|
||||
│ ├────────────────────────────┤ │
|
||||
│ │ • registerSidebarEntry │ │
|
||||
│ │ • registerRoute │ │
|
||||
│ │ • registerAppBarAction │ │
|
||||
│ │ • registerPluginSettings │ │
|
||||
│ │ • registerDetailsViewSection│ │
|
||||
│ └─────────────┬──────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────▼──────────────┐ │
|
||||
│ │ PolarisDataContext │ │
|
||||
│ │ (React Context Provider) │ │
|
||||
│ └─────────────┬──────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────┼──────────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ ┌────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
|
||||
│ │Dashboard │ │ Namespaces │ │ Namespace │ │
|
||||
│ │View │ │ ListView │ │ Detail │ │
|
||||
│ └──────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ ApiProxy │
|
||||
│ (Headlamp) │
|
||||
└───────┬────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ Kubernetes │
|
||||
│ API Server │
|
||||
└───────┬────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ Service Proxy │
|
||||
│ /api/v1/ns/ │
|
||||
│ polaris/svcs/ │
|
||||
│ polaris- │
|
||||
│ dashboard/ │
|
||||
│ proxy/ │
|
||||
└───────┬────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ Polaris │
|
||||
│ Dashboard │
|
||||
│ (ClusterIP) │
|
||||
└───────┬────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ results.json │
|
||||
│ (AuditData) │
|
||||
└────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### 1. Initial Load
|
||||
|
||||
```
|
||||
User loads Headlamp
|
||||
↓
|
||||
Headlamp loads plugins
|
||||
↓
|
||||
Plugin registers routes, sidebar, app bar actions
|
||||
↓
|
||||
User navigates to /polaris
|
||||
↓
|
||||
DashboardView mounts
|
||||
↓
|
||||
PolarisDataContext.Provider wraps component
|
||||
↓
|
||||
usePolarisDataContext() hook triggers fetch
|
||||
↓
|
||||
ApiProxy.request() → K8s API → Service Proxy → Polaris
|
||||
↓
|
||||
AuditData returned and cached in Context
|
||||
↓
|
||||
Components receive data and render
|
||||
```
|
||||
|
||||
### 2. Data Refresh
|
||||
|
||||
```
|
||||
User clicks "Refresh" button or auto-refresh interval elapses
|
||||
↓
|
||||
refresh() function called in Context
|
||||
↓
|
||||
setRefreshKey() increments (forces re-fetch)
|
||||
↓
|
||||
useEffect dependency triggers new fetch
|
||||
↓
|
||||
ApiProxy.request() → Polaris Dashboard
|
||||
↓
|
||||
Context state updated with new data
|
||||
↓
|
||||
All consuming components re-render automatically
|
||||
```
|
||||
|
||||
### 3. Navigation Flow
|
||||
|
||||
```
|
||||
User clicks "Polaris" in sidebar
|
||||
↓
|
||||
Route: /c/main/polaris (DashboardView)
|
||||
↓
|
||||
Display cluster score, check distribution
|
||||
↓
|
||||
User clicks "Namespaces" submenu
|
||||
↓
|
||||
Route: /c/main/polaris/namespaces (NamespacesListView)
|
||||
↓
|
||||
Display table of namespaces with scores
|
||||
↓
|
||||
User clicks namespace button in table
|
||||
↓
|
||||
Drawer opens, URL hash updates (#namespace-name)
|
||||
↓
|
||||
NamespaceDetailView renders in drawer
|
||||
↓
|
||||
Display namespace score + resource table
|
||||
```
|
||||
|
||||
## Component Hierarchy
|
||||
|
||||
### Plugin Entry Point
|
||||
|
||||
**`src/index.tsx`**
|
||||
|
||||
- Registers sidebar entries (Polaris → Overview, Namespaces)
|
||||
- Registers routes (`/polaris`, `/polaris/namespaces`)
|
||||
- Registers app bar action (score badge)
|
||||
- Registers plugin settings page
|
||||
- Registers details view section (inline audit)
|
||||
|
||||
### Data Layer
|
||||
|
||||
**`src/api/PolarisDataContext.tsx`**
|
||||
|
||||
- React Context Provider for shared data
|
||||
- Fetches AuditData from Polaris dashboard
|
||||
- Handles auto-refresh based on user settings
|
||||
- Provides `{ data, loading, error, refresh }` to consumers
|
||||
|
||||
**`src/api/polaris.ts`**
|
||||
|
||||
- TypeScript types for AuditData schema
|
||||
- Utility functions: `countResults()`, `computeScore()`
|
||||
- Settings management: `getRefreshInterval()`, `setRefreshInterval()`
|
||||
- Constants: `DASHBOARD_URL_DEFAULT`, `INTERVAL_OPTIONS`
|
||||
|
||||
**`src/api/checkMapping.ts`**
|
||||
|
||||
- Maps Polaris check IDs to human-readable names
|
||||
- Used for display in UI (e.g., "hostIPCSet" → "Host IPC")
|
||||
|
||||
**`src/api/topIssues.ts`**
|
||||
|
||||
- Aggregates failing checks across cluster
|
||||
- Groups by check ID and severity
|
||||
- Used for top issues dashboard
|
||||
|
||||
### View Components
|
||||
|
||||
**`src/components/DashboardView.tsx`**
|
||||
|
||||
- **Route:** `/polaris`
|
||||
- **Purpose:** Cluster-wide overview
|
||||
- **Features:**
|
||||
- Cluster score (percentage)
|
||||
- Check distribution (pass/warning/danger/skipped)
|
||||
- Cluster info (Polaris version, last audit time)
|
||||
- Refresh button
|
||||
- **Data:** Uses `usePolarisDataContext()`
|
||||
|
||||
**`src/components/NamespacesListView.tsx`**
|
||||
|
||||
- **Route:** `/polaris/namespaces`
|
||||
- **Purpose:** List all namespaces with scores
|
||||
- **Features:**
|
||||
- Table with namespace, score, pass/warning/danger counts
|
||||
- Clickable namespace buttons (opens drawer)
|
||||
- Sorted by score (lowest first)
|
||||
- **Data:** Uses `usePolarisDataContext()`, aggregates by namespace
|
||||
|
||||
**`src/components/NamespaceDetailView.tsx`**
|
||||
|
||||
- **Route:** Drawer on `/polaris/namespaces#<namespace>`
|
||||
- **Purpose:** Namespace-level drill-down
|
||||
- **Features:**
|
||||
- Namespace score
|
||||
- Resource table (kind, name, score, counts)
|
||||
- URL hash navigation
|
||||
- Keyboard shortcuts (Escape to close)
|
||||
- **Data:** Filters `usePolarisDataContext()` by namespace
|
||||
|
||||
### UI Components
|
||||
|
||||
**`src/components/AppBarScoreBadge.tsx`**
|
||||
|
||||
- **Location:** Headlamp app bar (top-right)
|
||||
- **Purpose:** Quick cluster score visibility
|
||||
- **Features:**
|
||||
- Color-coded badge (green ≥80%, orange ≥50%, red <50%)
|
||||
- Clickable (navigates to `/polaris`)
|
||||
- Shield emoji icon
|
||||
- **Data:** Uses `usePolarisDataContext()`
|
||||
|
||||
**`src/components/PolarisSettings.tsx`**
|
||||
|
||||
- **Location:** Settings → Plugins → Polaris
|
||||
- **Purpose:** Plugin configuration
|
||||
- **Features:**
|
||||
- Refresh interval selector (1 min to 30 min)
|
||||
- Dashboard URL input (custom Polaris instances)
|
||||
- Connection test button
|
||||
- **Data:** localStorage for persistence
|
||||
|
||||
**`src/components/InlineAuditSection.tsx`**
|
||||
|
||||
- **Location:** Resource detail pages (Deployment, StatefulSet, etc.)
|
||||
- **Purpose:** Show Polaris audit inline
|
||||
- **Features:**
|
||||
- Pass/warning/danger counts
|
||||
- Check details with messages
|
||||
- Severity badges
|
||||
- **Data:** Uses `usePolarisDataContext()`, filters by resource
|
||||
|
||||
**`src/components/ExemptionManager.tsx`**
|
||||
|
||||
- **Location:** (Planned feature, UI exists but not fully integrated)
|
||||
- **Purpose:** Manage Polaris exemptions via annotations
|
||||
- **Features:**
|
||||
- View current exemptions
|
||||
- Add exemptions for failing checks
|
||||
- Remove exemptions
|
||||
|
||||
## State Management
|
||||
|
||||
### Why React Context?
|
||||
|
||||
**Decision:** Use React Context instead of Redux/Zustand
|
||||
|
||||
**Rationale:**
|
||||
|
||||
1. **Simple state:** Single AuditData object shared across views
|
||||
2. **Read-only:** No complex mutations or transactions
|
||||
3. **Headlamp constraints:** Plugin cannot add dependencies (Redux not bundled)
|
||||
4. **Performance:** Data changes infrequently (refresh interval 1-30 min)
|
||||
|
||||
### Context Structure
|
||||
|
||||
```typescript
|
||||
interface PolarisDataContextValue {
|
||||
data: AuditData | null; // Audit results or null if loading/error
|
||||
loading: boolean; // True during initial fetch
|
||||
error: string | null; // Error message if fetch failed
|
||||
refresh: () => void; // Manual refresh function
|
||||
}
|
||||
```
|
||||
|
||||
### Data Fetching Strategy
|
||||
|
||||
1. **Initial fetch:** On first mount of any component using the context
|
||||
2. **Auto-refresh:** Based on user setting (default 5 minutes)
|
||||
3. **Manual refresh:** Via refresh button in UI
|
||||
4. **Caching:** Data persists in context until refresh (no per-route refetch)
|
||||
|
||||
### localStorage Usage
|
||||
|
||||
Settings persisted in localStorage:
|
||||
|
||||
- **`polaris-plugin-refresh-interval`**: Number (seconds), default 300
|
||||
- **`polaris-plugin-dashboard-url`**: String, default service proxy path
|
||||
|
||||
No sensitive data stored in localStorage.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### 1. Service Proxy vs. Direct Access
|
||||
|
||||
**Decision:** Use Kubernetes service proxy, not direct ClusterIP access
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Headlamp already has K8s API credentials (service account or user token)
|
||||
- Service proxy leverages existing RBAC (no new credentials needed)
|
||||
- Works with Headlamp's token auth and OIDC
|
||||
- Simpler deployment (no additional network policies for plugin)
|
||||
|
||||
**Trade-off:**
|
||||
|
||||
- Requires `get` permission on `services/proxy` resource
|
||||
- Path is longer: `/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
|
||||
|
||||
### 2. Two-Level Sidebar Nesting
|
||||
|
||||
**Decision:** Sidebar has "Polaris" → "Overview" and "Namespaces" (2 levels max)
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Headlamp sidebar supports 2-level nesting maximum
|
||||
- Deeper nesting (e.g., Polaris → Namespaces → <each namespace>) doesn't work
|
||||
- Sidebar Collapse component is route-based, not click-to-toggle
|
||||
|
||||
**Alternative Considered:**
|
||||
|
||||
- Dynamic sidebar with namespace entries → rejected (Headlamp limitation)
|
||||
|
||||
**Current Solution:**
|
||||
|
||||
- Use table in NamespacesListView with clickable namespace buttons
|
||||
- Namespace detail opens in drawer (not new route)
|
||||
|
||||
### 3. Drawer Navigation Instead of Routes
|
||||
|
||||
**Decision:** Namespace detail uses drawer, not dedicated route
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Better UX (drawer overlays table, no navigation loss)
|
||||
- URL hash preserves navigation state (`#namespace-name`)
|
||||
- Keyboard shortcuts (Escape to close)
|
||||
- Sidebar doesn't support 3-level nesting for per-namespace routes
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- URL: `/polaris/namespaces#kube-system`
|
||||
- Drawer controlled by hash presence
|
||||
- `useEffect` watches hash changes
|
||||
|
||||
### 4. No MUI Direct Imports
|
||||
|
||||
**Decision:** Never import from `@mui/material` or `@mui/icons-material`
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Headlamp plugin environment doesn't provide full MUI library
|
||||
- Importing MUI causes `createSvgIcon undefined` error
|
||||
- Plugins must use Headlamp CommonComponents only
|
||||
|
||||
**Alternative:**
|
||||
|
||||
- Use standard HTML elements with inline styles
|
||||
- Use theme-aware CSS variables (`--mui-palette-*`)
|
||||
|
||||
### 5. TypeScript Strict Mode
|
||||
|
||||
**Decision:** Enable all TypeScript strict checks
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Catch errors at compile time
|
||||
- Better IDE support and autocomplete
|
||||
- Enforces type safety (no `any`, no implicit unknowns)
|
||||
|
||||
**Impact:**
|
||||
|
||||
- More verbose code (explicit types required)
|
||||
- Better maintainability and refactorability
|
||||
|
||||
### 6. Auto-Refresh Default: 5 Minutes
|
||||
|
||||
**Decision:** Default refresh interval is 5 minutes (configurable)
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Polaris audits typically run every 10-30 minutes
|
||||
- Balance between data freshness and API load
|
||||
- User can configure from 1 minute to 30 minutes
|
||||
|
||||
**Considered:**
|
||||
|
||||
- WebSocket/SSE for real-time updates → rejected (Polaris dashboard doesn't support)
|
||||
- Shorter default → rejected (unnecessary API calls)
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Headlamp Plugin API
|
||||
|
||||
**Version:** ≥ v0.13.0
|
||||
|
||||
**Registration Functions Used:**
|
||||
|
||||
```typescript
|
||||
// Sidebar navigation
|
||||
registerSidebarEntry({ parent, name, label, url, icon });
|
||||
|
||||
// Routes
|
||||
registerRoute({ path, sidebar, name, exact, component });
|
||||
|
||||
// App bar actions
|
||||
registerAppBarAction(component);
|
||||
|
||||
// Plugin settings
|
||||
registerPluginSettings(name, component, displaySaveButton);
|
||||
|
||||
// Resource detail sections
|
||||
registerDetailsViewSection(component);
|
||||
```
|
||||
|
||||
**Key Changes in v0.13.0:**
|
||||
|
||||
- `registerDetailsViewSection` now takes 1 argument (component), not 2 (name, component)
|
||||
- `registerAppBarAction` now takes 1 argument (component), not 2 (name, component)
|
||||
|
||||
### Headlamp CommonComponents
|
||||
|
||||
**Used Components:**
|
||||
|
||||
- `SectionBox` - Card-like container with title
|
||||
- `SectionHeader` - Page header with title
|
||||
- `StatusLabel` - Color-coded status badges
|
||||
- `NameValueTable` - Key-value table layout
|
||||
- `SimpleTable` - Data table with sorting
|
||||
- `Drawer` - Right-side overlay panel
|
||||
- `Loader` - Loading spinner
|
||||
|
||||
**Router:**
|
||||
|
||||
- `Router.createRouteURL()` - Generate plugin route URLs
|
||||
- React Router's `useHistory()`, `useParams()`, `useLocation()`
|
||||
|
||||
### Kubernetes API (via ApiProxy)
|
||||
|
||||
**Used for:**
|
||||
|
||||
- Fetching Polaris results: `ApiProxy.request(dashboardUrl + 'results.json')`
|
||||
- No direct K8s API calls (all data from Polaris dashboard)
|
||||
|
||||
**RBAC Required:**
|
||||
|
||||
- `get` on `services/proxy` for `polaris-dashboard` in `polaris` namespace
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### 1. Sidebar Nesting Depth
|
||||
|
||||
**Limitation:** Headlamp sidebar supports only 2 levels
|
||||
|
||||
**Impact:** Cannot have dynamic per-namespace sidebar entries
|
||||
|
||||
**Workaround:** Use table with drawer navigation
|
||||
|
||||
### 2. Skipped Checks Visibility
|
||||
|
||||
**Limitation:** Skipped checks (severity "ignore") counted but details not shown in dashboard
|
||||
|
||||
**Reason:** Polaris API groups skipped checks but doesn't provide per-check details
|
||||
|
||||
**Impact:** Users see skipped count but can't drill down to specific skipped checks
|
||||
|
||||
**Documented:** README, tooltip on skipped count
|
||||
|
||||
### 3. No Write Operations
|
||||
|
||||
**Limitation:** Plugin cannot modify Polaris configuration or exemptions
|
||||
|
||||
**Reason:** Read-only by design (service proxy only has `get` permission)
|
||||
|
||||
**Impact:** Exemption manager UI exists but requires manual annotation edits
|
||||
|
||||
**Future:** Could add PATCH permission to enable exemption annotations via UI
|
||||
|
||||
### 4. No Real-Time Updates
|
||||
|
||||
**Limitation:** Data refreshes on interval (1-30 minutes), not real-time
|
||||
|
||||
**Reason:** Polaris dashboard doesn't support WebSocket/SSE
|
||||
|
||||
**Impact:** Users may see stale data between refreshes
|
||||
|
||||
**Workaround:** Manual refresh button, configurable interval
|
||||
|
||||
### 5. MUI Import Restrictions
|
||||
|
||||
**Limitation:** Cannot import MUI components directly
|
||||
|
||||
**Reason:** Headlamp plugin environment doesn't provide full MUI bundle
|
||||
|
||||
**Impact:** Must use Headlamp CommonComponents or HTML elements
|
||||
|
||||
**Documented:** CLAUDE.md, CONTRIBUTING.md
|
||||
|
||||
### 6. Single Cluster Support
|
||||
|
||||
**Limitation:** Plugin shows data for current cluster only
|
||||
|
||||
**Reason:** Headlamp's multi-cluster support is route-based (`/c/<cluster>/...`)
|
||||
|
||||
**Impact:** Users must switch clusters in Headlamp to see different cluster's Polaris data
|
||||
|
||||
**Future:** Could enhance to aggregate multi-cluster if Headlamp API supports it
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Bundle Size
|
||||
|
||||
- **Current:** ~27 KB minified (gzip: ~7.6 KB)
|
||||
- **Target:** Keep under 50 KB to ensure fast loading
|
||||
- **Strategy:** No heavy dependencies, tree-shaking enabled
|
||||
|
||||
### Data Fetching
|
||||
|
||||
- **Lazy loading:** Data not fetched until user navigates to plugin
|
||||
- **Caching:** Single fetch shared across all views (React Context)
|
||||
- **Refresh strategy:** User-controlled interval prevents excessive API calls
|
||||
|
||||
### Rendering
|
||||
|
||||
- **React.memo:** Not needed (data changes infrequently)
|
||||
- **Virtual scrolling:** Not needed (namespace/resource lists typically <100 items)
|
||||
- **Component splitting:** Lazy load views if bundle grows significantly
|
||||
|
||||
## Future Architecture Enhancements
|
||||
|
||||
### Potential Improvements
|
||||
|
||||
1. **WebWorker for data processing**
|
||||
|
||||
- Offload `countResults()` aggregation for large clusters
|
||||
- Keep UI responsive during heavy computation
|
||||
|
||||
2. **IndexedDB caching**
|
||||
|
||||
- Cache audit data offline
|
||||
- Show stale data + "refresh available" indicator
|
||||
|
||||
3. **GraphQL/REST API abstraction**
|
||||
|
||||
- Decouple from Polaris dashboard JSON format
|
||||
- Support multiple backend sources
|
||||
|
||||
4. **Plugin-to-plugin communication**
|
||||
|
||||
- Integrate with other Headlamp plugins (e.g., policy enforcement)
|
||||
- Shared state between plugins
|
||||
|
||||
5. **Incremental updates**
|
||||
- Fetch only changed namespaces/resources
|
||||
- Reduce bandwidth and processing
|
||||
|
||||
## References
|
||||
|
||||
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
- [Fairwinds Polaris Documentation](https://polaris.docs.fairwinds.com/)
|
||||
- [React Context API](https://react.dev/reference/react/useContext)
|
||||
- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/)
|
||||
@@ -0,0 +1,672 @@
|
||||
# Deployment Guide
|
||||
|
||||
This document provides comprehensive deployment instructions for the Headlamp Polaris Plugin in production Kubernetes environments.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation Methods](#installation-methods)
|
||||
- [Helm Integration](#helm-integration)
|
||||
- [RBAC Configuration](#rbac-configuration)
|
||||
- [Network Policies](#network-policies)
|
||||
- [Plugin Manager Setup](#plugin-manager-setup)
|
||||
- [Production Checklist](#production-checklist)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Components
|
||||
|
||||
1. **Kubernetes Cluster:** v1.19 or later
|
||||
2. **Headlamp:** v0.26 or later (v0.39+ recommended)
|
||||
3. **Polaris:** Deployed and accessible via service
|
||||
4. **RBAC:** Permissions to create Roles and RoleBindings
|
||||
|
||||
### Pre-Deployment Verification
|
||||
|
||||
```bash
|
||||
# Verify Polaris is deployed
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
|
||||
# Verify Polaris dashboard is responding
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||
|
||||
# Verify Headlamp is deployed
|
||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
||||
```
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### Method 1: Headlamp Plugin Manager (Recommended)
|
||||
|
||||
**Best for:** Production deployments, managed updates
|
||||
|
||||
1. **Enable Plugin Manager in Headlamp:**
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
repositories:
|
||||
- https://artifacthub.io/packages/search?kind=4
|
||||
```
|
||||
|
||||
2. **Deploy/Update Headlamp:**
|
||||
|
||||
```bash
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
3. **Install Plugin via UI:**
|
||||
- Navigate to Headlamp → Settings → Plugins
|
||||
- Search for "Polaris"
|
||||
- Click "Install"
|
||||
- Refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
|
||||
|
||||
### Method 2: Sidecar Container (Alternative)
|
||||
|
||||
**Best for:** Controlled plugin versions, air-gapped environments
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
initContainers:
|
||||
- name: install-polaris-plugin
|
||||
image: node:lts-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
npm install -g @kinvolk/headlamp-plugin
|
||||
headlamp-plugin install --config /config/plugin.yml --plugins-dir /plugins
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /plugins
|
||||
- name: plugin-config
|
||||
mountPath: /config
|
||||
|
||||
volumes:
|
||||
- name: plugins
|
||||
emptyDir: {}
|
||||
- name: plugin-config
|
||||
configMap:
|
||||
name: headlamp-plugin-config
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-plugin-config
|
||||
namespace: kube-system
|
||||
data:
|
||||
plugin.yml: |
|
||||
- name: headlamp-polaris-plugin
|
||||
version: 0.3.4
|
||||
url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
### Method 3: Volume Mount (Development)
|
||||
|
||||
**Best for:** Local testing, development
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: '/plugins'
|
||||
|
||||
volumes:
|
||||
- name: plugins
|
||||
hostPath:
|
||||
path: /path/to/plugins
|
||||
type: Directory
|
||||
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /plugins
|
||||
readOnly: true
|
||||
```
|
||||
|
||||
Then manually place `headlamp-polaris-plugin/` in the host path.
|
||||
|
||||
## Helm Integration
|
||||
|
||||
### Complete Helm Values Example
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
repository: ghcr.io/headlamp-k8s/headlamp
|
||||
tag: v0.39.0
|
||||
|
||||
config:
|
||||
baseURL: ''
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
repositories:
|
||||
- https://artifacthub.io/packages/search?kind=4
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
hosts:
|
||||
- host: headlamp.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: headlamp-tls
|
||||
hosts:
|
||||
- headlamp.example.com
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: headlamp
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
# OIDC Authentication (optional)
|
||||
env:
|
||||
- name: HEADLAMP_CONFIG_OIDC_CLIENT_ID
|
||||
value: 'headlamp'
|
||||
- name: HEADLAMP_CONFIG_OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: headlamp-oidc
|
||||
key: client-secret
|
||||
- name: HEADLAMP_CONFIG_OIDC_ISSUER_URL
|
||||
value: 'https://auth.example.com/realms/kubernetes'
|
||||
- name: HEADLAMP_CONFIG_OIDC_SCOPES
|
||||
value: 'openid,profile,email,groups'
|
||||
```
|
||||
|
||||
### FluxCD HelmRelease Example
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
spec:
|
||||
interval: 30m
|
||||
chart:
|
||||
spec:
|
||||
chart: headlamp
|
||||
version: 0.26.x
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: headlamp
|
||||
namespace: flux-system
|
||||
interval: 12h
|
||||
|
||||
values:
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
repositories:
|
||||
- https://artifacthub.io/packages/search?kind=4
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: headlamp.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
```
|
||||
|
||||
## RBAC Configuration
|
||||
|
||||
### Minimal Role for Plugin
|
||||
|
||||
The plugin requires **read-only** access to the Polaris dashboard service proxy.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
```
|
||||
|
||||
### RoleBinding Options
|
||||
|
||||
#### Option A: Headlamp Service Account (In-Cluster Mode)
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
#### Option B: User Groups (Token/OIDC Mode)
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: users-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: system:authenticated # All authenticated users
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
#### Option C: Specific Users (Fine-Grained Control)
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: devops-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: User
|
||||
name: alice@example.com
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
- kind: User
|
||||
name: bob@example.com
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
- kind: Group
|
||||
name: devops-team
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### Complete RBAC Manifest
|
||||
|
||||
```yaml
|
||||
---
|
||||
# Role: Read-only access to Polaris service proxy
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp-polaris-plugin
|
||||
app.kubernetes.io/component: rbac
|
||||
rules:
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
|
||||
---
|
||||
# RoleBinding: Grant Headlamp service account access
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp-polaris-plugin
|
||||
app.kubernetes.io/component: rbac
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
Apply with:
|
||||
|
||||
```bash
|
||||
kubectl apply -f polaris-plugin-rbac.yaml
|
||||
```
|
||||
|
||||
## Network Policies
|
||||
|
||||
### Required Network Access
|
||||
|
||||
The plugin requires network connectivity:
|
||||
|
||||
- **Headlamp pod** → **Kubernetes API server** (service proxy)
|
||||
- **Kubernetes API server** → **Polaris dashboard service** (port 80)
|
||||
|
||||
### Network Policy Example
|
||||
|
||||
If your `polaris` namespace has strict NetworkPolicies:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-headlamp-to-polaris
|
||||
namespace: polaris
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: polaris
|
||||
app.kubernetes.io/component: dashboard
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow from API server (service proxy)
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: kube-apiserver
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
```
|
||||
|
||||
**Note:** The API server performs the proxy hop, not the Headlamp pod directly.
|
||||
|
||||
## Plugin Manager Setup
|
||||
|
||||
### Plugin Manager Verification
|
||||
|
||||
```bash
|
||||
# Check plugin is installed
|
||||
kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/
|
||||
|
||||
# Expected output:
|
||||
# drwxr-xr-x headlamp-polaris-plugin/
|
||||
```
|
||||
|
||||
## Production Checklist
|
||||
|
||||
### Pre-Deployment
|
||||
|
||||
- [ ] Polaris deployed and running
|
||||
- [ ] Polaris dashboard service exists (`polaris-dashboard` in `polaris` namespace)
|
||||
- [ ] RBAC Role and RoleBinding created
|
||||
- [ ] Headlamp v0.26+ deployed
|
||||
|
||||
### Deployment
|
||||
|
||||
- [ ] Plugin installed via plugin manager or sidecar
|
||||
- [ ] Headlamp pods restarted (if config changed)
|
||||
- [ ] Browser cache cleared (Cmd+Shift+R / Ctrl+Shift+R)
|
||||
|
||||
### Post-Deployment Verification
|
||||
|
||||
```bash
|
||||
# 1. Verify Polaris is accessible via service proxy
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||
|
||||
# Expected: "1.0" or similar
|
||||
|
||||
# 2. Verify RBAC is correct
|
||||
kubectl auth can-i get services/proxy --as=system:serviceaccount:kube-system:headlamp -n polaris --resource-name=polaris-dashboard
|
||||
|
||||
# Expected: yes
|
||||
|
||||
# 3. Check Headlamp logs
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
|
||||
# Expected: No errors related to plugin loading
|
||||
|
||||
# 4. Verify plugin files exist
|
||||
kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected: dist/, package.json present
|
||||
```
|
||||
|
||||
### UI Verification
|
||||
|
||||
- [ ] Navigate to Headlamp → Settings → Plugins
|
||||
- [ ] Plugin "headlamp-polaris-plugin" listed
|
||||
- [ ] Sidebar shows "Polaris" entry
|
||||
- [ ] Click "Polaris" → Overview page loads
|
||||
- [ ] Cluster score displays correctly
|
||||
- [ ] Namespaces page shows table
|
||||
- [ ] App bar shows Polaris score badge
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin Not Appearing in Sidebar
|
||||
|
||||
**Symptom:** Plugin listed in Settings → Plugins but no sidebar entry
|
||||
|
||||
**Causes:**
|
||||
|
||||
1. Browser cache not cleared
|
||||
2. Plugin files not properly installed
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Restart Headlamp
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
|
||||
# Clear browser cache
|
||||
# Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
|
||||
```
|
||||
|
||||
### 403 Forbidden Error
|
||||
|
||||
**Symptom:** Error loading Polaris data, 403 in console
|
||||
|
||||
**Cause:** RBAC missing or incorrect
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Verify RBAC exists
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission
|
||||
kubectl auth can-i get services/proxy --as=system:serviceaccount:kube-system:headlamp -n polaris --resource-name=polaris-dashboard
|
||||
|
||||
# If "no", create RBAC (see RBAC Configuration section)
|
||||
```
|
||||
|
||||
### 404 Not Found Error
|
||||
|
||||
**Symptom:** Error loading Polaris data, 404 in console
|
||||
|
||||
**Causes:**
|
||||
|
||||
1. Polaris not deployed
|
||||
2. Polaris service name wrong
|
||||
3. Polaris namespace wrong
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Check Polaris deployment
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
|
||||
# If service doesn't exist, install Polaris:
|
||||
helm install polaris fairwinds-stable/polaris \
|
||||
--namespace polaris \
|
||||
--create-namespace \
|
||||
--set dashboard.enabled=true
|
||||
```
|
||||
|
||||
### Custom Dashboard URL Not Working
|
||||
|
||||
**Symptom:** Error when using custom Polaris URL in settings
|
||||
|
||||
**Causes:**
|
||||
|
||||
1. URL format incorrect
|
||||
2. CORS not configured on external Polaris
|
||||
3. Network policy blocking external access
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Test URL manually
|
||||
curl -v https://my-polaris.example.com/results.json
|
||||
|
||||
# For external Polaris, check CORS headers
|
||||
# Must allow Headlamp origin
|
||||
```
|
||||
|
||||
### Plugin Shows Old Version
|
||||
|
||||
**Symptom:** Plugin version in Settings doesn't match expected
|
||||
|
||||
**Cause:** Plugin manager hasn't synced from ArtifactHub
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Wait 30 minutes (ArtifactHub sync interval)
|
||||
# Or manually refresh plugin list in Headlamp UI
|
||||
|
||||
# Force Headlamp restart
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
```
|
||||
|
||||
### Network Policy Blocking Access
|
||||
|
||||
**Symptom:** Timeout or connection errors despite correct RBAC
|
||||
|
||||
**Cause:** NetworkPolicy in `polaris` namespace blocking API server
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Check NetworkPolicies
|
||||
kubectl -n polaris get networkpolicy
|
||||
|
||||
# Test connectivity from API server (if possible)
|
||||
# Add NetworkPolicy to allow API server → Polaris dashboard (see Network Policies section)
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Least Privilege
|
||||
|
||||
- Grant only `get` on `services/proxy`, not broader permissions
|
||||
- Use `resourceNames` to restrict to specific service (`polaris-dashboard`)
|
||||
- Scope to `polaris` namespace only (Role, not ClusterRole)
|
||||
|
||||
### Audit Logging
|
||||
|
||||
Kubernetes audit logs will record:
|
||||
|
||||
- User/service account accessing service proxy
|
||||
- Timestamp and response code
|
||||
|
||||
Configure audit policy if needed:
|
||||
|
||||
```yaml
|
||||
apiVersion: audit.k8s.io/v1
|
||||
kind: Policy
|
||||
rules:
|
||||
- level: Metadata
|
||||
verbs: ['get']
|
||||
resources:
|
||||
- group: ''
|
||||
resources: ['services/proxy']
|
||||
namespaces: ['polaris']
|
||||
```
|
||||
|
||||
### Data Sensitivity
|
||||
|
||||
Polaris audit data may contain:
|
||||
|
||||
- Resource names and namespaces
|
||||
- Configuration details
|
||||
- Potential security vulnerabilities
|
||||
|
||||
**Recommendation:** Restrict plugin access to authorized users only (not `system:authenticated` group unless appropriate).
|
||||
|
||||
## Upgrading
|
||||
|
||||
### Plugin Upgrade via Plugin Manager
|
||||
|
||||
1. Navigate to Settings → Plugins
|
||||
2. Find "headlamp-polaris-plugin"
|
||||
3. Click "Update" if new version available
|
||||
4. Refresh browser (Cmd+Shift+R / Ctrl+Shift+R)
|
||||
|
||||
### Sidecar Method Upgrade
|
||||
|
||||
1. Update ConfigMap with new version/URL
|
||||
2. Restart Headlamp deployment
|
||||
3. Verify new version in Settings → Plugins
|
||||
|
||||
```bash
|
||||
kubectl -n kube-system edit configmap headlamp-plugin-config
|
||||
# Update version and URL
|
||||
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Headlamp Deployment](https://headlamp.dev/docs/latest/installation/)
|
||||
- [Headlamp Helm Chart](https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp)
|
||||
- [Polaris Installation](https://polaris.docs.fairwinds.com/infrastructure-as-code/)
|
||||
- [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
|
||||
- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/#manually-constructing-apiserver-proxy-urls)
|
||||
@@ -0,0 +1,606 @@
|
||||
# Documentation Standardization Plan
|
||||
|
||||
**Date**: 2026-02-12
|
||||
**Repositories**: headlamp-polaris-plugin, headlamp-sealed-secrets-plugin
|
||||
**Goal**: Establish consistent documentation standards across Headlamp plugin projects
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This plan standardizes documentation structure, formatting, and content across two Headlamp plugins to create a consistent, professional documentation experience. The standardization adopts the best practices from both repositories while maintaining each plugin's unique technical content.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Polaris Plugin (v0.3.5)
|
||||
|
||||
**Structure**: Topic-focused with monolithic files
|
||||
**Strengths**:
|
||||
|
||||
- Comprehensive CONTRIBUTING.md with branching strategy and commit conventions
|
||||
- Complete CHANGELOG.md (35 versions documented)
|
||||
- Dedicated SECURITY.md with vulnerability reporting
|
||||
- JSDoc comments on all API exports
|
||||
- CI/CD badges in README
|
||||
- Well-organized TROUBLESHOOTING.md with common issues
|
||||
|
||||
**Gaps**:
|
||||
|
||||
- No user journey-based organization
|
||||
- No Architecture Decision Records
|
||||
- Limited quick-start tutorials
|
||||
- No FAQ section
|
||||
- Deployment guide is monolithic (needs breakdown)
|
||||
|
||||
### Sealed Secrets Plugin
|
||||
|
||||
**Structure**: User journey-based with granular topic files
|
||||
**Strengths**:
|
||||
|
||||
- Excellent user journey organization (Getting Started → User Guide → Tutorials)
|
||||
- Architecture Decision Records (5 ADRs)
|
||||
- Quick diagnosis flowchart in troubleshooting
|
||||
- Multi-platform installation guides
|
||||
- Auto-generated API reference
|
||||
- Visual hierarchy with strategic emoji use
|
||||
|
||||
**Gaps**:
|
||||
|
||||
- No dedicated CONTRIBUTING.md (content in README)
|
||||
- No SECURITY.md for vulnerability reporting
|
||||
- Incomplete tutorial placeholders
|
||||
- No comprehensive CHANGELOG
|
||||
- Missing E2E testing documentation
|
||||
|
||||
## Standardization Principles
|
||||
|
||||
### 1. File Structure Standard
|
||||
|
||||
**Root-Level Files** (Common to Both):
|
||||
|
||||
```
|
||||
README.md # Main entry point with badges, quick links
|
||||
CHANGELOG.md # Keep a Changelog format, semantic versioning
|
||||
CONTRIBUTING.md # Development workflow, branching, PR process
|
||||
SECURITY.md # Security model, vulnerability reporting, RBAC
|
||||
LICENSE # Apache-2.0 License
|
||||
package.json # Plugin metadata
|
||||
```
|
||||
|
||||
**Documentation Directory** (Organized by User Journey):
|
||||
|
||||
```
|
||||
docs/
|
||||
├── README.md # Documentation hub with quick links
|
||||
├── getting-started/
|
||||
│ ├── installation.md
|
||||
│ ├── prerequisites.md
|
||||
│ └── quick-start.md
|
||||
├── user-guide/
|
||||
│ ├── features.md
|
||||
│ ├── configuration.md
|
||||
│ └── rbac-permissions.md
|
||||
├── tutorials/
|
||||
│ └── (plugin-specific)
|
||||
├── troubleshooting/
|
||||
│ ├── README.md (quick diagnosis)
|
||||
│ └── common-issues.md
|
||||
├── architecture/
|
||||
│ ├── overview.md
|
||||
│ ├── data-flow.md
|
||||
│ ├── design-decisions.md
|
||||
│ └── adr/ (Architecture Decision Records)
|
||||
├── development/
|
||||
│ ├── workflow.md
|
||||
│ ├── testing.md
|
||||
│ ├── code-style.md
|
||||
│ └── release-process.md
|
||||
└── deployment/
|
||||
├── kubernetes.md
|
||||
├── helm.md
|
||||
└── production.md
|
||||
```
|
||||
|
||||
### 2. README.md Standard
|
||||
|
||||
**Required Sections** (Order Matters):
|
||||
|
||||
1. Title + Badges (ArtifactHub, CI, E2E, License)
|
||||
2. Quick navigation links (📚 Documentation | 🚀 Installation | 🔒 Security | 🛠️ Development)
|
||||
3. **What It Does** (features with visual hierarchy)
|
||||
4. **Prerequisites** (table format)
|
||||
5. **Installing** (4 options: Plugin Manager, Sidecar, Manual, Source)
|
||||
6. **RBAC / Security Setup** (minimal manifests)
|
||||
7. **Documentation** (table linking to docs/)
|
||||
8. **Troubleshooting** (quick reference table + link to full guide)
|
||||
9. **Development** (quick start commands + link to CONTRIBUTING.md)
|
||||
10. **Known Limitations** (if applicable)
|
||||
11. **Releasing** (brief + link to development/release-process.md)
|
||||
12. **Contributing** (link to CONTRIBUTING.md)
|
||||
13. **Links** (GitHub, ArtifactHub, Headlamp, related tools)
|
||||
14. **License** (MIT with link)
|
||||
15. Footer ("Made with ❤️ for the Kubernetes community")
|
||||
|
||||
**Formatting Standards**:
|
||||
|
||||
- Use emojis strategically for visual scanning (not excessive)
|
||||
- Quick navigation at top
|
||||
- Tables for structured data (prerequisites, troubleshooting quick ref)
|
||||
- Code blocks with language hints
|
||||
- Keep main README under 400 lines (details go in docs/)
|
||||
|
||||
### 3. CHANGELOG.md Standard
|
||||
|
||||
**Format**: Keep a Changelog (https://keepachangelog.com/)
|
||||
|
||||
**Structure**:
|
||||
|
||||
```markdown
|
||||
# Changelog
|
||||
|
||||
All notable changes will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [X.Y.Z] - YYYY-MM-DD
|
||||
|
||||
### Added
|
||||
|
||||
- New features
|
||||
|
||||
### Changed
|
||||
|
||||
- Changes to existing functionality
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Soon-to-be removed features
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed features
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bug fixes
|
||||
|
||||
### Security
|
||||
|
||||
- Security fixes
|
||||
|
||||
[Unreleased]: https://github.com/user/repo/compare/vX.Y.Z...HEAD
|
||||
[X.Y.Z]: https://github.com/user/repo/releases/tag/vX.Y.Z
|
||||
```
|
||||
|
||||
**Standards**:
|
||||
|
||||
- One entry per version, newest first
|
||||
- Date in ISO 8601 format (YYYY-MM-DD)
|
||||
- Link to GitHub release
|
||||
- Group changes by type (Added, Changed, Fixed, Security)
|
||||
- Keep descriptions concise (1-2 lines per item)
|
||||
|
||||
### 4. CONTRIBUTING.md Standard
|
||||
|
||||
**Required Sections**:
|
||||
|
||||
1. Code of Conduct (brief, respectful)
|
||||
2. Getting Started (prerequisites, setup)
|
||||
3. Development Workflow (feature development, testing)
|
||||
4. Branching Strategy (feat/, fix/, docs/, chore/)
|
||||
5. Commit Message Guidelines (Conventional Commits)
|
||||
6. Pull Request Process (before creating, creating, review)
|
||||
7. Code Style (TypeScript, React, linting, formatting)
|
||||
8. Testing Requirements (unit, E2E, coverage goals)
|
||||
9. Documentation (when to update docs)
|
||||
10. Release Process (version numbering, creating releases)
|
||||
|
||||
**Formatting**:
|
||||
|
||||
- Use tables for branch naming conventions
|
||||
- Code blocks for commit message examples
|
||||
- Checklists for PR requirements
|
||||
- Links to detailed guides in docs/development/
|
||||
|
||||
### 5. SECURITY.md Standard
|
||||
|
||||
**Required Sections**:
|
||||
|
||||
1. Overview (security model, read-only vs. write operations)
|
||||
2. Data Flow Diagram (how data moves through system)
|
||||
3. RBAC Requirements (minimal permissions table)
|
||||
4. Network Security (NetworkPolicies, TLS)
|
||||
5. Authentication Methods (service account, OIDC)
|
||||
6. Vulnerability Reporting (supported versions table, how to report)
|
||||
7. Dependency Security (scanning, update process)
|
||||
8. Deployment Security (production checklist)
|
||||
9. Common Security Scenarios (FAQs with solutions)
|
||||
10. Compliance Considerations (audit trail, GDPR/privacy)
|
||||
|
||||
**Formatting**:
|
||||
|
||||
- Tables for permissions and supported versions
|
||||
- YAML examples for RBAC manifests
|
||||
- Bash commands for security verification
|
||||
- Clear "Do NOT" warnings for unsafe practices
|
||||
|
||||
### 6. Documentation Hub (docs/README.md) Standard
|
||||
|
||||
**Purpose**: Central navigation for all documentation
|
||||
|
||||
**Structure**:
|
||||
|
||||
```markdown
|
||||
# Documentation
|
||||
|
||||
Central hub for [Plugin Name] documentation.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- 🚀 [Quick Start](getting-started/quick-start.md)
|
||||
- 📖 [User Guide](user-guide/README.md)
|
||||
- 🔧 [Troubleshooting](troubleshooting/README.md)
|
||||
- 🏗️ [Architecture](architecture/overview.md)
|
||||
- 💻 [Development](development/workflow.md)
|
||||
|
||||
## Getting Started
|
||||
|
||||
Description + links to installation, prerequisites, quick-start
|
||||
|
||||
## User Guide
|
||||
|
||||
Description + links to features, configuration, RBAC
|
||||
|
||||
## Tutorials
|
||||
|
||||
Description + links to plugin-specific tutorials
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Description + link to quick diagnosis + common issues
|
||||
|
||||
## Architecture
|
||||
|
||||
Description + links to overview, data flow, ADRs
|
||||
|
||||
## Development
|
||||
|
||||
Description + links to workflow, testing, code style, release
|
||||
|
||||
## Deployment
|
||||
|
||||
Description + links to Kubernetes, Helm, production
|
||||
|
||||
## API Reference
|
||||
|
||||
Link to JSDoc or generated API docs
|
||||
```
|
||||
|
||||
**Formatting**:
|
||||
|
||||
- Emojis for visual scanning
|
||||
- Brief descriptions (1-2 sentences) for each section
|
||||
- Organized by user journey (beginner → advanced)
|
||||
|
||||
### 7. Architecture Decision Records (ADR) Standard
|
||||
|
||||
**When to Create ADRs**:
|
||||
|
||||
- Significant architectural choices
|
||||
- Technology selection (libraries, patterns)
|
||||
- Security or performance trade-offs
|
||||
- Design patterns that impact maintainability
|
||||
|
||||
**Template** (Based on Michael Nygard's ADR):
|
||||
|
||||
```markdown
|
||||
# ADR-NNN: Title
|
||||
|
||||
**Status**: [Proposed | Accepted | Deprecated | Superseded by ADR-XXX]
|
||||
**Date**: YYYY-MM-DD
|
||||
**Deciders**: [List key decision makers]
|
||||
|
||||
## Context
|
||||
|
||||
What is the issue that we're seeing that is motivating this decision or change?
|
||||
|
||||
## Decision
|
||||
|
||||
What is the change that we're proposing and/or doing?
|
||||
|
||||
## Consequences
|
||||
|
||||
What becomes easier or more difficult to do because of this change?
|
||||
|
||||
### Positive
|
||||
|
||||
- ...
|
||||
|
||||
### Negative
|
||||
|
||||
- ...
|
||||
|
||||
### Neutral
|
||||
|
||||
- ...
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Option 1: Name
|
||||
|
||||
**Pros**: ...
|
||||
**Cons**: ...
|
||||
**Decision**: Not chosen because...
|
||||
|
||||
## References
|
||||
|
||||
- [Link to related issues, docs, discussions]
|
||||
```
|
||||
|
||||
**Numbering**: ADR-001, ADR-002, etc. (zero-padded 3 digits)
|
||||
|
||||
**Index File** (architecture/adr/README.md):
|
||||
|
||||
```markdown
|
||||
# Architecture Decision Records
|
||||
|
||||
| ADR | Title | Status | Date |
|
||||
| ------------------- | ----- | -------- | ---------- |
|
||||
| [001](001-title.md) | Title | Accepted | 2026-01-01 |
|
||||
```
|
||||
|
||||
### 8. Troubleshooting Standard
|
||||
|
||||
**Structure**:
|
||||
|
||||
**troubleshooting/README.md** (Quick Diagnosis):
|
||||
|
||||
```markdown
|
||||
# Troubleshooting
|
||||
|
||||
Quick diagnosis guide for common issues.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Symptom | Likely Cause | Quick Fix |
|
||||
| ------- | ------------ | --------- |
|
||||
| ... | ... | ... |
|
||||
|
||||
## Detailed Guides
|
||||
|
||||
- [Common Errors](common-errors.md)
|
||||
- [RBAC Issues](rbac-issues.md)
|
||||
- [Network Problems](network-problems.md)
|
||||
```
|
||||
|
||||
**Individual Issue Files**:
|
||||
|
||||
- Symptom-based organization
|
||||
- Step-by-step resolution
|
||||
- Bash commands for verification
|
||||
- Links to related docs
|
||||
- "Still Having Issues?" section with bug report link
|
||||
|
||||
### 9. Testing Documentation Standard
|
||||
|
||||
**docs/development/testing.md**:
|
||||
|
||||
**Required Sections**:
|
||||
|
||||
1. Overview (testing philosophy, types of tests)
|
||||
2. Unit Testing (framework, running tests, writing tests, examples)
|
||||
3. E2E Testing (framework, prerequisites, running tests, examples)
|
||||
4. CI/CD Integration (workflows, required secrets)
|
||||
5. Test Coverage (goals, generating reports)
|
||||
6. Best Practices (unit, E2E, general)
|
||||
7. Debugging (common issues, useful commands)
|
||||
|
||||
**Formatting**:
|
||||
|
||||
- Tables for test types and coverage goals
|
||||
- Code blocks for examples
|
||||
- Bash commands for running tests
|
||||
- Links to test files in repository
|
||||
|
||||
### 10. Visual Formatting Standards
|
||||
|
||||
**Emoji Usage** (Strategic, Not Excessive):
|
||||
|
||||
- 📚 Documentation
|
||||
- 🚀 Installation/Quick Start
|
||||
- 🔒 Security
|
||||
- 🛠️ Development
|
||||
- ✅ Success/Completed
|
||||
- ❌ Error/Failed
|
||||
- ⚠️ Warning/Important
|
||||
- 🔧 Troubleshooting/Fix
|
||||
- 🏗️ Architecture
|
||||
- 💻 Code/Technical
|
||||
|
||||
**Code Block Languages**:
|
||||
|
||||
- `bash` for shell commands
|
||||
- `yaml` for Kubernetes/Helm manifests
|
||||
- `typescript` for TypeScript code
|
||||
- `json` for JSON config
|
||||
- `diff` for showing changes
|
||||
|
||||
**Tables**:
|
||||
|
||||
- Use for structured data (prerequisites, commands, permissions, troubleshooting)
|
||||
- Keep columns concise
|
||||
- Left-align text columns, center-align status columns
|
||||
|
||||
**Links**:
|
||||
|
||||
- Use descriptive text, not "click here"
|
||||
- Relative paths within repo (`docs/architecture/overview.md`)
|
||||
- Absolute URLs for external resources
|
||||
- Link to specific sections with anchors where helpful
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Polaris Plugin Enhancements
|
||||
|
||||
**Priority 1: Granular Documentation Structure**
|
||||
|
||||
- [ ] Create docs/README.md (documentation hub)
|
||||
- [ ] Break down DEPLOYMENT.md:
|
||||
- [ ] docs/getting-started/installation.md
|
||||
- [ ] docs/getting-started/prerequisites.md
|
||||
- [ ] docs/deployment/kubernetes.md
|
||||
- [ ] docs/deployment/helm.md
|
||||
- [ ] docs/deployment/production.md
|
||||
- [ ] Break down ARCHITECTURE.md:
|
||||
- [ ] docs/architecture/overview.md
|
||||
- [ ] docs/architecture/data-flow.md
|
||||
- [ ] docs/architecture/design-decisions.md
|
||||
- [ ] Move TROUBLESHOOTING.md → docs/troubleshooting/
|
||||
- [ ] Create troubleshooting/README.md (quick diagnosis)
|
||||
- [ ] Break into common-issues.md, rbac-issues.md, etc.
|
||||
- [ ] Move TESTING.md → docs/development/testing.md
|
||||
|
||||
**Priority 2: Add Missing Content**
|
||||
|
||||
- [ ] Create docs/getting-started/quick-start.md (5-minute tutorial)
|
||||
- [ ] Create docs/user-guide/features.md
|
||||
- [ ] Create docs/user-guide/configuration.md
|
||||
- [ ] Create docs/architecture/adr/ directory with ADR template
|
||||
- [ ] Create FAQ section in troubleshooting
|
||||
|
||||
**Priority 3: Content Refinement**
|
||||
|
||||
- [ ] Add multi-platform instructions to installation.md
|
||||
- [ ] Enhance README.md with better visual hierarchy
|
||||
- [ ] Add more code examples to user guide
|
||||
- [ ] Create architecture diagrams (ASCII art or mermaid)
|
||||
|
||||
### Phase 2: Sealed Secrets Plugin Enhancements
|
||||
|
||||
**Priority 1: Root-Level Documentation**
|
||||
|
||||
- [ ] Extract CONTRIBUTING.md from README
|
||||
- [ ] Create SECURITY.md with vulnerability reporting
|
||||
- [ ] Expand CHANGELOG.md to include all versions
|
||||
- [ ] Update README.md to match standardized format
|
||||
|
||||
**Priority 2: Complete Incomplete Files**
|
||||
|
||||
- [ ] Finish placeholder tutorial files
|
||||
- [ ] Add E2E testing guide to docs/development/testing.md
|
||||
- [ ] Expand API reference (ensure generated docs are readable)
|
||||
- [ ] Add FAQ section
|
||||
|
||||
**Priority 3: Content Refinement**
|
||||
|
||||
- [ ] Add CI/CD badges to README
|
||||
- [ ] Ensure consistent emoji usage
|
||||
- [ ] Standardize code block languages
|
||||
- [ ] Add more cross-links between related topics
|
||||
|
||||
### Phase 3: Cross-Repository Standards
|
||||
|
||||
**Documentation Templates**
|
||||
|
||||
- [ ] ADR template in both repos
|
||||
- [ ] Bug report template (GitHub issue template)
|
||||
- [ ] Feature request template
|
||||
- [ ] PR template
|
||||
|
||||
**Shared Patterns**
|
||||
|
||||
- [ ] Consistent branching strategy docs
|
||||
- [ ] Identical commit message conventions
|
||||
- [ ] Same release process documentation
|
||||
- [ ] Unified code style guidelines
|
||||
|
||||
## Success Metrics
|
||||
|
||||
**Completeness**:
|
||||
|
||||
- [ ] All standard files present in both repos
|
||||
- [ ] No broken links in documentation
|
||||
- [ ] All code examples tested and functional
|
||||
|
||||
**Consistency**:
|
||||
|
||||
- [ ] Same file structure in both repos
|
||||
- [ ] Same formatting standards applied
|
||||
- [ ] Same terminology used for common concepts
|
||||
|
||||
**Usability**:
|
||||
|
||||
- [ ] New users can get started in < 5 minutes
|
||||
- [ ] Contributors can find development workflow easily
|
||||
- [ ] Troubleshooting guides resolve 80%+ of common issues
|
||||
|
||||
**Maintainability**:
|
||||
|
||||
- [ ] Documentation updates documented in CHANGELOG
|
||||
- [ ] ADRs created for all major decisions
|
||||
- [ ] Test documentation kept in sync with code
|
||||
|
||||
## Maintenance Guidelines
|
||||
|
||||
**When to Update Documentation**:
|
||||
|
||||
1. **New Feature**: Add to CHANGELOG, update user guide, add tutorial if complex
|
||||
2. **Bug Fix**: Add to CHANGELOG, update troubleshooting if user-facing
|
||||
3. **Architecture Change**: Create ADR, update architecture docs
|
||||
4. **Breaking Change**: Add to CHANGELOG with migration guide, update SECURITY.md if relevant
|
||||
5. **New Dependency**: Document in prerequisites, update installation guide
|
||||
6. **Configuration Change**: Update user guide, add migration notes if needed
|
||||
|
||||
**Documentation Review Checklist**:
|
||||
|
||||
- [ ] Spelling and grammar checked
|
||||
- [ ] Links tested (no 404s)
|
||||
- [ ] Code examples tested
|
||||
- [ ] Screenshots/diagrams up to date (if applicable)
|
||||
- [ ] Cross-references added where relevant
|
||||
- [ ] CHANGELOG updated
|
||||
- [ ] Version numbers current
|
||||
|
||||
**Annual Documentation Audit**:
|
||||
|
||||
- Review all docs for accuracy (especially version numbers, screenshots)
|
||||
- Check for outdated information
|
||||
- Update ADR status if superseded
|
||||
- Archive obsolete tutorials
|
||||
- Refresh getting-started for latest best practices
|
||||
|
||||
## Appendix: File Mapping
|
||||
|
||||
### Polaris Plugin Current → Standard
|
||||
|
||||
| Current | Standard Location |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| README.md | README.md (enhanced) |
|
||||
| CHANGELOG.md | CHANGELOG.md (no change) |
|
||||
| CONTRIBUTING.md | CONTRIBUTING.md (no change) |
|
||||
| SECURITY.md | SECURITY.md (no change) |
|
||||
| docs/ARCHITECTURE.md | docs/architecture/overview.md + data-flow.md + design-decisions.md |
|
||||
| docs/DEPLOYMENT.md | docs/getting-started/installation.md + docs/deployment/kubernetes.md + helm.md + production.md |
|
||||
| docs/TROUBLESHOOTING.md | docs/troubleshooting/README.md + common-issues.md |
|
||||
| docs/TESTING.md | docs/development/testing.md |
|
||||
| — (new) | docs/README.md |
|
||||
| — (new) | docs/getting-started/quick-start.md |
|
||||
| — (new) | docs/user-guide/features.md |
|
||||
| — (new) | docs/architecture/adr/ |
|
||||
|
||||
### Sealed Secrets Plugin Current → Standard
|
||||
|
||||
| Current | Standard Location |
|
||||
| ------------ | ----------------------------------------------- |
|
||||
| README.md | README.md (extract contributing section) |
|
||||
| CHANGELOG.md | CHANGELOG.md (expand) |
|
||||
| — (new) | CONTRIBUTING.md (extract from README) |
|
||||
| — (new) | SECURITY.md (new file) |
|
||||
| docs/\* | docs/\* (mostly keep, enhance incomplete files) |
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Last Updated**: 2026-02-12
|
||||
**Approved By**: [Pending]
|
||||
@@ -0,0 +1,71 @@
|
||||
# Documentation
|
||||
|
||||
Central hub for Headlamp Polaris Plugin documentation.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Quick Start](getting-started/quick-start.md)
|
||||
- [Installation Guide](getting-started/installation.md)
|
||||
- [Troubleshooting](troubleshooting/README.md)
|
||||
- [Architecture](architecture/overview.md)
|
||||
- [Development](development/workflow.md)
|
||||
|
||||
## Getting Started
|
||||
|
||||
New to the Headlamp Polaris Plugin? Start here:
|
||||
|
||||
- **[Prerequisites](getting-started/prerequisites.md)** - System requirements, Headlamp version, Polaris installation
|
||||
- **[Installation](getting-started/installation.md)** - Four installation methods: Plugin Manager, Sidecar, Manual, Source
|
||||
- **[Quick Start](getting-started/quick-start.md)** - Get up and running in 5 minutes
|
||||
|
||||
## User Guide
|
||||
|
||||
Learn how to use the plugin:
|
||||
|
||||
- **[Features](user-guide/features.md)** - Overview dashboard, namespace views, inline audits, exemption management
|
||||
- **[Configuration](user-guide/configuration.md)** - Refresh intervals, dashboard URLs, settings
|
||||
- **[RBAC Permissions](user-guide/rbac-permissions.md)** - Required permissions, service proxy access, token-auth mode
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Having issues? Check here:
|
||||
|
||||
- **[Quick Diagnosis](troubleshooting/README.md)** - Quick reference table for common symptoms
|
||||
- **[Common Issues](troubleshooting/common-issues.md)** - Detailed resolution steps for frequent problems
|
||||
- **[RBAC Issues](troubleshooting/rbac-issues.md)** - Permission debugging, 403 errors, token-auth
|
||||
- **[Network Problems](troubleshooting/network-problems.md)** - NetworkPolicies, connectivity, proxy issues
|
||||
|
||||
## Architecture
|
||||
|
||||
Understand how the plugin works:
|
||||
|
||||
- **[Overview](architecture/overview.md)** - High-level architecture, component hierarchy
|
||||
- **[Data Flow](architecture/data-flow.md)** - How data moves from Polaris to the UI
|
||||
- **[Design Decisions](architecture/design-decisions.md)** - Key architectural choices and rationale
|
||||
- **[ADRs](architecture/adr/README.md)** - Architecture Decision Records
|
||||
|
||||
## Development
|
||||
|
||||
Contributing to the plugin:
|
||||
|
||||
- **[Development Workflow](development/workflow.md)** - Setup, building, hot reload
|
||||
- **[Testing](development/testing.md)** - Unit tests, E2E tests, CI/CD
|
||||
- **[Code Style](development/code-style.md)** - TypeScript, React, linting, formatting
|
||||
- **[Release Process](development/release-process.md)** - Versioning, changelog, GitHub Actions
|
||||
|
||||
## Deployment
|
||||
|
||||
Production deployment guides:
|
||||
|
||||
- **[Kubernetes](deployment/kubernetes.md)** - Direct Kubernetes manifest deployment
|
||||
- **[Helm](deployment/helm.md)** - Helm chart configuration, values
|
||||
- **[Production Checklist](deployment/production.md)** - RBAC, NetworkPolicies, security, monitoring
|
||||
|
||||
## API Reference
|
||||
|
||||
- **[polaris.ts](../src/api/polaris.ts)** - JSDoc-annotated TypeScript API (data fetching, types, utilities)
|
||||
- **[PolarisDataContext.tsx](../src/api/PolarisDataContext.tsx)** - React Context provider for shared data
|
||||
|
||||
---
|
||||
|
||||
**Need help?** Open an issue on [GitHub](https://github.com/privilegedescalation/headlamp-polaris-plugin/issues) or check [CONTRIBUTING.md](../CONTRIBUTING.md) for development guidelines.
|
||||
+703
@@ -0,0 +1,703 @@
|
||||
# Testing Guide
|
||||
|
||||
Comprehensive guide to testing the Headlamp Polaris Plugin, covering unit tests, E2E tests, and CI/CD integration.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Unit Testing](#unit-testing)
|
||||
- [E2E Testing](#e2e-testing)
|
||||
- [CI/CD Integration](#cicd-integration)
|
||||
- [Test Coverage](#test-coverage)
|
||||
- [Best Practices](#best-practices)
|
||||
- [Debugging](#debugging)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Headlamp Polaris Plugin uses a multi-layered testing approach:
|
||||
|
||||
| Test Type | Framework | Purpose | Location |
|
||||
| ----------------- | ---------- | ------------------------------------------------------- | ----------------------- |
|
||||
| **Unit Tests** | Vitest | Test individual functions and components in isolation | `src/**/*.test.ts(x)` |
|
||||
| **E2E Tests** | Playwright | Test complete user flows against live Headlamp instance | `e2e/*.spec.ts` |
|
||||
| **Type Checking** | TypeScript | Ensure type safety across codebase | `tsc --noEmit` |
|
||||
| **Linting** | ESLint | Enforce code style and catch common errors | `eslint src/` |
|
||||
| **Formatting** | Prettier | Maintain consistent code formatting | `prettier --check src/` |
|
||||
|
||||
### Test Philosophy
|
||||
|
||||
1. **Unit tests** focus on business logic (data parsing, score calculation, filtering)
|
||||
2. **E2E tests** validate user-facing functionality (navigation, rendering, interactions)
|
||||
3. **Both** run automatically in CI on every commit
|
||||
4. **Coverage** targets meaningful tests, not arbitrary percentages
|
||||
|
||||
---
|
||||
|
||||
## Unit Testing
|
||||
|
||||
### Framework: Vitest
|
||||
|
||||
Vitest is a fast, modern testing framework compatible with Jest APIs but optimized for Vite-based projects.
|
||||
|
||||
### Running Unit Tests
|
||||
|
||||
```bash
|
||||
# Run all unit tests
|
||||
npm test
|
||||
|
||||
# Run in watch mode (re-runs on file changes)
|
||||
npm run test:watch
|
||||
|
||||
# Run specific test file
|
||||
npx vitest src/api/polaris.test.ts
|
||||
|
||||
# Run with coverage
|
||||
npx vitest --coverage
|
||||
```
|
||||
|
||||
### Test Structure
|
||||
|
||||
Unit tests are colocated with source files:
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/
|
||||
│ ├── polaris.ts
|
||||
│ ├── polaris.test.ts # Unit tests for polaris.ts
|
||||
│ ├── PolarisDataContext.tsx
|
||||
│ └── PolarisDataContext.test.tsx
|
||||
└── components/
|
||||
├── DashboardView.tsx
|
||||
└── DashboardView.test.tsx
|
||||
```
|
||||
|
||||
### Example: Testing Utility Functions
|
||||
|
||||
**File:** `src/api/polaris.test.ts`
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { countResults, computeScore, getNamespaces, filterResultsByNamespace } from './polaris';
|
||||
|
||||
describe('countResults', () => {
|
||||
it('counts passing, warning, danger, and skipped results correctly', () => {
|
||||
const data = {
|
||||
Results: [
|
||||
{
|
||||
Name: 'test-deployment',
|
||||
Namespace: 'default',
|
||||
Kind: 'Deployment',
|
||||
Results: {
|
||||
'check-1': { Success: true, Severity: 'warning' },
|
||||
'check-2': { Success: false, Severity: 'danger' },
|
||||
'check-3': { Success: false, Severity: 'ignore' }, // skipped
|
||||
},
|
||||
CreatedTime: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const counts = countResults(data);
|
||||
|
||||
expect(counts).toEqual({
|
||||
total: 3,
|
||||
pass: 1,
|
||||
warning: 0,
|
||||
danger: 1,
|
||||
skipped: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('handles empty results', () => {
|
||||
const data = { Results: [] };
|
||||
const counts = countResults(data);
|
||||
|
||||
expect(counts).toEqual({
|
||||
total: 0,
|
||||
pass: 0,
|
||||
warning: 0,
|
||||
danger: 0,
|
||||
skipped: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeScore', () => {
|
||||
it('returns 0 for zero total checks', () => {
|
||||
expect(computeScore({ total: 0, pass: 0, warning: 0, danger: 0, skipped: 0 })).toBe(0);
|
||||
});
|
||||
|
||||
it('calculates percentage correctly', () => {
|
||||
expect(computeScore({ total: 100, pass: 75, warning: 20, danger: 5, skipped: 0 })).toBe(75);
|
||||
});
|
||||
|
||||
it('rounds to nearest integer', () => {
|
||||
expect(computeScore({ total: 3, pass: 2, warning: 1, danger: 0, skipped: 0 })).toBe(67);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Example: Testing React Components
|
||||
|
||||
**File:** `src/components/DashboardView.test.tsx`
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { DashboardView } from './DashboardView';
|
||||
import * as PolarisDataContext from '../api/PolarisDataContext';
|
||||
|
||||
describe('DashboardView', () => {
|
||||
it('renders loading state', () => {
|
||||
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
||||
data: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders error state', () => {
|
||||
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: '403 Forbidden',
|
||||
refresh: vi.fn(),
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByText(/403 Forbidden/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays cluster score when data is loaded', () => {
|
||||
const mockData = {
|
||||
DisplayName: 'test-cluster',
|
||||
ClusterInfo: { Version: '1.27', Nodes: 3, Pods: 100, Namespaces: 10, Controllers: 50 },
|
||||
Results: [
|
||||
/* ... */
|
||||
],
|
||||
};
|
||||
|
||||
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
||||
data: mockData,
|
||||
loading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByText(/Cluster Score/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### What to Unit Test
|
||||
|
||||
✅ **Do test:**
|
||||
|
||||
- Pure functions (score calculation, filtering, data transformation)
|
||||
- Data parsing and validation
|
||||
- Utility functions
|
||||
- Error handling logic
|
||||
- Edge cases (empty arrays, null values, invalid input)
|
||||
|
||||
❌ **Don't test:**
|
||||
|
||||
- Third-party libraries (Headlamp, React)
|
||||
- Simple prop passing
|
||||
- Trivial getters/setters
|
||||
- Implementation details (internal state)
|
||||
|
||||
---
|
||||
|
||||
## E2E Testing
|
||||
|
||||
### Framework: Playwright
|
||||
|
||||
Playwright provides cross-browser testing with auto-wait, network interception, and screenshot/video capture.
|
||||
|
||||
### Running E2E Tests
|
||||
|
||||
```bash
|
||||
# Run all E2E tests (headless)
|
||||
npm run e2e
|
||||
|
||||
# Run with browser visible (headed mode)
|
||||
npm run e2e:headed
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test e2e/polaris.spec.ts
|
||||
|
||||
# Debug mode (step through tests)
|
||||
npx playwright test --debug
|
||||
|
||||
# Generate trace for debugging
|
||||
npx playwright test --trace on
|
||||
npx playwright show-trace test-results/<test-name>/trace.zip
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
|
||||
**1. Headlamp Instance**
|
||||
|
||||
E2E tests require a running Headlamp instance with:
|
||||
|
||||
- Polaris plugin installed (version being tested)
|
||||
- Polaris dashboard deployed and accessible
|
||||
- RBAC configured (service proxy permissions)
|
||||
|
||||
**2. Authentication**
|
||||
|
||||
Choose one of two authentication methods:
|
||||
|
||||
**Option A: OIDC via Authentik**
|
||||
|
||||
```bash
|
||||
export AUTHENTIK_USERNAME="user@example.com"
|
||||
export AUTHENTIK_PASSWORD="password"
|
||||
export HEADLAMP_URL="https://headlamp.example.com"
|
||||
npm run e2e
|
||||
```
|
||||
|
||||
**Option B: Kubernetes Token**
|
||||
|
||||
```bash
|
||||
# Create token
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
|
||||
# Port-forward for local testing
|
||||
kubectl port-forward -n kube-system svc/headlamp 4466:80
|
||||
|
||||
# Run tests
|
||||
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
||||
```
|
||||
|
||||
**3. Environment Variables**
|
||||
|
||||
Create `.env` file (optional, for persistent config):
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env`:
|
||||
|
||||
```bash
|
||||
HEADLAMP_URL=https://headlamp.example.com
|
||||
HEADLAMP_TOKEN=eyJhbGciOi...
|
||||
# OR
|
||||
AUTHENTIK_USERNAME=user@example.com
|
||||
AUTHENTIK_PASSWORD=secret
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
|
||||
#### Current E2E Tests
|
||||
|
||||
**File:** `e2e/polaris.spec.ts`
|
||||
|
||||
| Test | Description | Validates |
|
||||
| --------------------------------------------- | ------------------------------- | ----------------------------- |
|
||||
| `sidebar contains Polaris entry` | Polaris appears in sidebar | Plugin registration |
|
||||
| `overview page renders cluster score` | Score displayed on overview | Data fetching, rendering |
|
||||
| `namespaces page renders table` | Namespace table loads | Data parsing, table rendering |
|
||||
| `namespace detail drawer opens` | Clicking namespace shows drawer | Navigation, drawer UI |
|
||||
| `namespace detail drawer closes with Escape` | Keyboard shortcut works | Keyboard navigation |
|
||||
| `namespace detail drawer opens from URL hash` | Direct URL navigation | URL routing, deep linking |
|
||||
|
||||
**File:** `e2e/settings.spec.ts`
|
||||
|
||||
| Test | Description | Validates |
|
||||
| ------------------------------------ | ------------------- | --------------------------- |
|
||||
| `plugin settings page is accessible` | Settings page loads | Settings registration |
|
||||
| `refresh interval can be changed` | Dropdown works | User preference persistence |
|
||||
| `dashboard URL can be customized` | Input field works | URL configuration |
|
||||
| `connection test button works` | Test functionality | API connectivity validation |
|
||||
|
||||
**File:** `e2e/appbar.spec.ts`
|
||||
|
||||
| Test | Description | Validates |
|
||||
| -------------------------------------- | ------------------------------- | ------------------- |
|
||||
| `app bar displays Polaris badge` | Badge visible in header | App bar integration |
|
||||
| `badge shows cluster score` | Score matches dashboard | Data consistency |
|
||||
| `clicking badge navigates to overview` | Navigation works | App bar action |
|
||||
| `badge color reflects score` | Red/yellow/green based on score | Visual feedback |
|
||||
|
||||
### Writing E2E Tests
|
||||
|
||||
**Example: Testing User Flow**
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('user can view namespace details and navigate back', async ({ page }) => {
|
||||
// Navigate to namespaces page
|
||||
await page.goto('/c/main/polaris/namespaces');
|
||||
await expect(page.getByText('Polaris — Namespaces')).toBeVisible();
|
||||
|
||||
// Click first namespace
|
||||
const firstNamespace = page.locator('table tbody tr').first();
|
||||
const namespaceName = await firstNamespace.locator('td').first().textContent();
|
||||
await firstNamespace.getByRole('button').click();
|
||||
|
||||
// Drawer should open
|
||||
await expect(page.getByText(`Polaris — ${namespaceName}`)).toBeVisible();
|
||||
await expect(page).toHaveURL(new RegExp(`#${namespaceName}`));
|
||||
|
||||
// Close drawer with Escape
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByText(`Polaris — ${namespaceName}`)).not.toBeVisible();
|
||||
await expect(page).toHaveURL(/namespaces$/);
|
||||
});
|
||||
```
|
||||
|
||||
**Example: Testing Dark Mode Adaptation**
|
||||
|
||||
```typescript
|
||||
test('plugin adapts to dark mode', async ({ page }) => {
|
||||
await page.goto('/c/main/polaris/namespaces');
|
||||
|
||||
// Toggle dark mode
|
||||
await page.getByLabel(/theme/i).click();
|
||||
|
||||
// Open namespace drawer
|
||||
const firstNamespace = page.locator('table tbody tr button').first();
|
||||
await firstNamespace.click();
|
||||
|
||||
// Drawer background should be dark
|
||||
const drawer = page.locator('[style*="position: fixed"][style*="right: 0"]');
|
||||
await expect(drawer).toHaveCSS('background-color', /rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
||||
});
|
||||
```
|
||||
|
||||
### Debugging E2E Tests
|
||||
|
||||
**1. Headed Mode (See Browser)**
|
||||
|
||||
```bash
|
||||
npm run e2e:headed
|
||||
```
|
||||
|
||||
**2. Debug Mode (Step Through Tests)**
|
||||
|
||||
```bash
|
||||
npx playwright test --debug
|
||||
```
|
||||
|
||||
This opens Playwright Inspector where you can:
|
||||
|
||||
- Step through each test action
|
||||
- Inspect page state
|
||||
- Edit test selectors live
|
||||
|
||||
**3. Screenshots on Failure**
|
||||
|
||||
Screenshots are automatically saved to `test-results/` on failure:
|
||||
|
||||
```bash
|
||||
test-results/
|
||||
└── polaris-overview-page-renders-cluster-score/
|
||||
├── test-failed-1.png
|
||||
└── trace.zip
|
||||
```
|
||||
|
||||
**4. Trace Viewer**
|
||||
|
||||
Record full trace (DOM snapshots, network, console):
|
||||
|
||||
```bash
|
||||
npx playwright test --trace on
|
||||
npx playwright show-trace test-results/<test-name>/trace.zip
|
||||
```
|
||||
|
||||
**5. Verbose Logging**
|
||||
|
||||
```bash
|
||||
DEBUG=pw:api npx playwright test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Workflows
|
||||
|
||||
#### CI Workflow (`.github/workflows/ci.yaml`)
|
||||
|
||||
Runs on every push to `main` and all pull requests:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
lint-and-test:
|
||||
steps:
|
||||
- Build plugin
|
||||
- Lint (eslint)
|
||||
- Type-check (tsc)
|
||||
- Format check (prettier)
|
||||
- Run unit tests
|
||||
```
|
||||
|
||||
#### E2E Workflow (`.github/workflows/e2e.yaml`)
|
||||
|
||||
Runs E2E tests against live Headlamp instance:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
e2e:
|
||||
steps:
|
||||
- Install dependencies
|
||||
- Install Playwright browsers
|
||||
- Run auth setup
|
||||
- Run E2E tests
|
||||
- Upload artifacts on failure
|
||||
```
|
||||
|
||||
### Required GitHub Secrets
|
||||
|
||||
Configure in GitHub repository settings (Settings → Secrets and variables → Actions):
|
||||
|
||||
| Secret | Required | Description |
|
||||
| -------------------- | -------- | ------------------------------------------------------- |
|
||||
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to configured instance) |
|
||||
| `AUTHENTIK_USERNAME` | OIDC | Authentik username/email for CI user |
|
||||
| `AUTHENTIK_PASSWORD` | OIDC | Authentik password |
|
||||
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
|
||||
|
||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
||||
|
||||
### Manual Trigger
|
||||
|
||||
Trigger workflows manually from GitHub Actions UI:
|
||||
|
||||
1. Go to Actions → [Workflow Name]
|
||||
2. Click "Run workflow"
|
||||
3. Select branch and run
|
||||
|
||||
---
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### Current Coverage
|
||||
|
||||
| Category | Coverage | Notes |
|
||||
| -------------------- | -------- | ------------------------------ |
|
||||
| **API Functions** | 95% | Core utilities fully tested |
|
||||
| **React Components** | 60% | Focus on critical render paths |
|
||||
| **E2E User Flows** | 80% | Main features covered |
|
||||
|
||||
### Coverage Goals
|
||||
|
||||
- **Unit Tests**: 80%+ for `src/api/` (business logic)
|
||||
- **Component Tests**: 50%+ for `src/components/` (critical rendering)
|
||||
- **E2E Tests**: Cover all major user journeys
|
||||
|
||||
### Generating Coverage Reports
|
||||
|
||||
```bash
|
||||
# Unit test coverage
|
||||
npx vitest --coverage
|
||||
|
||||
# View HTML report
|
||||
open coverage/index.html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Unit Testing
|
||||
|
||||
1. **Test behavior, not implementation**
|
||||
|
||||
- ✅ `expect(computeScore({ total: 100, pass: 75 })).toBe(75)`
|
||||
- ❌ `expect(mockInternalFunction).toHaveBeenCalled()`
|
||||
|
||||
2. **Use descriptive test names**
|
||||
|
||||
- ✅ `it('returns 0 when total checks is zero')`
|
||||
- ❌ `it('works')`
|
||||
|
||||
3. **One assertion per test (when possible)**
|
||||
|
||||
- Makes failures easier to debug
|
||||
- Exceptions: testing multiple properties of same object
|
||||
|
||||
4. **Mock external dependencies**
|
||||
|
||||
- Mock API calls, context providers, external libraries
|
||||
- Don't mock the code you're testing
|
||||
|
||||
5. **Test edge cases**
|
||||
- Empty arrays, null values, zero counts
|
||||
- Invalid input, malformed data
|
||||
|
||||
### E2E Testing
|
||||
|
||||
1. **Use semantic selectors**
|
||||
|
||||
- ✅ `page.getByRole('button', { name: 'Close' })`
|
||||
- ✅ `page.getByText('Polaris — Overview')`
|
||||
- ❌ `page.locator('.MuiButton-root')`
|
||||
|
||||
2. **Wait for visibility, not arbitrary timeouts**
|
||||
|
||||
- ✅ `await expect(element).toBeVisible()`
|
||||
- ❌ `await page.waitForTimeout(5000)`
|
||||
|
||||
3. **Keep tests independent**
|
||||
|
||||
- Each test should work in isolation
|
||||
- Don't rely on previous tests' state
|
||||
|
||||
4. **Test complete user flows**
|
||||
|
||||
- Navigate → Interact → Verify outcome
|
||||
- Don't just test page loads
|
||||
|
||||
5. **Clean up after tests**
|
||||
|
||||
- Close drawers/modals
|
||||
- Reset state if needed
|
||||
|
||||
6. **Use storage state for auth**
|
||||
|
||||
- Reuse authenticated session across tests
|
||||
- Faster than logging in for every test
|
||||
|
||||
7. **Parallelize carefully**
|
||||
- Tests must not interfere with each other
|
||||
- Currently disabled due to shared cluster state
|
||||
|
||||
### General
|
||||
|
||||
1. **Run tests before committing**
|
||||
|
||||
```bash
|
||||
npm run build && npm run lint && npm test
|
||||
```
|
||||
|
||||
2. **Fix failing tests immediately**
|
||||
|
||||
- Don't commit failing tests
|
||||
- Don't skip tests to "fix later"
|
||||
|
||||
3. **Update tests when changing code**
|
||||
|
||||
- Tests are documentation
|
||||
- Keep them in sync with implementation
|
||||
|
||||
4. **Review test failures in CI**
|
||||
- Check artifacts (screenshots, traces)
|
||||
- Reproduce locally before fixing
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
**Issue: Mock not working**
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Mock after import
|
||||
import { usePolarisData } from './polaris';
|
||||
vi.mock('./polaris');
|
||||
|
||||
// ✅ Correct: Mock before import
|
||||
vi.mock('./polaris', () => ({
|
||||
usePolarisData: vi.fn(),
|
||||
}));
|
||||
import { usePolarisData } from './polaris';
|
||||
```
|
||||
|
||||
**Issue: "Cannot read property of undefined"**
|
||||
|
||||
Check mocks are returning expected structure:
|
||||
|
||||
```typescript
|
||||
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
||||
data: mockData, // Ensure mockData has all required fields
|
||||
loading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
});
|
||||
```
|
||||
|
||||
#### E2E Tests
|
||||
|
||||
**Issue: "Element not found"**
|
||||
|
||||
```bash
|
||||
# Enable verbose logging
|
||||
DEBUG=pw:api npx playwright test
|
||||
|
||||
# Use headed mode to see what's happening
|
||||
npm run e2e:headed
|
||||
```
|
||||
|
||||
Common causes:
|
||||
|
||||
- Element hasn't rendered yet (use `toBeVisible()` instead of `toBeDefined()`)
|
||||
- Wrong selector (use Playwright Inspector to verify)
|
||||
- Authentication failed (check token/credentials)
|
||||
|
||||
**Issue: "Test timeout"**
|
||||
|
||||
Increase timeout for slow operations:
|
||||
|
||||
```typescript
|
||||
test('slow operation', async ({ page }) => {
|
||||
test.setTimeout(60000); // 60 seconds
|
||||
|
||||
await page.goto('/c/main/polaris');
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
**Issue: Network errors in E2E tests**
|
||||
|
||||
```bash
|
||||
# Check Headlamp accessibility
|
||||
curl -I $HEADLAMP_URL
|
||||
|
||||
# Check Polaris service
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
```
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# Run specific test file
|
||||
npx vitest src/api/polaris.test.ts
|
||||
|
||||
# Run specific test case
|
||||
npx vitest -t "computes score correctly"
|
||||
|
||||
# Run E2E test by name
|
||||
npx playwright test -g "sidebar contains Polaris"
|
||||
|
||||
# Update snapshots
|
||||
npx vitest -u
|
||||
|
||||
# Clear test cache
|
||||
npx vitest --clearCache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Vitest Documentation](https://vitest.dev/)
|
||||
- [Playwright Documentation](https://playwright.dev/)
|
||||
- [Testing Library (React)](https://testing-library.com/docs/react-testing-library/intro/)
|
||||
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
- [Project Architecture](./ARCHITECTURE.md)
|
||||
- [Contributing Guide](../CONTRIBUTING.md)
|
||||
@@ -0,0 +1,715 @@
|
||||
# Troubleshooting Guide
|
||||
|
||||
This guide covers common issues encountered when using the Headlamp Polaris Plugin and their solutions.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Plugin Not Showing in Sidebar](#plugin-not-showing-in-sidebar)
|
||||
- [403 Forbidden Error](#403-forbidden-error)
|
||||
- [404 Not Found Error](#404-not-found-error)
|
||||
- [Plugin Settings Page Empty](#plugin-settings-page-empty)
|
||||
- [Dark Mode Issues](#dark-mode-issues)
|
||||
- [Data Not Loading / Infinite Spinner](#data-not-loading--infinite-spinner)
|
||||
- [Browser Console Errors](#browser-console-errors)
|
||||
- [Network and RBAC Debugging](#network-and-rbac-debugging)
|
||||
- [Plugin Installation Issues](#plugin-installation-issues)
|
||||
- [ArtifactHub Sync Delays](#artifacthub-sync-delays)
|
||||
|
||||
---
|
||||
|
||||
## Plugin Not Showing in Sidebar
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Plugin appears in Settings → Plugins but sidebar entry is missing
|
||||
- No "Polaris" section in navigation
|
||||
- Routes like `/polaris` return 404 or blank page
|
||||
|
||||
### Common Causes
|
||||
|
||||
**1. Plugin Not Installed**
|
||||
|
||||
**Check plugin installation**:
|
||||
|
||||
```bash
|
||||
# View Headlamp pod logs (plugin sidecar)
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
||||
|
||||
# Expected output:
|
||||
# Installing plugin from https://github.com/.../headlamp-polaris-plugin-X.Y.Z.tar.gz
|
||||
# Plugin installed successfully
|
||||
```
|
||||
|
||||
**Verify plugin files exist**:
|
||||
|
||||
```bash
|
||||
kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
||||
# Should show: headlamp-polaris-plugin/
|
||||
```
|
||||
|
||||
**2. JavaScript Cached by Browser**
|
||||
|
||||
After upgrading the plugin, old JavaScript may be cached.
|
||||
|
||||
**Solution**:
|
||||
|
||||
- Hard refresh: Cmd+Shift+R (macOS) or Ctrl+Shift+R (Linux/Windows)
|
||||
- Clear browser cache for Headlamp domain
|
||||
- Open DevTools → Application → Clear Storage → Clear all
|
||||
|
||||
**3. Plugin Disabled in Settings**
|
||||
|
||||
**Check Settings → Plugins**:
|
||||
|
||||
- Navigate to Headlamp Settings → Plugins
|
||||
- Ensure "Polaris" plugin is enabled (toggle should be ON)
|
||||
- If disabled, enable it and refresh the page
|
||||
|
||||
---
|
||||
|
||||
## 403 Forbidden Error
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Error message: "Error loading Polaris audit data: 403 Forbidden"
|
||||
- Browser console shows 403 response from API proxy
|
||||
- Plugin sidebar shows but data fails to load
|
||||
|
||||
### Root Cause
|
||||
|
||||
User or service account lacks `services/proxy` permission on `polaris-dashboard` service in the `polaris` namespace.
|
||||
|
||||
### Solution
|
||||
|
||||
**1. Verify RBAC Configuration**
|
||||
|
||||
Check if Role exists:
|
||||
|
||||
```bash
|
||||
kubectl get role polaris-proxy-reader -n polaris -o yaml
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
```
|
||||
|
||||
**2. Verify RoleBinding**
|
||||
|
||||
For service account mode:
|
||||
|
||||
```bash
|
||||
kubectl get rolebinding headlamp-polaris-proxy -n polaris -o yaml
|
||||
```
|
||||
|
||||
Expected subjects:
|
||||
|
||||
```yaml
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
```
|
||||
|
||||
For OIDC mode:
|
||||
|
||||
```bash
|
||||
kubectl get rolebinding -n polaris -o yaml | grep -A 5 polaris-proxy-reader
|
||||
```
|
||||
|
||||
Ensure your user or group is bound to the `polaris-proxy-reader` role.
|
||||
|
||||
**3. Create Missing RBAC**
|
||||
|
||||
If RBAC is missing, apply the minimal configuration:
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
EOF
|
||||
```
|
||||
|
||||
**4. Test RBAC Permissions**
|
||||
|
||||
Service account mode:
|
||||
|
||||
```bash
|
||||
# Impersonate Headlamp service account
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
--resource-name=polaris-dashboard \
|
||||
-n polaris
|
||||
# Expected: yes
|
||||
```
|
||||
|
||||
OIDC mode (test as yourself):
|
||||
|
||||
```bash
|
||||
kubectl auth can-i get services/proxy \
|
||||
--resource-name=polaris-dashboard \
|
||||
-n polaris
|
||||
# Expected: yes (if you have proper RoleBinding)
|
||||
```
|
||||
|
||||
**5. Restart Headlamp**
|
||||
|
||||
After applying RBAC changes:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment headlamp -n kube-system
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 404 Not Found Error
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Error message: "Error loading Polaris audit data: 404 Not Found"
|
||||
- Service proxy request returns 404
|
||||
- Polaris dashboard not reachable
|
||||
|
||||
### Root Cause
|
||||
|
||||
Polaris dashboard service doesn't exist or is in a different namespace.
|
||||
|
||||
### Solution
|
||||
|
||||
**1. Verify Polaris Installation**
|
||||
|
||||
Check if Polaris is installed:
|
||||
|
||||
```bash
|
||||
kubectl get pods -n polaris
|
||||
# Expected: polaris-dashboard-* pod running
|
||||
```
|
||||
|
||||
Check if service exists:
|
||||
|
||||
```bash
|
||||
kubectl get service polaris-dashboard -n polaris
|
||||
# Expected: ClusterIP service on port 80
|
||||
```
|
||||
|
||||
**2. Verify Service Name and Port**
|
||||
|
||||
The plugin expects:
|
||||
|
||||
- **Namespace**: `polaris`
|
||||
- **Service Name**: `polaris-dashboard`
|
||||
- **Port**: `80` (or named port `dashboard`)
|
||||
|
||||
If your service has a different name or is in a different namespace, you'll need to modify the plugin source or redeploy Polaris with standard naming.
|
||||
|
||||
**3. Test Service Proxy Manually**
|
||||
|
||||
```bash
|
||||
kubectl proxy &
|
||||
curl http://localhost:8001/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
```
|
||||
|
||||
If this returns JSON, the service proxy works and the issue is elsewhere.
|
||||
If this returns 404, Polaris service is not configured correctly.
|
||||
|
||||
**4. Check Polaris Dashboard Configuration**
|
||||
|
||||
Verify Polaris is running with dashboard enabled:
|
||||
|
||||
```bash
|
||||
kubectl get deployment polaris-dashboard -n polaris -o yaml | grep -A 5 dashboard
|
||||
```
|
||||
|
||||
If `dashboard.enabled: false` in Helm values, enable it:
|
||||
|
||||
```yaml
|
||||
# values.yaml
|
||||
dashboard:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
**5. Reinstall Polaris**
|
||||
|
||||
If Polaris is missing or misconfigured:
|
||||
|
||||
```bash
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm upgrade --install polaris fairwinds-stable/polaris \
|
||||
--namespace polaris \
|
||||
--create-namespace \
|
||||
--set dashboard.enabled=true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plugin Settings Page Empty
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Settings → Polaris shows title but no content
|
||||
- Refresh interval and dashboard URL fields not visible
|
||||
|
||||
### Root Cause (Fixed in v0.3.3)
|
||||
|
||||
Plugin settings registration name didn't match `package.json` name.
|
||||
|
||||
### Solution
|
||||
|
||||
Upgrade to v0.3.3 or later:
|
||||
|
||||
```bash
|
||||
# Via Headlamp UI: Settings → Plugins → Update
|
||||
# Or redeploy with latest version
|
||||
```
|
||||
|
||||
If manually installing, ensure plugin name matches `package.json`:
|
||||
|
||||
```typescript
|
||||
registerPluginSettings('headlamp-polaris-plugin', PolarisSettings, true);
|
||||
// NOT 'polaris' — must match package.json name
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode Issues
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Drawer background remains white in dark mode
|
||||
- Text is hard to read in dark mode
|
||||
- Theme colors don't match Headlamp UI
|
||||
|
||||
### Solution (Fixed in v0.3.5)
|
||||
|
||||
Upgrade to v0.3.5 or later for complete dark mode support.
|
||||
|
||||
**Verify CSS Variables**:
|
||||
The plugin uses MUI CSS variables for theming:
|
||||
|
||||
- `--mui-palette-background-default` (drawer background)
|
||||
- `--mui-palette-text-primary` (text color)
|
||||
- `--mui-palette-primary-main` (links, buttons)
|
||||
- `--mui-palette-error-main` (danger states)
|
||||
|
||||
These automatically adapt to Headlamp's theme (light/dark/system).
|
||||
|
||||
**Hard Refresh Required**:
|
||||
After upgrading from v0.3.4 or earlier, hard refresh your browser:
|
||||
|
||||
- macOS: Cmd+Shift+R
|
||||
- Linux/Windows: Ctrl+Shift+R
|
||||
|
||||
**Clear Browser Cache**:
|
||||
If hard refresh doesn't help, clear cache for Headlamp domain.
|
||||
|
||||
---
|
||||
|
||||
## Data Not Loading / Infinite Spinner
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Plugin shows "Loading Polaris audit data..." forever
|
||||
- No error message in UI
|
||||
- Data never appears
|
||||
|
||||
### Debugging Steps
|
||||
|
||||
**1. Check Browser Console**
|
||||
|
||||
Open DevTools (F12) → Console tab.
|
||||
|
||||
Look for:
|
||||
|
||||
- Network errors (CORS, timeouts, 5xx responses)
|
||||
- JavaScript errors
|
||||
- Failed API requests
|
||||
|
||||
**2. Check Network Tab**
|
||||
|
||||
Open DevTools → Network tab → Filter by "results.json"
|
||||
|
||||
Expected request:
|
||||
|
||||
```
|
||||
GET /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
Status: 200
|
||||
Response: JSON data
|
||||
```
|
||||
|
||||
Common issues:
|
||||
|
||||
- **Status 0 / Failed**: Network policy blocking request
|
||||
- **Status 403**: RBAC issue (see [403 Forbidden Error](#403-forbidden-error))
|
||||
- **Status 404**: Service not found (see [404 Not Found Error](#404-not-found-error))
|
||||
- **Status 500**: Polaris dashboard error
|
||||
- **Status 502/504**: Service unreachable (network policy or pod down)
|
||||
|
||||
**3. Check Polaris Dashboard Health**
|
||||
|
||||
```bash
|
||||
# Check if Polaris pod is running
|
||||
kubectl get pods -n polaris
|
||||
|
||||
# Check Polaris logs
|
||||
kubectl logs -n polaris deployment/polaris-dashboard
|
||||
|
||||
# Test direct access to Polaris
|
||||
kubectl port-forward -n polaris svc/polaris-dashboard 8080:80
|
||||
curl http://localhost:8080/results.json
|
||||
```
|
||||
|
||||
**4. Check Network Policies**
|
||||
|
||||
If your cluster uses NetworkPolicies:
|
||||
|
||||
```bash
|
||||
kubectl get networkpolicy -n polaris
|
||||
```
|
||||
|
||||
Ensure API server (or Headlamp pod) can reach Polaris dashboard.
|
||||
|
||||
**Example fix**:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-api-server-to-polaris
|
||||
namespace: polaris
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: polaris
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector: {} # Allow from all namespaces (API server)
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
```
|
||||
|
||||
**5. Increase Timeout / Disable Auto-Refresh**
|
||||
|
||||
If Polaris responds slowly:
|
||||
|
||||
- Open Settings → Polaris
|
||||
- Increase refresh interval to 10+ minutes
|
||||
- Or set to "Manual only" to disable auto-refresh
|
||||
|
||||
---
|
||||
|
||||
## Browser Console Errors
|
||||
|
||||
### Common Errors and Solutions
|
||||
|
||||
**Error: "Failed to fetch"**
|
||||
|
||||
**Cause**: Network request failed (CORS, network policy, timeout)
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Check Network tab for actual HTTP status
|
||||
2. Verify network policies allow API server → Polaris
|
||||
3. Check Polaris pod is running
|
||||
|
||||
---
|
||||
|
||||
**Error: "Unexpected token < in JSON"**
|
||||
|
||||
**Cause**: API returned HTML (error page) instead of JSON
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Check Network tab response body (likely 404 or 500 error page)
|
||||
2. Verify Polaris service exists and is healthy
|
||||
3. Check service proxy URL is correct
|
||||
|
||||
---
|
||||
|
||||
**Error: "registerPluginSettings is not a function"**
|
||||
|
||||
**Cause**: Headlamp version too old (< v0.26)
|
||||
|
||||
**Solution**: Upgrade Headlamp to v0.26 or later.
|
||||
|
||||
---
|
||||
|
||||
**Error: "Cannot read property 'AuditData' of undefined"**
|
||||
|
||||
**Cause**: Polaris returned empty or malformed response
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Check Polaris logs for errors
|
||||
2. Verify Polaris is scanning the cluster (check audit timestamp)
|
||||
3. Test `/results.json` endpoint directly
|
||||
|
||||
---
|
||||
|
||||
## Network and RBAC Debugging
|
||||
|
||||
### Comprehensive RBAC Test
|
||||
|
||||
Run this script to test all RBAC components:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
NS="polaris"
|
||||
SA="headlamp"
|
||||
SA_NS="kube-system"
|
||||
|
||||
echo "=== Testing RBAC for Polaris Plugin ==="
|
||||
|
||||
# 1. Check if service exists
|
||||
echo "1. Service check:"
|
||||
kubectl get svc polaris-dashboard -n $NS || echo "❌ Service not found"
|
||||
|
||||
# 2. Check if Role exists
|
||||
echo "2. Role check:"
|
||||
kubectl get role polaris-proxy-reader -n $NS || echo "❌ Role not found"
|
||||
|
||||
# 3. Check if RoleBinding exists
|
||||
echo "3. RoleBinding check:"
|
||||
kubectl get rolebinding headlamp-polaris-proxy -n $NS || echo "❌ RoleBinding not found"
|
||||
|
||||
# 4. Test service account permissions
|
||||
echo "4. Permission test (service account):"
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:$SA_NS:$SA \
|
||||
--resource-name=polaris-dashboard \
|
||||
-n $NS
|
||||
|
||||
# 5. Test actual proxy request (requires kubectl proxy)
|
||||
echo "5. Proxy test:"
|
||||
kubectl proxy &
|
||||
PROXY_PID=$!
|
||||
sleep 2
|
||||
curl -s http://localhost:8001/api/v1/namespaces/$NS/services/polaris-dashboard:80/proxy/results.json | jq '.DisplayName' || echo "❌ Proxy request failed"
|
||||
kill $PROXY_PID
|
||||
|
||||
echo "=== Test complete ==="
|
||||
```
|
||||
|
||||
### Network Policy Debugging
|
||||
|
||||
Test connectivity from Headlamp to Polaris:
|
||||
|
||||
```bash
|
||||
# Create debug pod in kube-system namespace
|
||||
kubectl run netdebug -n kube-system --rm -it --image=nicolaka/netshoot -- bash
|
||||
|
||||
# Inside pod, test DNS and HTTP
|
||||
nslookup polaris-dashboard.polaris.svc.cluster.local
|
||||
curl -v http://polaris-dashboard.polaris.svc.cluster.local/results.json
|
||||
```
|
||||
|
||||
If this fails, network policies are blocking traffic.
|
||||
|
||||
### API Server Audit Logs
|
||||
|
||||
If you have audit logging enabled, check for denied requests:
|
||||
|
||||
```bash
|
||||
# View recent audit logs (location varies by cluster)
|
||||
kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
|
||||
|
||||
# Look for lines with:
|
||||
# "reason": "Forbidden"
|
||||
# "user": "system:serviceaccount:kube-system:headlamp"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plugin Installation Issues
|
||||
|
||||
### Sidecar Fails to Install Plugin
|
||||
|
||||
**Symptoms**:
|
||||
|
||||
- Plugin sidecar logs show download errors
|
||||
- Plugin directory is empty
|
||||
- Settings → Plugins shows nothing
|
||||
|
||||
**Check sidecar logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
||||
```
|
||||
|
||||
**Common errors**:
|
||||
|
||||
**1. Network timeout downloading tarball**
|
||||
|
||||
```
|
||||
Error: connect ETIMEDOUT
|
||||
```
|
||||
|
||||
**Solution**: Check cluster egress network policies allow HTTPS to GitHub.
|
||||
|
||||
---
|
||||
|
||||
**2. Invalid tarball URL**
|
||||
|
||||
```
|
||||
Error: 404 Not Found
|
||||
```
|
||||
|
||||
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
|
||||
|
||||
```bash
|
||||
kubectl get configmap headlamp-plugin-config -n kube-system -o yaml
|
||||
```
|
||||
|
||||
Expected format:
|
||||
|
||||
```
|
||||
https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**3. Permission denied writing to /headlamp/plugins**
|
||||
|
||||
**Solution**: Ensure volume mount is writable:
|
||||
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /headlamp/plugins
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Plugin Manager Not Working
|
||||
|
||||
**Symptoms**:
|
||||
|
||||
- Headlamp → Settings → Plugins shows "Catalog" tab but plugins don't install
|
||||
- "Install" button does nothing
|
||||
|
||||
**Root Cause**: Plugin manager requires `config.pluginsDir` to be set.
|
||||
|
||||
**Solution**: Configure Headlamp for plugin manager:
|
||||
|
||||
```yaml
|
||||
# HelmRelease values
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ArtifactHub Sync Delays
|
||||
|
||||
### Symptoms
|
||||
|
||||
- New version released on GitHub but not showing in ArtifactHub
|
||||
- Headlamp plugin catalog shows old version
|
||||
|
||||
### Root Cause
|
||||
|
||||
ArtifactHub pulls metadata every 30 minutes. There is no webhook or push mechanism.
|
||||
|
||||
### Solution
|
||||
|
||||
**Wait 30 minutes** after pushing a GitHub release, then check:
|
||||
|
||||
```
|
||||
https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin
|
||||
```
|
||||
|
||||
**Verify metadata**:
|
||||
|
||||
1. Check `artifacthub-pkg.yml` is in repository root
|
||||
2. Check `headlamp/plugin/archive-url` points to GitHub release
|
||||
3. Check `headlamp/plugin/archive-checksum` matches tarball SHA256
|
||||
|
||||
**Force sync** (ArtifactHub UI):
|
||||
|
||||
- Log in to ArtifactHub as package maintainer
|
||||
- Go to package settings
|
||||
- Click "Reindex now"
|
||||
|
||||
**Note**: First sync after repository registration may take up to 1 hour.
|
||||
|
||||
---
|
||||
|
||||
## Still Having Issues?
|
||||
|
||||
If none of these solutions work, gather debugging information and open an issue:
|
||||
|
||||
### Required Information
|
||||
|
||||
1. **Version Information**:
|
||||
|
||||
```bash
|
||||
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
||||
```
|
||||
|
||||
2. **Plugin Version**:
|
||||
|
||||
- Check Settings → Plugins in Headlamp UI
|
||||
- Or: `kubectl exec -n kube-system deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
|
||||
|
||||
3. **Browser Console Output**:
|
||||
|
||||
- Open DevTools (F12) → Console
|
||||
- Screenshot or copy errors
|
||||
|
||||
4. **Network Tab**:
|
||||
|
||||
- Open DevTools → Network
|
||||
- Screenshot failed requests to `results.json`
|
||||
|
||||
5. **Pod Logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100
|
||||
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
|
||||
```
|
||||
|
||||
6. **RBAC Configuration**:
|
||||
```bash
|
||||
kubectl get role,rolebinding -n polaris
|
||||
```
|
||||
|
||||
### Where to Get Help
|
||||
|
||||
- **GitHub Issues**: [https://github.com/privilegedescalation/headlamp-polaris-plugin/issues](https://github.com/privilegedescalation/headlamp-polaris-plugin/issues)
|
||||
- **GitHub Discussions**: [https://github.com/privilegedescalation/headlamp-polaris-plugin/discussions](https://github.com/privilegedescalation/headlamp-polaris-plugin/discussions)
|
||||
|
||||
Include the debugging information above when opening an issue.
|
||||
@@ -0,0 +1,222 @@
|
||||
# ADR-001: Use React Context for State Management
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-02-12
|
||||
**Deciders:** Plugin maintainers
|
||||
|
||||
## Context
|
||||
|
||||
The Headlamp Polaris Plugin needs to fetch Polaris audit data once and share it across multiple components:
|
||||
|
||||
- Dashboard view (cluster overview)
|
||||
- Namespaces list view
|
||||
- Namespace detail view (drawer)
|
||||
- Inline audit sections on resource detail pages
|
||||
- App bar score badge
|
||||
|
||||
Multiple state management approaches are available: Redux, Zustand, Jotai, Recoil, React Context (built-in), or component props with prop drilling.
|
||||
|
||||
**Constraints:**
|
||||
|
||||
- Headlamp plugin environment does not allow adding external dependencies (peer dependencies only)
|
||||
- Redux, Zustand, Jotai, Recoil are not available in the plugin runtime
|
||||
- Plugin must work with Headlamp's existing React context (React 17+)
|
||||
- Bundle size should remain small (<50 KB)
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Share `AuditData` object across all views without duplicate API calls
|
||||
- Support auto-refresh on user-configurable interval (1-30 minutes)
|
||||
- Handle loading and error states consistently
|
||||
- Minimal re-renders (data updates infrequently)
|
||||
|
||||
## Decision
|
||||
|
||||
Use **React Context API** (built-in, no dependencies) for shared state management.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- `PolarisDataProvider` wraps all plugin routes
|
||||
- `usePolarisDataContext()` hook provides `{ data, loading, error, refresh }` to consumers
|
||||
- Single fetch shared across all views
|
||||
- Auto-refresh via `useEffect` + interval timer
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- ✅ **No additional dependencies** - Plugins cannot add external libraries (Headlamp constraint)
|
||||
- ✅ **Simple implementation** - Single AuditData object, read-only, no complex mutations
|
||||
- ✅ **Built into React** - No learning curve, well-documented, stable API
|
||||
- ✅ **Small bundle impact** - 0 KB additional (built-in feature)
|
||||
- ✅ **Works with Headlamp** - Compatible with Headlamp's React version and plugin system
|
||||
- ✅ **TypeScript support** - Full type safety with `React.createContext<T>()`
|
||||
|
||||
### Negative
|
||||
|
||||
- ❌ **Less powerful for complex state** - No built-in middleware, time-travel debugging, or DevTools
|
||||
- ❌ **Potential for unnecessary re-renders** - All consumers re-render on context update
|
||||
- **Mitigated by:** Data updates every 5-30 minutes (low frequency), memoization not needed
|
||||
- ❌ **No built-in async handling** - Must implement loading/error states manually
|
||||
- **Mitigated by:** Simple `useState` + `useEffect` pattern sufficient
|
||||
|
||||
### Neutral
|
||||
|
||||
- Performance is excellent for this use case (infrequent updates, small consumer count)
|
||||
- Context providers work well for read-only or mostly-read state
|
||||
- Standard React pattern, familiar to contributors
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Option 1: Redux
|
||||
|
||||
**Pros:**
|
||||
|
||||
- Powerful state management with middleware
|
||||
- Excellent DevTools for debugging
|
||||
- Time-travel debugging
|
||||
- Well-established patterns
|
||||
|
||||
**Cons:**
|
||||
|
||||
- Redux is not available as a peer dependency in Headlamp plugins
|
||||
- Massive overkill for single AuditData object
|
||||
- Adds significant bundle size (~10-15 KB)
|
||||
- Requires additional boilerplate (actions, reducers, store)
|
||||
|
||||
**Decision:** Rejected (dependency not available, too heavy)
|
||||
|
||||
### Option 2: Zustand
|
||||
|
||||
**Pros:**
|
||||
|
||||
- Lightweight (~1 KB)
|
||||
- Simple API similar to `useState`
|
||||
- No provider boilerplate
|
||||
|
||||
**Cons:**
|
||||
|
||||
- External peer dependency (not available in plugin runtime)
|
||||
- Still adds bundle size
|
||||
- Unnecessary for read-only state
|
||||
|
||||
**Decision:** Rejected (dependency not available)
|
||||
|
||||
### Option 3: Component Props (Prop Drilling)
|
||||
|
||||
**Pros:**
|
||||
|
||||
- No dependencies
|
||||
- Explicit data flow
|
||||
- TypeScript tracks prop types
|
||||
|
||||
**Cons:**
|
||||
|
||||
- Prop drilling through 5+ component layers (index.tsx → route → view → subcomponent)
|
||||
- Duplicate fetches if not carefully managed
|
||||
- Refactoring nightmare if component tree changes
|
||||
- Each route would need its own fetch logic
|
||||
|
||||
**Decision:** Rejected (poor code organization, maintenance burden)
|
||||
|
||||
### Option 4: Global Variable / Module State
|
||||
|
||||
**Pros:**
|
||||
|
||||
- Simple to implement
|
||||
- No React dependencies
|
||||
|
||||
**Cons:**
|
||||
|
||||
- No reactivity (components don't re-render on data change)
|
||||
- No built-in loading/error handling
|
||||
- Breaks React's declarative model
|
||||
- Testing difficulties (global mutable state)
|
||||
|
||||
**Decision:** Rejected (not idiomatic React, no reactivity)
|
||||
|
||||
## Implementation Details
|
||||
|
||||
**Context Definition:**
|
||||
|
||||
```typescript
|
||||
interface PolarisDataContextValue {
|
||||
data: AuditData | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
const PolarisDataContext = React.createContext<PolarisDataContextValue | undefined>(undefined);
|
||||
```
|
||||
|
||||
**Provider Implementation:**
|
||||
|
||||
```typescript
|
||||
export function PolarisDataProvider({ children }: { children: React.ReactNode }) {
|
||||
const [data, setData] = useState<AuditData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
setRefreshKey(k => k + 1);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch logic here
|
||||
// Auto-refresh on interval
|
||||
}, [refreshKey]);
|
||||
|
||||
return (
|
||||
<PolarisDataContext.Provider value={{ data, loading, error, refresh }}>
|
||||
{children}
|
||||
</PolarisDataContext.Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Consumer Hook:**
|
||||
|
||||
```typescript
|
||||
export function usePolarisDataContext() {
|
||||
const context = useContext(PolarisDataContext);
|
||||
if (!context) {
|
||||
throw new Error('usePolarisDataContext must be used within PolarisDataProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
**Success Metrics:**
|
||||
|
||||
- ✅ All views share single fetch (verified via network tab - one request per refresh)
|
||||
- ✅ No duplicate API calls (verified via Kubernetes audit logs)
|
||||
- ✅ Auto-refresh works correctly (5-30 minute intervals)
|
||||
- ✅ Loading states consistent across views
|
||||
- ✅ Error handling consistent across views
|
||||
- ✅ Bundle size remains <50 KB (currently ~27 KB)
|
||||
|
||||
**Tested Scenarios:**
|
||||
|
||||
- ✅ Initial load with loading spinner
|
||||
- ✅ Error handling (403, 404, network errors)
|
||||
- ✅ Manual refresh via button
|
||||
- ✅ Auto-refresh interval (configurable via settings)
|
||||
- ✅ Multiple views consuming same data (no duplicate fetches)
|
||||
- ✅ Navigation between routes (data persists)
|
||||
|
||||
## References
|
||||
|
||||
- [React Context API](https://react.dev/reference/react/useContext)
|
||||
- [React Context Performance](https://react.dev/reference/react/useContext#optimizing-re-renders-when-passing-objects-and-functions)
|
||||
- [Headlamp Plugin Constraints](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
- [Plugin Implementation](../../api/PolarisDataContext.tsx)
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Author | Change |
|
||||
| ---------- | ----------- | ---------------- |
|
||||
| 2026-02-12 | Plugin Team | Initial decision |
|
||||
@@ -0,0 +1,95 @@
|
||||
# Architecture Decision Records
|
||||
|
||||
This directory contains Architecture Decision Records (ADRs) for significant architectural choices made in the Headlamp Polaris Plugin.
|
||||
|
||||
## What is an ADR?
|
||||
|
||||
An Architecture Decision Record (ADR) captures an important architectural decision made along with its context and consequences. AD Rs provide historical context for future developers and serve as documentation for why certain approaches were chosen.
|
||||
|
||||
## When to Create an ADR
|
||||
|
||||
Create an ADR when:
|
||||
|
||||
- Making a significant architectural choice (e.g., state management approach)
|
||||
- Selecting between multiple technology options (e.g., React Context vs. Redux)
|
||||
- Establishing a pattern that impacts multiple components
|
||||
- Making a trade-off decision with non-trivial consequences
|
||||
- Introducing a new dependency or external integration
|
||||
- Defining security or performance constraints
|
||||
|
||||
## ADR Format
|
||||
|
||||
Each ADR follows this template (based on Michael Nygard's format):
|
||||
|
||||
```markdown
|
||||
# ADR-NNN: Title
|
||||
|
||||
**Status**: [Proposed | Accepted | Deprecated | Superseded by ADR-XXX]
|
||||
**Date**: YYYY-MM-DD
|
||||
**Deciders**: [List key decision makers]
|
||||
|
||||
## Context
|
||||
|
||||
What is the issue that we're seeing that is motivating this decision or change?
|
||||
|
||||
## Decision
|
||||
|
||||
What is the change that we're proposing and/or doing?
|
||||
|
||||
## Consequences
|
||||
|
||||
What becomes easier or more difficult to do because of this change?
|
||||
|
||||
### Positive
|
||||
|
||||
- ...
|
||||
|
||||
### Negative
|
||||
|
||||
- ...
|
||||
|
||||
### Neutral
|
||||
|
||||
- ...
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Option 1: Name
|
||||
|
||||
**Pros**: ...
|
||||
**Cons**: ...
|
||||
**Decision**: Not chosen because...
|
||||
|
||||
## References
|
||||
|
||||
- [Link to related issues, docs, discussions]
|
||||
```
|
||||
|
||||
## ADR Index
|
||||
|
||||
| ADR | Title | Status | Date |
|
||||
| ------------------------------------- | -------------------------------------- | -------- | ---------- |
|
||||
| [001](001-react-context-for-state.md) | Use React Context for State Management | Accepted | 2026-02-12 |
|
||||
|
||||
**Note:** Additional ADRs documenting other significant decisions (service proxy approach, drawer navigation, MUI import restrictions) can be created following the template above.
|
||||
|
||||
## Creating a New ADR
|
||||
|
||||
1. **Determine the next ADR number** (e.g., if last ADR is 004, new ADR is 005)
|
||||
2. **Create a new file**: `NNN-short-title.md` (e.g., `005-exemption-management.md`)
|
||||
3. **Use the template above** and fill in all sections
|
||||
4. **Add entry to this README** in the ADR Index table
|
||||
5. **Submit for review** via pull request
|
||||
|
||||
## ADR Lifecycle
|
||||
|
||||
- **Proposed**: ADR is drafted and under discussion
|
||||
- **Accepted**: Decision has been made and is currently in effect
|
||||
- **Deprecated**: Decision is no longer recommended but not yet superseded
|
||||
- **Superseded by ADR-XXX**: Decision has been replaced by a newer ADR
|
||||
|
||||
## References
|
||||
|
||||
- [ADR GitHub Organization](https://adr.github.io/)
|
||||
- [Michael Nygard's ADR Template](https://github.com/joelparkerhenderson/architecture-decision-record/blob/main/templates/decision-record-template-by-michael-nygard/index.md)
|
||||
- [ADR Tools](https://github.com/npryce/adr-tools)
|
||||
@@ -0,0 +1,397 @@
|
||||
# Data Flow
|
||||
|
||||
Detailed data flow sequences for the Headlamp Polaris Plugin.
|
||||
|
||||
## 1. Initial Load
|
||||
|
||||
```
|
||||
User loads Headlamp
|
||||
↓
|
||||
Headlamp loads plugins
|
||||
↓
|
||||
Plugin registers routes, sidebar, app bar actions
|
||||
↓
|
||||
User navigates to /polaris
|
||||
↓
|
||||
DashboardView mounts
|
||||
↓
|
||||
PolarisDataContext.Provider wraps component
|
||||
↓
|
||||
usePolarisDataContext() hook triggers fetch
|
||||
↓
|
||||
ApiProxy.request() → K8s API → Service Proxy → Polaris
|
||||
↓
|
||||
AuditData returned and cached in Context
|
||||
↓
|
||||
Components receive data and render
|
||||
```
|
||||
|
||||
## 2. Data Refresh
|
||||
|
||||
```
|
||||
User clicks "Refresh" button or auto-refresh interval elapses
|
||||
↓
|
||||
refresh() function called in Context
|
||||
↓
|
||||
setRefreshKey() increments (forces re-fetch)
|
||||
↓
|
||||
useEffect dependency triggers new fetch
|
||||
↓
|
||||
ApiProxy.request() → Polaris Dashboard
|
||||
↓
|
||||
Context state updated with new data
|
||||
↓
|
||||
All consuming components re-render automatically
|
||||
```
|
||||
|
||||
## 3. Navigation Flow
|
||||
|
||||
```
|
||||
User clicks "Polaris" in sidebar
|
||||
↓
|
||||
Route: /c/main/polaris (DashboardView)
|
||||
↓
|
||||
Display cluster score, check distribution
|
||||
↓
|
||||
User clicks "Namespaces" submenu
|
||||
↓
|
||||
Route: /c/main/polaris/namespaces (NamespacesListView)
|
||||
↓
|
||||
Display table of namespaces with scores
|
||||
↓
|
||||
User clicks namespace button in table
|
||||
↓
|
||||
Drawer opens, URL hash updates (#namespace-name)
|
||||
↓
|
||||
NamespaceDetailView renders in drawer
|
||||
↓
|
||||
Display namespace score + resource table
|
||||
```
|
||||
|
||||
## 4. Error Handling Flow
|
||||
|
||||
```
|
||||
ApiProxy.request() called
|
||||
↓
|
||||
Fetch fails with HTTP error
|
||||
↓
|
||||
Error caught in usePolarisData hook
|
||||
↓
|
||||
Error status code checked (403, 404, 503, etc.)
|
||||
↓
|
||||
Context-specific error message set:
|
||||
• 403: RBAC permission denied
|
||||
• 404/503: Polaris not installed
|
||||
• Other: Generic network error
|
||||
↓
|
||||
Error state propagated to consuming components
|
||||
↓
|
||||
Components render error UI with StatusLabel
|
||||
↓
|
||||
User sees error message with actionable guidance
|
||||
```
|
||||
|
||||
## 5. Service Proxy Request Flow
|
||||
|
||||
```
|
||||
Plugin code: ApiProxy.request(path)
|
||||
↓
|
||||
Headlamp backend proxies request
|
||||
↓
|
||||
HTTP GET to Kubernetes API server
|
||||
↓
|
||||
API server authenticates request (service account or user token)
|
||||
↓
|
||||
API server checks RBAC:
|
||||
• Verb: get
|
||||
• Resource: services/proxy
|
||||
• ResourceName: polaris-dashboard
|
||||
• Namespace: polaris
|
||||
↓
|
||||
If authorized:
|
||||
API server proxies to Polaris service
|
||||
↓
|
||||
Polaris dashboard returns results.json
|
||||
↓
|
||||
Response flows back to plugin
|
||||
↓
|
||||
If denied (403):
|
||||
RBAC error returned to plugin
|
||||
↓
|
||||
Plugin displays error with RBAC guidance
|
||||
```
|
||||
|
||||
## 6. Settings Persistence Flow
|
||||
|
||||
```
|
||||
User navigates to Settings → Plugins → Polaris
|
||||
↓
|
||||
PolarisSettings component mounts
|
||||
↓
|
||||
Component reads localStorage:
|
||||
• polaris-plugin-refresh-interval
|
||||
• polaris-plugin-dashboard-url
|
||||
↓
|
||||
Form populated with current values
|
||||
↓
|
||||
User modifies settings (refresh interval, dashboard URL)
|
||||
↓
|
||||
User clicks "Save"
|
||||
↓
|
||||
Settings written to localStorage:
|
||||
localStorage.setItem('polaris-plugin-refresh-interval', value)
|
||||
localStorage.setItem('polaris-plugin-dashboard-url', url)
|
||||
↓
|
||||
Success message displayed
|
||||
↓
|
||||
Context refreshes data with new interval
|
||||
↓
|
||||
All plugin views use new settings immediately
|
||||
```
|
||||
|
||||
## 7. App Bar Badge Flow
|
||||
|
||||
```
|
||||
Headlamp renders app bar
|
||||
↓
|
||||
Plugin's registerAppBarAction called
|
||||
↓
|
||||
AppBarScoreBadge component rendered in app bar
|
||||
↓
|
||||
Component uses usePolarisDataContext()
|
||||
↓
|
||||
Data fetched from Polaris (shared with views)
|
||||
↓
|
||||
Score computed: (pass / total) * 100
|
||||
↓
|
||||
Badge color determined:
|
||||
• Green: score ≥ 80
|
||||
• Yellow: score 50-79
|
||||
• Red: score < 50
|
||||
↓
|
||||
Badge rendered with score and shield icon
|
||||
↓
|
||||
User clicks badge
|
||||
↓
|
||||
Navigate to /polaris (overview page)
|
||||
```
|
||||
|
||||
## 8. Inline Audit Section Flow
|
||||
|
||||
```
|
||||
User views Deployment/StatefulSet detail page
|
||||
↓
|
||||
Headlamp calls registered details view sections
|
||||
↓
|
||||
Plugin's InlineAuditSection component rendered
|
||||
↓
|
||||
Component receives resource metadata from Headlamp
|
||||
↓
|
||||
Component uses usePolarisDataContext()
|
||||
↓
|
||||
Filters audit results by:
|
||||
• Namespace === resource.namespace
|
||||
• Kind === resource.kind
|
||||
• Name === resource.name
|
||||
↓
|
||||
If matching audit result found:
|
||||
Extract check counts (pass/warning/danger)
|
||||
↓
|
||||
Render compact audit section:
|
||||
• Score badge
|
||||
• Check counts
|
||||
• Link to full Polaris report
|
||||
↓
|
||||
If no match found:
|
||||
Render "No audit data available" message
|
||||
```
|
||||
|
||||
## Data Structures
|
||||
|
||||
### AuditData Schema
|
||||
|
||||
```typescript
|
||||
interface AuditData {
|
||||
PolarisOutputVersion: string; // "1.0"
|
||||
AuditTime: string; // ISO 8601 timestamp
|
||||
SourceType: string; // "Cluster"
|
||||
SourceName: string; // Cluster identifier
|
||||
DisplayName: string; // Human-readable name
|
||||
ClusterInfo: {
|
||||
Version: string; // K8s version
|
||||
Nodes: number;
|
||||
Pods: number;
|
||||
Namespaces: number;
|
||||
Controllers: number;
|
||||
};
|
||||
Results: Result[]; // Array of resource audit results
|
||||
}
|
||||
|
||||
interface Result {
|
||||
Name: string; // Resource name
|
||||
Namespace: string; // Kubernetes namespace
|
||||
Kind: string; // "Deployment", "StatefulSet", etc.
|
||||
Results: ResultSet; // Resource-level checks
|
||||
PodResult?: {
|
||||
Name: string;
|
||||
Results: ResultSet; // Pod-level checks
|
||||
ContainerResults: {
|
||||
Name: string;
|
||||
Results: ResultSet; // Container-level checks
|
||||
}[];
|
||||
};
|
||||
CreatedTime: string; // ISO 8601 timestamp
|
||||
}
|
||||
|
||||
type ResultSet = Record<string, ResultMessage>;
|
||||
|
||||
interface ResultMessage {
|
||||
ID: string; // Check ID (e.g., "cpuLimitsMissing")
|
||||
Message: string; // Human-readable message
|
||||
Details: string[]; // Additional context
|
||||
Success: boolean; // true = passed, false = failed
|
||||
Severity: 'ignore' | 'warning' | 'danger';
|
||||
Category: string; // "Security", "Efficiency", etc.
|
||||
}
|
||||
```
|
||||
|
||||
### Result Counts
|
||||
|
||||
```typescript
|
||||
interface ResultCounts {
|
||||
total: number; // Total checks performed
|
||||
pass: number; // Checks that passed (Success: true)
|
||||
warning: number; // Failed checks with Severity: "warning"
|
||||
danger: number; // Failed checks with Severity: "danger"
|
||||
skipped: number; // Failed checks with Severity: "ignore"
|
||||
}
|
||||
```
|
||||
|
||||
## Data Transformations
|
||||
|
||||
### 1. Aggregating Counts
|
||||
|
||||
```typescript
|
||||
// Input: AuditData.Results[]
|
||||
// Output: ResultCounts
|
||||
|
||||
function countResults(data: AuditData): ResultCounts {
|
||||
const counts = { total: 0, pass: 0, warning: 0, danger: 0, skipped: 0 };
|
||||
|
||||
for (const result of data.Results) {
|
||||
// Count resource-level checks
|
||||
countResultSet(result.Results, counts);
|
||||
|
||||
// Count pod-level checks
|
||||
if (result.PodResult) {
|
||||
countResultSet(result.PodResult.Results, counts);
|
||||
|
||||
// Count container-level checks
|
||||
for (const container of result.PodResult.ContainerResults) {
|
||||
countResultSet(container.Results, counts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return counts;
|
||||
}
|
||||
|
||||
function countResultSet(rs: ResultSet, counts: ResultCounts): void {
|
||||
for (const key in rs) {
|
||||
const msg = rs[key];
|
||||
counts.total++;
|
||||
if (msg.Success) {
|
||||
counts.pass++;
|
||||
} else if (msg.Severity === 'ignore') {
|
||||
counts.skipped++;
|
||||
} else if (msg.Severity === 'warning') {
|
||||
counts.warning++;
|
||||
} else if (msg.Severity === 'danger') {
|
||||
counts.danger++;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Computing Score
|
||||
|
||||
```typescript
|
||||
// Input: ResultCounts
|
||||
// Output: Score (0-100)
|
||||
|
||||
function computeScore(counts: ResultCounts): number {
|
||||
if (counts.total === 0) return 0;
|
||||
return Math.round((counts.pass / counts.total) * 100);
|
||||
}
|
||||
|
||||
// Examples:
|
||||
// { total: 100, pass: 90, ... } → 90
|
||||
// { total: 100, pass: 50, ... } → 50
|
||||
// { total: 0, pass: 0, ... } → 0
|
||||
```
|
||||
|
||||
### 3. Filtering by Namespace
|
||||
|
||||
```typescript
|
||||
// Input: AuditData, namespace string
|
||||
// Output: Result[] for that namespace
|
||||
|
||||
function filterResultsByNamespace(data: AuditData, namespace: string): Result[] {
|
||||
return data.Results.filter(r => r.Namespace === namespace);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Extracting Namespaces
|
||||
|
||||
```typescript
|
||||
// Input: AuditData
|
||||
// Output: Sorted array of unique namespace names
|
||||
|
||||
function getNamespaces(data: AuditData): string[] {
|
||||
const namespaces = new Set<string>();
|
||||
for (const result of data.Results) {
|
||||
if (result.Namespace) {
|
||||
namespaces.add(result.Namespace);
|
||||
}
|
||||
}
|
||||
return Array.from(namespaces).sort();
|
||||
}
|
||||
```
|
||||
|
||||
## Caching Strategy
|
||||
|
||||
**Current Implementation:**
|
||||
|
||||
- Data fetched once and stored in React Context
|
||||
- Shared across all plugin views (no duplicate fetches)
|
||||
- Cached until manual refresh or auto-refresh interval
|
||||
|
||||
**Cache Invalidation:**
|
||||
|
||||
- Manual refresh button click
|
||||
- Auto-refresh interval elapses
|
||||
- Settings change (dashboard URL)
|
||||
|
||||
**No Persistence:**
|
||||
|
||||
- Data NOT persisted to localStorage
|
||||
- Each browser session fetches fresh data
|
||||
- No offline mode
|
||||
|
||||
**Future Enhancement:**
|
||||
|
||||
- IndexedDB caching for offline access
|
||||
- Incremental updates (fetch only changed namespaces)
|
||||
- Service Worker for background refresh
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Architecture Overview](overview.md)** - High-level component hierarchy
|
||||
- **[Design Decisions](design-decisions.md)** - Key architectural choices
|
||||
- **[ADRs](adr/README.md)** - Formal Architecture Decision Records
|
||||
|
||||
## References
|
||||
|
||||
- [Polaris API Documentation](https://polaris.docs.fairwinds.com/)
|
||||
- [React Context API](https://react.dev/reference/react/useContext)
|
||||
- [Headlamp ApiProxy](https://headlamp.dev/docs/latest/development/api/)
|
||||
@@ -0,0 +1,296 @@
|
||||
# Design Decisions
|
||||
|
||||
Key architectural choices and their rationale for the Headlamp Polaris Plugin.
|
||||
|
||||
## 1. Service Proxy vs. Direct Access
|
||||
|
||||
**Decision:** Use Kubernetes service proxy, not direct ClusterIP access
|
||||
|
||||
**Context:**
|
||||
|
||||
- Plugin needs to access Polaris dashboard API
|
||||
- Two options: Direct ClusterIP access or Kubernetes service proxy
|
||||
-Headlamp already has K8s API credentials
|
||||
|
||||
**Decision:**
|
||||
Use service proxy path: `/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Headlamp already has K8s API credentials (service account or user token)
|
||||
- Service proxy leverages existing RBAC (no new credentials needed)
|
||||
- Works with Headlamp's token auth and OIDC
|
||||
- Simpler deployment (no additional network policies for plugin)
|
||||
- Consistent with Headlamp's architecture (all API calls go through K8s API)
|
||||
|
||||
**Trade-offs:**
|
||||
|
||||
- ✅ **Pros:** Simpler RBAC, works with user tokens, no new credentials
|
||||
- ❌ **Cons:** Longer URL path, requires `services/proxy` permission
|
||||
|
||||
**Alternatives Considered:**
|
||||
|
||||
- Direct ClusterIP access → Rejected (requires new credentials, network policies)
|
||||
- External Polaris URL → Supported as optional feature (custom URL setting)
|
||||
|
||||
## 2. React Context vs. Redux/Zustand
|
||||
|
||||
**Decision:** Use React Context for state management
|
||||
|
||||
**Context:**
|
||||
|
||||
- Plugin needs to share Polaris audit data across multiple views
|
||||
- Options: React Context, Redux, Zustand, or component props
|
||||
|
||||
**Decision:**
|
||||
Use React Context with `PolarisDataProvider`
|
||||
|
||||
**Rationale:**
|
||||
|
||||
1. **Simple state:** Single AuditData object, no complex mutations
|
||||
2. **Read-only:** No transactions, undo/redo, or optimistic updates
|
||||
3. **Headlamp constraints:** Cannot add external dependencies (Redux not bundled)
|
||||
4. **Performance:** Data changes infrequently (5-30 minute refresh interval)
|
||||
|
||||
**Trade-offs:**
|
||||
|
||||
- ✅ **Pros:** No dependencies, simple API, built-in React feature
|
||||
- ❌ **Cons:** All consumers re-render on data change (acceptable for infrequent updates)
|
||||
|
||||
**Alternatives Considered:**
|
||||
|
||||
- Redux → Rejected (not available in plugin environment)
|
||||
- Zustand → Rejected (requires external dependency)
|
||||
- Component props → Rejected (prop drilling, duplicate fetches)
|
||||
|
||||
## 3. Drawer Navigation vs. Dedicated Routes
|
||||
|
||||
**Decision:** Use drawer for namespace detail, not dedicated route
|
||||
|
||||
**Context:**
|
||||
|
||||
- Namespaces list needs drill-down to per-namespace detail
|
||||
- Options: Dedicated route (`/polaris/ns/:namespace`) or drawer overlay
|
||||
|
||||
**Decision:**
|
||||
Use drawer with URL hash (`/polaris/namespaces#kube-system`)
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- **Better UX:** Drawer overlays table, preserves scroll position and context
|
||||
- **URL hash:** Preserves navigation state, supports browser back/forward
|
||||
- **Keyboard shortcuts:** Escape key to close drawer
|
||||
- **Sidebar limitation:** Headlamp sidebar doesn't support 3-level nesting
|
||||
|
||||
**Trade-offs:**
|
||||
|
||||
- ✅ **Pros:** Better UX, preserves context, keyboard navigation
|
||||
- ❌ **Cons:** Hash-based routing (not "true" route), drawer accessibility considerations
|
||||
|
||||
**Alternatives Considered:**
|
||||
|
||||
- Dedicated route → Rejected (loses table context, requires back navigation)
|
||||
- Modal → Rejected (less natural for drill-down, no URL state)
|
||||
|
||||
## 4. No MUI Direct Imports
|
||||
|
||||
**Decision:** Never import from `@mui/material` or `@mui/icons-material`
|
||||
|
||||
**Context:**
|
||||
|
||||
- Plugin needs UI components (buttons, icons, etc.)
|
||||
- Headlamp uses MUI but doesn't expose full library to plugins
|
||||
|
||||
**Decision:**
|
||||
Use only Headlamp CommonComponents or HTML elements with inline styles
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Importing MUI causes `createSvgIcon undefined` runtime error
|
||||
- Headlamp plugin environment provides limited MUI exports
|
||||
- CommonComponents cover 90% of use cases
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Use `StatusLabel`, `SectionBox`, `SimpleTable` from CommonComponents
|
||||
- Use standard HTML elements (`<button>`, `<div>`) with inline styles
|
||||
- Use theme-aware CSS variables (`--mui-palette-background-paper`)
|
||||
|
||||
**Trade-offs:**
|
||||
|
||||
- ✅ **Pros:** No runtime errors, smaller bundle, consistent with Headlamp
|
||||
- ❌ **Cons:** Limited component variety, inline styles verbose
|
||||
|
||||
**Alternatives Considered:**
|
||||
|
||||
- Bundle full MUI → Rejected (huge bundle size, version conflicts)
|
||||
- Use Headlamp's MUI exports → Rejected (incomplete, undocumented)
|
||||
|
||||
## 5. Two-Level Sidebar Nesting
|
||||
|
||||
**Decision:** Sidebar has "Polaris" → "Overview" and "Namespaces" (2 levels max)
|
||||
|
||||
**Context:**
|
||||
|
||||
- Plugin needs hierarchical navigation
|
||||
- Headlamp sidebar supports limited nesting depth
|
||||
|
||||
**Decision:**
|
||||
Use 2-level sidebar: `Polaris` (parent) → `Overview`, `Namespaces` (children)
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Headlamp sidebar `Collapse` component only supports 2 levels
|
||||
- Deeper nesting (Polaris → Namespaces → <each namespace>) doesn't work
|
||||
- Sidebar collapse is route-based, not click-to-toggle
|
||||
|
||||
**Workaround:**
|
||||
|
||||
- Namespace navigation via table (NamespacesListView)
|
||||
- Clickable namespace buttons open drawer (not new route)
|
||||
|
||||
**Trade-offs:**
|
||||
|
||||
- ✅ **Pros:** Works within Headlamp constraints
|
||||
- ❌ **Cons:** Can't have dynamic per-namespace sidebar entries
|
||||
|
||||
**Alternatives Considered:**
|
||||
|
||||
- Dynamic sidebar with namespace entries → Rejected (Headlamp limitation)
|
||||
- Flat sidebar (no nesting) → Rejected (poor UX for plugin with multiple views)
|
||||
|
||||
## 6. TypeScript Strict Mode
|
||||
|
||||
**Decision:** Enable all TypeScript strict checks
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictPropertyInitialization": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Catch errors at compile time (not runtime)
|
||||
- Better IDE support and autocomplete
|
||||
- Enforces type safety (no `any`, no implicit unknowns)
|
||||
- Easier refactoring (type errors surface immediately)
|
||||
|
||||
**Trade-offs:**
|
||||
|
||||
- ✅ **Pros:** Fewer runtime errors, better maintainability, self-documenting code
|
||||
- ❌ **Cons:** More verbose code, steeper learning curve
|
||||
|
||||
## 7. Auto-Refresh Default: 5 Minutes
|
||||
|
||||
**Decision:** Default refresh interval is 5 minutes (configurable 1-30 min)
|
||||
|
||||
**Context:**
|
||||
|
||||
- Plugin needs to refresh Polaris data periodically
|
||||
- Polaris audits typically run every 10-30 minutes
|
||||
|
||||
**Decision:**
|
||||
Default to 5 minutes, allow user to configure (1 / 5 / 10 / 30 minutes)
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Balance between data freshness and API load
|
||||
- Polaris audits don't change frequently (10-30 min intervals)
|
||||
- 5 minutes provides reasonably fresh data without excessive API calls
|
||||
|
||||
**Trade-offs:**
|
||||
|
||||
- ✅ **Pros:** Reasonable default, user-configurable, low API load
|
||||
- ❌ **Cons:** Not real-time (acceptable for audit data)
|
||||
|
||||
**Alternatives Considered:**
|
||||
|
||||
- WebSocket/SSE for real-time → Rejected (Polaris dashboard doesn't support)
|
||||
- 1 minute default → Rejected (unnecessary API calls, audit data changes slowly)
|
||||
- 30 minute default → Rejected (too stale for interactive dashboard)
|
||||
|
||||
## 8. Read-Only Plugin
|
||||
|
||||
**Decision:** Plugin is read-only (no write operations)
|
||||
|
||||
**Context:**
|
||||
|
||||
- Plugin could potentially modify Polaris configuration or add exemptions
|
||||
- Write operations require additional RBAC permissions (PATCH, CREATE)
|
||||
|
||||
**Decision:**
|
||||
Plugin only performs GET requests (read-only)
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- **Security:** Minimal RBAC footprint (`get` on `services/proxy` only)
|
||||
- **Simplicity:** No mutation logic, error handling for writes, or rollback
|
||||
- **Polaris design:** Exemptions managed via annotations (outside plugin scope)
|
||||
- **Future:** Can add writes later if user demand exists
|
||||
|
||||
**Trade-offs:**
|
||||
|
||||
- ✅ **Pros:** Minimal permissions, simpler code, fewer failure modes
|
||||
- ❌ **Cons:** Cannot add exemptions via UI (must edit annotations manually)
|
||||
|
||||
**Future Enhancement:**
|
||||
|
||||
- Add PATCH permission for workload annotations
|
||||
- Implement `ExemptionManager` component (UI exists, not integrated)
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### 1. Sidebar Nesting Depth
|
||||
|
||||
**Limitation:** Headlamp sidebar supports only 2 levels
|
||||
|
||||
**Impact:** Cannot have dynamic per-namespace sidebar entries
|
||||
|
||||
**Workaround:** Use table with drawer navigation
|
||||
|
||||
### 2. Skipped Checks Visibility
|
||||
|
||||
**Limitation:** Skipped checks (annotation-based exemptions) not fully counted
|
||||
|
||||
**Reason:** Polaris API omits exempted checks from `results.json`
|
||||
|
||||
**Impact:** "Skipped" count only reflects checks with `Severity: "ignore"`
|
||||
|
||||
**Documented:** README, tooltip on skipped count, KNOWN_LIMITATIONS section
|
||||
|
||||
### 3. No Real-Time Updates
|
||||
|
||||
**Limitation:** Data refreshes on interval (1-30 min), not real-time
|
||||
|
||||
**Reason:** Polaris dashboard doesn't support WebSocket/SSE
|
||||
|
||||
**Workaround:** Manual refresh button, configurable interval
|
||||
|
||||
### 4. Single Cluster Support
|
||||
|
||||
**Limitation:** Plugin shows data for current cluster only
|
||||
|
||||
**Reason:** Headlamp's multi-cluster support is route-based (`/c/<cluster>/...`)
|
||||
|
||||
**Impact:** Must switch clusters in Headlamp to see different cluster's data
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Architecture Overview](overview.md)** - High-level component hierarchy
|
||||
- **[Data Flow](data-flow.md)** - Detailed data flow sequences
|
||||
- **[ADRs](adr/README.md)** - Formal Architecture Decision Records
|
||||
|
||||
## References
|
||||
|
||||
- [Headlamp Plugin Constraints](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
- [React Context Performance](https://react.dev/reference/react/useContext#optimizing-re-renders-when-passing-objects-and-functions)
|
||||
- [TypeScript Strict Mode](https://www.typescriptlang.org/tsconfig#strict)
|
||||
@@ -0,0 +1,333 @@
|
||||
# Architecture Overview
|
||||
|
||||
High-level architecture of the Headlamp Polaris Plugin.
|
||||
|
||||
## Overview
|
||||
|
||||
The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds Polaris audit results within the Headlamp UI. It fetches data from the Polaris dashboard API via the Kubernetes service proxy and presents it in a hierarchical navigation structure.
|
||||
|
||||
**Key Characteristics:**
|
||||
|
||||
- **Read-only:** No write operations to cluster or Polaris
|
||||
- **Service proxy based:** Uses K8s API server proxy to reach Polaris
|
||||
- **React Context for state:** Shared data fetch across components
|
||||
- **Headlamp plugin API:** Integrates via official plugin system
|
||||
- **Type-safe:** Full TypeScript with strict mode
|
||||
|
||||
## System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Headlamp UI (React) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ App Bar │ │ Sidebar │ │ Routes │ │
|
||||
│ │ (Badge) │ │ (Navigation)│ │ (Views) │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────┼──────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────▼────────┐ │
|
||||
│ │ Plugin Registry │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────▼──────────────┐ │
|
||||
│ │ Polaris Plugin │ │
|
||||
│ ├────────────────────────────┤ │
|
||||
│ │ • registerSidebarEntry │ │
|
||||
│ │ • registerRoute │ │
|
||||
│ │ • registerAppBarAction │ │
|
||||
│ │ • registerPluginSettings │ │
|
||||
│ │ • registerDetailsViewSection│ │
|
||||
│ └─────────────┬──────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────▼──────────────┐ │
|
||||
│ │ PolarisDataContext │ │
|
||||
│ │ (React Context Provider) │ │
|
||||
│ └─────────────┬──────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────┼──────────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ ┌────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
|
||||
│ │Dashboard │ │ Namespaces │ │ Namespace │ │
|
||||
│ │View │ │ ListView │ │ Detail │ │
|
||||
│ └──────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ ApiProxy │
|
||||
│ (Headlamp) │
|
||||
└───────┬────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ Kubernetes │
|
||||
│ API Server │
|
||||
└───────┬────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ Service Proxy │
|
||||
│ /api/v1/ns/ │
|
||||
│ polaris/svcs/ │
|
||||
│ polaris- │
|
||||
│ dashboard/ │
|
||||
│ proxy/ │
|
||||
└───────┬────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ Polaris │
|
||||
│ Dashboard │
|
||||
│ (ClusterIP) │
|
||||
└───────┬────────┘
|
||||
│
|
||||
┌───────▼────────┐
|
||||
│ results.json │
|
||||
│ (AuditData) │
|
||||
└────────────────┘
|
||||
```
|
||||
|
||||
## Component Hierarchy
|
||||
|
||||
### Plugin Entry Point
|
||||
|
||||
**`src/index.tsx`**
|
||||
|
||||
- Registers sidebar entries (Polaris → Overview, Namespaces)
|
||||
- Registers routes (`/polaris`, `/polaris/namespaces`)
|
||||
- Registers app bar action (score badge)
|
||||
- Registers plugin settings page
|
||||
- Registers details view section (inline audit)
|
||||
|
||||
### Data Layer
|
||||
|
||||
**`src/api/PolarisDataContext.tsx`**
|
||||
|
||||
- React Context Provider for shared data
|
||||
- Fetches AuditData from Polaris dashboard
|
||||
- Handles auto-refresh based on user settings
|
||||
- Provides `{ data, loading, error, refresh }` to consumers
|
||||
|
||||
**`src/api/polaris.ts`**
|
||||
|
||||
- TypeScript types for AuditData schema
|
||||
- Utility functions: `countResults()`, `computeScore()`
|
||||
- Settings management: `getRefreshInterval()`, `setRefreshInterval()`
|
||||
- Constants: `DASHBOARD_URL_DEFAULT`, `INTERVAL_OPTIONS`
|
||||
|
||||
**`src/api/checkMapping.ts`**
|
||||
|
||||
- Maps Polaris check IDs to human-readable names
|
||||
- Used for display in UI (e.g., "hostIPCSet" → "Host IPC")
|
||||
|
||||
**`src/api/topIssues.ts`**
|
||||
|
||||
- Aggregates failing checks across cluster
|
||||
- Groups by check ID and severity
|
||||
- Used for top issues dashboard
|
||||
|
||||
### View Components
|
||||
|
||||
**`src/components/DashboardView.tsx`**
|
||||
|
||||
- **Route:** `/polaris`
|
||||
- **Purpose:** Cluster-wide overview
|
||||
- **Features:**
|
||||
- Cluster score (percentage)
|
||||
- Check distribution (pass/warning/danger/skipped)
|
||||
- Cluster info (Polaris version, last audit time)
|
||||
- Refresh button
|
||||
- **Data:** Uses `usePolarisDataContext()`
|
||||
|
||||
**`src/components/NamespacesListView.tsx`**
|
||||
|
||||
- **Route:** `/polaris/namespaces`
|
||||
- **Purpose:** List all namespaces with scores
|
||||
- **Features:**
|
||||
- Table with namespace, score, pass/warning/danger counts
|
||||
- Clickable namespace buttons (opens drawer)
|
||||
- Sorted by score (lowest first)
|
||||
- **Data:** Uses `usePolarisDataContext()`, aggregates by namespace
|
||||
|
||||
**`src/components/NamespaceDetailView.tsx`**
|
||||
|
||||
- **Route:** Drawer on `/polaris/namespaces#<namespace>`
|
||||
- **Purpose:** Namespace-level drill-down
|
||||
- **Features:**
|
||||
- Namespace score
|
||||
- Resource table (kind, name, score, counts)
|
||||
- URL hash navigation
|
||||
- Keyboard shortcuts (Escape to close)
|
||||
- **Data:** Filters `usePolarisDataContext()` by namespace
|
||||
|
||||
### UI Components
|
||||
|
||||
**`src/components/AppBarScoreBadge.tsx`**
|
||||
|
||||
- **Location:** Headlamp app bar (top-right)
|
||||
- **Purpose:** Quick cluster score visibility
|
||||
- **Features:**
|
||||
- Color-coded badge (green ≥80%, orange ≥50%, red <50%)
|
||||
- Clickable (navigates to `/polaris`)
|
||||
- Shield emoji icon
|
||||
- **Data:** Uses `usePolarisDataContext()`
|
||||
|
||||
**`src/components/PolarisSettings.tsx`**
|
||||
|
||||
- **Location:** Settings → Plugins → Polaris
|
||||
- **Purpose:** Plugin configuration
|
||||
- **Features:**
|
||||
- Refresh interval selector (1 min to 30 min)
|
||||
- Dashboard URL input (custom Polaris instances)
|
||||
- Connection test button
|
||||
- **Data:** localStorage for persistence
|
||||
|
||||
**`src/components/InlineAuditSection.tsx`**
|
||||
|
||||
- **Location:** Resource detail pages (Deployment, StatefulSet, etc.)
|
||||
- **Purpose:** Show Polaris audit inline
|
||||
- **Features:**
|
||||
- Pass/warning/danger counts
|
||||
- Check details with messages
|
||||
- Severity badges
|
||||
- **Data:** Uses `usePolarisDataContext()`, filters by resource
|
||||
|
||||
**`src/components/ExemptionManager.tsx`**
|
||||
|
||||
- **Location:** (Planned feature, UI exists but not fully integrated)
|
||||
- **Purpose:** Manage Polaris exemptions via annotations
|
||||
- **Features:**
|
||||
- View current exemptions
|
||||
- Add exemptions for failing checks
|
||||
- Remove exemptions
|
||||
|
||||
## State Management
|
||||
|
||||
### Why React Context?
|
||||
|
||||
**Decision:** Use React Context instead of Redux/Zustand
|
||||
|
||||
**Rationale:**
|
||||
|
||||
1. **Simple state:** Single AuditData object shared across views
|
||||
2. **Read-only:** No complex mutations or transactions
|
||||
3. **Headlamp constraints:** Plugin cannot add dependencies (Redux not bundled)
|
||||
4. **Performance:** Data changes infrequently (refresh interval 1-30 min)
|
||||
|
||||
### Context Structure
|
||||
|
||||
```typescript
|
||||
interface PolarisDataContextValue {
|
||||
data: AuditData | null; // Audit results or null if loading/error
|
||||
loading: boolean; // True during initial fetch
|
||||
error: string | null; // Error message if fetch failed
|
||||
refresh: () => void; // Manual refresh function
|
||||
}
|
||||
```
|
||||
|
||||
### Data Fetching Strategy
|
||||
|
||||
1. **Initial fetch:** On first mount of any component using the context
|
||||
2. **Auto-refresh:** Based on user setting (default 5 minutes)
|
||||
3. **Manual refresh:** Via refresh button in UI
|
||||
4. **Caching:** Data persists in context until refresh (no per-route refetch)
|
||||
|
||||
### localStorage Usage
|
||||
|
||||
Settings persisted in localStorage:
|
||||
|
||||
- **`polaris-plugin-refresh-interval`**: Number (seconds), default 300
|
||||
- **`polaris-plugin-dashboard-url`**: String, default service proxy path
|
||||
|
||||
No sensitive data stored in localStorage.
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Headlamp Plugin API
|
||||
|
||||
**Version:** ≥ v0.13.0
|
||||
|
||||
**Registration Functions Used:**
|
||||
|
||||
```typescript
|
||||
// Sidebar navigation
|
||||
registerSidebarEntry({ parent, name, label, url, icon });
|
||||
|
||||
// Routes
|
||||
registerRoute({ path, sidebar, name, exact, component });
|
||||
|
||||
// App bar actions
|
||||
registerAppBarAction(component);
|
||||
|
||||
// Plugin settings
|
||||
registerPluginSettings(name, component, displaySaveButton);
|
||||
|
||||
// Resource detail sections
|
||||
registerDetailsViewSection(component);
|
||||
```
|
||||
|
||||
**Key Changes in v0.13.0:**
|
||||
|
||||
- `registerDetailsViewSection` now takes 1 argument (component), not 2 (name, component)
|
||||
- `registerAppBarAction` now takes 1 argument (component), not 2 (name, component)
|
||||
|
||||
### Headlamp CommonComponents
|
||||
|
||||
**Used Components:**
|
||||
|
||||
- `SectionBox` - Card-like container with title
|
||||
- `SectionHeader` - Page header with title
|
||||
- `StatusLabel` - Color-coded status badges
|
||||
- `NameValueTable` - Key-value table layout
|
||||
- `SimpleTable` - Data table with sorting
|
||||
- `Drawer` - Right-side overlay panel
|
||||
- `Loader` - Loading spinner
|
||||
|
||||
**Router:**
|
||||
|
||||
- `Router.createRouteURL()` - Generate plugin route URLs
|
||||
- React Router's `useHistory()`, `useParams()`, `useLocation()`
|
||||
|
||||
### Kubernetes API (via ApiProxy)
|
||||
|
||||
**Used for:**
|
||||
|
||||
- Fetching Polaris results: `ApiProxy.request(dashboardUrl + 'results.json')`
|
||||
- No direct K8s API calls (all data from Polaris dashboard)
|
||||
|
||||
**RBAC Required:**
|
||||
|
||||
- `get` on `services/proxy` for `polaris-dashboard` in `polaris` namespace
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Bundle Size
|
||||
|
||||
- **Current:** ~27 KB minified (gzip: ~7.6 KB)
|
||||
- **Target:** Keep under 50 KB to ensure fast loading
|
||||
- **Strategy:** No heavy dependencies, tree-shaking enabled
|
||||
|
||||
### Data Fetching
|
||||
|
||||
- **Lazy loading:** Data not fetched until user navigates to plugin
|
||||
- **Caching:** Single fetch shared across all views (React Context)
|
||||
- **Refresh strategy:** User-controlled interval prevents excessive API calls
|
||||
|
||||
### Rendering
|
||||
|
||||
- **React.memo:** Not needed (data changes infrequently)
|
||||
- **Virtual scrolling:** Not needed (namespace/resource lists typically <100 items)
|
||||
- **Component splitting:** Lazy load views if bundle grows significantly
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Data Flow](data-flow.md)** - Detailed data flow diagrams and sequences
|
||||
- **[Design Decisions](design-decisions.md)** - Architecture decision records
|
||||
- **[ADRs](adr/README.md)** - Formal Architecture Decision Records
|
||||
|
||||
## References
|
||||
|
||||
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
- [Fairwinds Polaris Documentation](https://polaris.docs.fairwinds.com/)
|
||||
- [React Context API](https://react.dev/reference/react/useContext)
|
||||
- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/)
|
||||
@@ -0,0 +1,432 @@
|
||||
# Helm Deployment
|
||||
|
||||
Deploy the Headlamp Polaris Plugin using Helm charts.
|
||||
|
||||
## Overview
|
||||
|
||||
Helm provides the easiest way to deploy and manage the plugin in production. This guide covers:
|
||||
|
||||
- Helm values configuration
|
||||
- Plugin Manager integration
|
||||
- FluxCD HelmRelease integration
|
||||
- Upgrade procedures
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Helm v3+ installed
|
||||
- Kubernetes cluster access
|
||||
- Headlamp Helm repository added
|
||||
|
||||
```bash
|
||||
# Add Headlamp Helm repository
|
||||
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
|
||||
helm repo update
|
||||
```
|
||||
|
||||
## Basic Helm Installation
|
||||
|
||||
### Minimal Configuration
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
repositories:
|
||||
- https://artifacthub.io/packages/search?kind=4
|
||||
```
|
||||
|
||||
```bash
|
||||
# Install Headlamp
|
||||
helm install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
|
||||
# Wait for deployment
|
||||
kubectl -n kube-system wait --for=condition=available deployment/headlamp --timeout=300s
|
||||
```
|
||||
|
||||
After installation, install the plugin via Headlamp UI (**Settings → Plugins → Catalog**).
|
||||
|
||||
## Complete Production Configuration
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
repository: ghcr.io/headlamp-k8s/headlamp
|
||||
tag: v0.39.0
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
config:
|
||||
baseURL: ''
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
repositories:
|
||||
- https://artifacthub.io/packages/search?kind=4
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
|
||||
hosts:
|
||||
- host: headlamp.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: headlamp-tls
|
||||
hosts:
|
||||
- headlamp.example.com
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: headlamp
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
# OIDC Authentication (optional)
|
||||
env:
|
||||
- name: HEADLAMP_CONFIG_OIDC_CLIENT_ID
|
||||
value: 'headlamp'
|
||||
- name: HEADLAMP_CONFIG_OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: headlamp-oidc
|
||||
key: client-secret
|
||||
- name: HEADLAMP_CONFIG_OIDC_ISSUER_URL
|
||||
value: 'https://auth.example.com/realms/kubernetes'
|
||||
- name: HEADLAMP_CONFIG_OIDC_SCOPES
|
||||
value: 'openid,profile,email,groups'
|
||||
```
|
||||
|
||||
Deploy:
|
||||
|
||||
```bash
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml \
|
||||
--wait \
|
||||
--timeout 5m
|
||||
```
|
||||
|
||||
## Sidecar Plugin Installation Method
|
||||
|
||||
Alternative to Plugin Manager: use an init container to download the plugin.
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
initContainers:
|
||||
- name: install-polaris-plugin
|
||||
image: node:lts-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
npm install -g @kinvolk/headlamp-plugin
|
||||
headlamp-plugin install --config /config/plugin.yml --plugins-dir /plugins
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /plugins
|
||||
- name: plugin-config
|
||||
mountPath: /config
|
||||
|
||||
volumes:
|
||||
- name: plugins
|
||||
emptyDir: {}
|
||||
- name: plugin-config
|
||||
configMap:
|
||||
name: headlamp-plugin-config
|
||||
```
|
||||
|
||||
Create the ConfigMap:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-plugin-config
|
||||
namespace: kube-system
|
||||
data:
|
||||
plugin.yml: |
|
||||
- name: headlamp-polaris-plugin
|
||||
version: 0.3.5
|
||||
url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
Apply ConfigMap then deploy Headlamp:
|
||||
|
||||
```bash
|
||||
kubectl apply -f headlamp-plugin-config.yaml
|
||||
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
## FluxCD HelmRelease Integration
|
||||
|
||||
For GitOps workflows with FluxCD:
|
||||
|
||||
### HelmRepository
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 1h
|
||||
url: https://headlamp-k8s.github.io/headlamp/
|
||||
```
|
||||
|
||||
### HelmRelease
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
spec:
|
||||
interval: 30m
|
||||
chart:
|
||||
spec:
|
||||
chart: headlamp
|
||||
version: 0.26.x # Use semver range
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: headlamp
|
||||
namespace: flux-system
|
||||
interval: 12h
|
||||
|
||||
install:
|
||||
crds: CreateReplace
|
||||
remediation:
|
||||
retries: 3
|
||||
|
||||
upgrade:
|
||||
crds: CreateReplace
|
||||
remediation:
|
||||
retries: 3
|
||||
|
||||
values:
|
||||
replicaCount: 2
|
||||
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
repositories:
|
||||
- https://artifacthub.io/packages/search?kind=4
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: headlamp.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
# Health checks
|
||||
postRenderers:
|
||||
- kustomize:
|
||||
patches:
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: headlamp
|
||||
patch: |
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/livenessProbe
|
||||
value:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
```
|
||||
|
||||
Apply FluxCD resources:
|
||||
|
||||
```bash
|
||||
kubectl apply -f helmrepository.yaml
|
||||
kubectl apply -f helmrelease.yaml
|
||||
|
||||
# Watch deployment
|
||||
flux get helmreleases -n kube-system --watch
|
||||
```
|
||||
|
||||
## RBAC Configuration
|
||||
|
||||
After deploying Headlamp, apply RBAC for the plugin:
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<EOF
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
EOF
|
||||
```
|
||||
|
||||
See [RBAC Permissions](../user-guide/rbac-permissions.md) for advanced RBAC configurations.
|
||||
|
||||
## Upgrading
|
||||
|
||||
### Upgrade Headlamp
|
||||
|
||||
```bash
|
||||
# Update Helm repo
|
||||
helm repo update
|
||||
|
||||
# Upgrade Headlamp (preserves plugin configuration)
|
||||
helm upgrade headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml \
|
||||
--wait
|
||||
```
|
||||
|
||||
### Upgrade Plugin (Plugin Manager Method)
|
||||
|
||||
1. Navigate to **Settings → Plugins** in Headlamp UI
|
||||
2. Find "headlamp-polaris-plugin"
|
||||
3. Click **Update** if new version available
|
||||
4. Hard refresh browser (**Cmd+Shift+R** / **Ctrl+Shift+R**)
|
||||
|
||||
### Upgrade Plugin (Sidecar Method)
|
||||
|
||||
```bash
|
||||
# Update ConfigMap with new version
|
||||
kubectl -n kube-system edit configmap headlamp-plugin-config
|
||||
|
||||
# Update version and URL:
|
||||
# version: 0.3.6
|
||||
# url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz
|
||||
|
||||
# Restart deployment to trigger init container
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
kubectl -n kube-system rollout status deployment/headlamp
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin Not Loading
|
||||
|
||||
```bash
|
||||
# Check Headlamp values
|
||||
helm get values headlamp -n kube-system
|
||||
|
||||
# Verify plugin files exist
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# If missing, reinstall plugin via UI or check init container logs
|
||||
kubectl -n kube-system logs deployment/headlamp -c install-polaris-plugin
|
||||
```
|
||||
|
||||
### Helm Release Stuck
|
||||
|
||||
```bash
|
||||
# Check Helm release status
|
||||
helm list -n kube-system
|
||||
|
||||
# If stuck, force upgrade
|
||||
helm upgrade headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml \
|
||||
--force \
|
||||
--wait
|
||||
```
|
||||
|
||||
### FluxCD Reconciliation Issues
|
||||
|
||||
```bash
|
||||
# Check HelmRelease status
|
||||
flux get helmreleases -n kube-system
|
||||
|
||||
# Check events
|
||||
kubectl -n kube-system describe helmrelease headlamp
|
||||
|
||||
# Force reconciliation
|
||||
flux reconcile helmrelease headlamp -n kube-system
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Kubernetes Deployment](kubernetes.md)** - Raw Kubernetes manifests
|
||||
- **[Production Checklist](production.md)** - Production deployment best practices
|
||||
- **[Troubleshooting](../troubleshooting/README.md)** - Comprehensive troubleshooting guide
|
||||
|
||||
## References
|
||||
|
||||
- [Headlamp Helm Chart](https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp)
|
||||
- [Helm Documentation](https://helm.sh/docs/)
|
||||
- [FluxCD HelmRelease](https://fluxcd.io/flux/components/helm/helmreleases/)
|
||||
@@ -0,0 +1,489 @@
|
||||
# Kubernetes Deployment
|
||||
|
||||
Direct Kubernetes manifest deployment for the Headlamp Polaris Plugin.
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers deploying the plugin using raw Kubernetes manifests without Helm. This approach is useful for:
|
||||
|
||||
- Environments where Helm is not available
|
||||
- Highly customized deployments
|
||||
- GitOps workflows with Kustomize
|
||||
- Learning the underlying Kubernetes resources
|
||||
|
||||
## RBAC Manifests
|
||||
|
||||
The plugin requires read-only access to the Polaris dashboard service proxy.
|
||||
|
||||
### Complete RBAC Configuration
|
||||
|
||||
```yaml
|
||||
---
|
||||
# Role: Read-only access to Polaris service proxy
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp-polaris-plugin
|
||||
app.kubernetes.io/component: rbac
|
||||
rules:
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
|
||||
---
|
||||
# RoleBinding: Grant Headlamp service account access
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp-polaris-plugin
|
||||
app.kubernetes.io/component: rbac
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
Apply the RBAC manifests:
|
||||
|
||||
```bash
|
||||
kubectl apply -f polaris-plugin-rbac.yaml
|
||||
```
|
||||
|
||||
### RBAC Verification
|
||||
|
||||
```bash
|
||||
# Verify Role exists
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
|
||||
# Verify RoleBinding exists
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected output: yes
|
||||
```
|
||||
|
||||
## Plugin Installation via Init Container
|
||||
|
||||
Use an init container to download and install the plugin.
|
||||
|
||||
### ConfigMap for Plugin Configuration
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-plugin-config
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
app.kubernetes.io/component: plugin-config
|
||||
data:
|
||||
plugin.yml: |
|
||||
- name: headlamp-polaris-plugin
|
||||
version: 0.3.5
|
||||
url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
### Headlamp Deployment with Plugin Init Container
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
spec:
|
||||
serviceAccountName: headlamp
|
||||
|
||||
# Init container to install plugins
|
||||
initContainers:
|
||||
- name: install-plugins
|
||||
image: node:lts-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
npm install -g @kinvolk/headlamp-plugin
|
||||
headlamp-plugin install --config /config/plugin.yml --plugins-dir /plugins
|
||||
echo "Plugin installation complete"
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /plugins
|
||||
- name: plugin-config
|
||||
mountPath: /config
|
||||
|
||||
containers:
|
||||
- name: headlamp
|
||||
image: ghcr.io/headlamp-k8s/headlamp:v0.39.0
|
||||
args:
|
||||
- '-in-cluster'
|
||||
- '-plugins-dir=/headlamp/plugins'
|
||||
env:
|
||||
- name: HEADLAMP_CONFIG_WATCH_PLUGINS
|
||||
value: 'false' # CRITICAL: Must be false for plugin manager
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 4466
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /headlamp/plugins
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
volumes:
|
||||
- name: plugins
|
||||
emptyDir: {}
|
||||
- name: plugin-config
|
||||
configMap:
|
||||
name: headlamp-plugin-config
|
||||
```
|
||||
|
||||
### Supporting Resources
|
||||
|
||||
```yaml
|
||||
---
|
||||
# ServiceAccount for Headlamp
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
|
||||
---
|
||||
# Service for Headlamp
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: headlamp
|
||||
```
|
||||
|
||||
## Complete Deployment Workflow
|
||||
|
||||
### 1. Apply All Manifests
|
||||
|
||||
```bash
|
||||
# Create RBAC for Polaris plugin
|
||||
kubectl apply -f polaris-plugin-rbac.yaml
|
||||
|
||||
# Create plugin configuration
|
||||
kubectl apply -f headlamp-plugin-config.yaml
|
||||
|
||||
# Deploy Headlamp with plugin init container
|
||||
kubectl apply -f headlamp-deployment.yaml
|
||||
kubectl apply -f headlamp-service.yaml
|
||||
kubectl apply -f headlamp-serviceaccount.yaml
|
||||
|
||||
# Wait for deployment to be ready
|
||||
kubectl -n kube-system wait --for=condition=available deployment/headlamp --timeout=300s
|
||||
```
|
||||
|
||||
### 2. Verify Deployment
|
||||
|
||||
```bash
|
||||
# Check pods are running
|
||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
||||
|
||||
# Expected output:
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
|
||||
|
||||
# Check init container logs
|
||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
||||
|
||||
# Expected output:
|
||||
# Plugin installation complete
|
||||
|
||||
# Verify plugin files exist
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
# drwxr-xr-x dist/
|
||||
# -rw-r--r-- package.json
|
||||
|
||||
# Test Polaris API access
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json \
|
||||
| jq .PolarisOutputVersion
|
||||
|
||||
# Expected output: "1.0" or similar
|
||||
```
|
||||
|
||||
### 3. Access Headlamp
|
||||
|
||||
```bash
|
||||
# Port-forward to access locally
|
||||
kubectl -n kube-system port-forward service/headlamp 8080:80
|
||||
|
||||
# Open browser to http://localhost:8080
|
||||
```
|
||||
|
||||
## Kustomize Integration
|
||||
|
||||
For GitOps workflows with Kustomize:
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
k8s/
|
||||
├── base/
|
||||
│ ├── kustomization.yaml
|
||||
│ ├── rbac.yaml
|
||||
│ ├── configmap.yaml
|
||||
│ ├── deployment.yaml
|
||||
│ ├── service.yaml
|
||||
│ └── serviceaccount.yaml
|
||||
└── overlays/
|
||||
├── production/
|
||||
│ ├── kustomization.yaml
|
||||
│ └── patches.yaml
|
||||
└── staging/
|
||||
├── kustomization.yaml
|
||||
└── patches.yaml
|
||||
```
|
||||
|
||||
### Base Kustomization
|
||||
|
||||
```yaml
|
||||
# k8s/base/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: kube-system
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
|
||||
resources:
|
||||
- serviceaccount.yaml
|
||||
- service.yaml
|
||||
- deployment.yaml
|
||||
- configmap.yaml
|
||||
- rbac.yaml
|
||||
|
||||
configMapGenerator:
|
||||
- name: headlamp-plugin-config
|
||||
files:
|
||||
- plugin.yml
|
||||
```
|
||||
|
||||
### Production Overlay
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/production/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
bases:
|
||||
- ../../base
|
||||
|
||||
nameSuffix: -prod
|
||||
|
||||
replicas:
|
||||
- name: headlamp
|
||||
count: 2
|
||||
|
||||
patches:
|
||||
- path: patches.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/production/patches.yaml
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: headlamp
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: headlamp
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
```
|
||||
|
||||
### Deploy with Kustomize
|
||||
|
||||
```bash
|
||||
# Build and preview
|
||||
kubectl kustomize k8s/overlays/production
|
||||
|
||||
# Apply
|
||||
kubectl apply -k k8s/overlays/production
|
||||
```
|
||||
|
||||
## FluxCD Integration
|
||||
|
||||
For GitOps with FluxCD:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: headlamp-polaris-plugin
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 10m
|
||||
path: ./k8s/overlays/production
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: infrastructure
|
||||
healthChecks:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
```
|
||||
|
||||
## Upgrading the Plugin
|
||||
|
||||
### Update ConfigMap
|
||||
|
||||
```bash
|
||||
# Edit ConfigMap with new version
|
||||
kubectl -n kube-system edit configmap headlamp-plugin-config
|
||||
|
||||
# Update version and URL:
|
||||
# version: 0.3.6
|
||||
# url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz
|
||||
|
||||
# Restart deployment to trigger init container
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
|
||||
# Wait for rollout to complete
|
||||
kubectl -n kube-system rollout status deployment/headlamp
|
||||
```
|
||||
|
||||
### Verify Upgrade
|
||||
|
||||
```bash
|
||||
# Check init container logs
|
||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
||||
|
||||
# Verify new version in UI
|
||||
# Navigate to Settings → Plugins in Headlamp
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Init Container Fails
|
||||
|
||||
```bash
|
||||
# Check init container logs
|
||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
||||
|
||||
# Common issues:
|
||||
# 1. Network connectivity to GitHub
|
||||
# 2. Invalid URL in ConfigMap
|
||||
# 3. Tarball checksum mismatch
|
||||
```
|
||||
|
||||
### Plugin Not Loading
|
||||
|
||||
```bash
|
||||
# Verify HEADLAMP_CONFIG_WATCH_PLUGINS is false
|
||||
kubectl -n kube-system get deployment headlamp -o yaml | grep WATCH_PLUGINS
|
||||
|
||||
# Expected output:
|
||||
# - name: HEADLAMP_CONFIG_WATCH_PLUGINS
|
||||
# value: "false"
|
||||
|
||||
# If not set or "true", update deployment
|
||||
kubectl -n kube-system edit deployment headlamp
|
||||
```
|
||||
|
||||
### RBAC Permissions Denied
|
||||
|
||||
```bash
|
||||
# Test RBAC
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# If "no", verify RBAC manifests applied:
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Helm Deployment](helm.md)** - Deploy with Helm for easier management
|
||||
- **[Production Checklist](production.md)** - Production deployment best practices
|
||||
- **[Troubleshooting](../troubleshooting/README.md)** - Comprehensive troubleshooting guide
|
||||
|
||||
## References
|
||||
|
||||
- [Kubernetes Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
|
||||
- [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
|
||||
- [Kustomize Documentation](https://kustomize.io/)
|
||||
- [FluxCD Kustomization](https://fluxcd.io/flux/components/kustomize/kustomization/)
|
||||
@@ -0,0 +1,488 @@
|
||||
# Production Deployment
|
||||
|
||||
Production deployment checklist, best practices, and security considerations for the Headlamp Polaris Plugin.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Pre-Deployment Checklist](#pre-deployment-checklist)
|
||||
- [Production Checklist](#production-checklist)
|
||||
- [Security Best Practices](#security-best-practices)
|
||||
- [High Availability](#high-availability)
|
||||
- [Monitoring and Observability](#monitoring-and-observability)
|
||||
- [Performance Tuning](#performance-tuning)
|
||||
- [Disaster Recovery](#disaster-recovery)
|
||||
- [Known Issues](#known-issues)
|
||||
|
||||
## Pre-Deployment Checklist
|
||||
|
||||
Before deploying to production:
|
||||
|
||||
### Infrastructure
|
||||
|
||||
- [ ] Kubernetes cluster v1.24+ running
|
||||
- [ ] Polaris deployed in `polaris` namespace
|
||||
- [ ] Polaris dashboard service (`polaris-dashboard:80`) accessible
|
||||
- [ ] Headlamp v0.26+ deployed (v0.39+ recommended)
|
||||
- [ ] Ingress controller configured (if exposing externally)
|
||||
- [ ] TLS certificates provisioned (cert-manager recommended)
|
||||
|
||||
### Verification Commands
|
||||
|
||||
```bash
|
||||
# Verify Polaris
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
|
||||
# Test Polaris API
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||
|
||||
# Verify Headlamp
|
||||
kubectl -n kube-system get deployment headlamp
|
||||
kubectl -n kube-system get svc headlamp
|
||||
```
|
||||
|
||||
## Production Checklist
|
||||
|
||||
### Deployment
|
||||
|
||||
- [ ] Plugin installed via Plugin Manager or sidecar init container
|
||||
- [ ] RBAC Role and RoleBinding applied
|
||||
- [ ] NetworkPolicies configured (if using strict network policies)
|
||||
- [ ] Headlamp pods running with 2+ replicas (high availability)
|
||||
- [ ] Resource limits and requests configured
|
||||
|
||||
### Post-Deployment Verification
|
||||
|
||||
```bash
|
||||
# 1. Verify Polaris API is accessible via service proxy
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||
# Expected: "1.0" or similar
|
||||
|
||||
# 2. Verify RBAC permissions
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
# Expected: yes
|
||||
|
||||
# 3. Check Headlamp logs for plugin loading
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
# Expected: No errors related to plugin loading
|
||||
|
||||
# 4. Verify plugin files exist
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
# Expected: dist/, package.json present
|
||||
```
|
||||
|
||||
### UI Verification
|
||||
|
||||
- [ ] Navigate to **Settings → Plugins**
|
||||
- [ ] Verify "headlamp-polaris-plugin" is listed with correct version
|
||||
- [ ] Sidebar shows "Polaris" entry
|
||||
- [ ] Click **Polaris → Overview** - page loads successfully
|
||||
- [ ] Cluster score gauge displays
|
||||
- [ ] Namespaces table loads with data
|
||||
- [ ] App bar shows Polaris score badge
|
||||
- [ ] Click namespace - detail drawer opens
|
||||
- [ ] Test inline audit section on a Deployment/StatefulSet
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### RBAC
|
||||
|
||||
**Principle of Least Privilege:**
|
||||
|
||||
```yaml
|
||||
# ✅ GOOD: Scoped to specific service
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
|
||||
# ❌ BAD: Too broad
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
verbs: ["get"] # Allows proxy to ALL services
|
||||
```
|
||||
|
||||
**Token-Auth Mode:**
|
||||
|
||||
When Headlamp uses user-supplied tokens (OIDC), each user needs the RoleBinding:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: authenticated-users-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: system:authenticated # All authenticated users
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
For fine-grained control, bind specific users or groups:
|
||||
|
||||
```yaml
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: sre-team # Only SRE team
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### Network Policies
|
||||
|
||||
If using strict NetworkPolicies:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-apiserver-to-polaris
|
||||
namespace: polaris
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: polaris
|
||||
app.kubernetes.io/component: dashboard
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow from API server (performs the proxy hop)
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: kube-apiserver
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
```
|
||||
|
||||
**Note:** The API server proxies the request, not the Headlamp pod directly.
|
||||
|
||||
### Audit Logging
|
||||
|
||||
Kubernetes audit logs record every service proxy request:
|
||||
|
||||
- **What's logged:** User/service account, timestamp, response code
|
||||
- **Volume:** Auto-refresh interval affects audit log volume
|
||||
- **Recommendation:** Configure audit policy level if concerned about log volume
|
||||
|
||||
```yaml
|
||||
# audit-policy.yaml
|
||||
apiVersion: audit.k8s.io/v1
|
||||
kind: Policy
|
||||
rules:
|
||||
- level: Metadata # Log metadata only (not full request/response)
|
||||
verbs: ['get']
|
||||
resources:
|
||||
- group: ''
|
||||
resources: ['services/proxy']
|
||||
namespaces: ['polaris']
|
||||
```
|
||||
|
||||
### Data Sensitivity
|
||||
|
||||
Polaris audit data may contain:
|
||||
|
||||
- Resource names and namespaces
|
||||
- Configuration details
|
||||
- Potential security vulnerabilities
|
||||
|
||||
**Recommendation:** Restrict plugin access to authorized users only (not `system:authenticated` unless appropriate).
|
||||
|
||||
## High Availability
|
||||
|
||||
### Headlamp Replicas
|
||||
|
||||
Deploy Headlamp with 2+ replicas for high availability:
|
||||
|
||||
```yaml
|
||||
# helm-values.yaml
|
||||
replicaCount: 2
|
||||
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
```
|
||||
|
||||
### Pod Disruption Budget
|
||||
|
||||
Ensure at least one replica is always available during node maintenance:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: headlamp-pdb
|
||||
namespace: kube-system
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
|
||||
Configure liveness and readiness probes:
|
||||
|
||||
```yaml
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
```
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
### Metrics to Monitor
|
||||
|
||||
**Application Metrics:**
|
||||
|
||||
- Headlamp pod CPU/memory usage
|
||||
- HTTP request latency and error rates
|
||||
- Plugin load time
|
||||
|
||||
**Polaris Metrics:**
|
||||
|
||||
- Polaris dashboard API response time
|
||||
- Service proxy request latency
|
||||
- RBAC denial rate (403 errors)
|
||||
|
||||
### Prometheus Integration
|
||||
|
||||
Example ServiceMonitor for Headlamp:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
endpoints:
|
||||
- port: http
|
||||
interval: 30s
|
||||
path: /metrics
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
**Headlamp Logs:**
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
kubectl -n kube-system logs deployment/headlamp -f
|
||||
|
||||
# Filter for plugin-related logs
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
```
|
||||
|
||||
**Polaris Dashboard Logs:**
|
||||
|
||||
```bash
|
||||
kubectl -n polaris logs deployment/polaris-dashboard -f
|
||||
```
|
||||
|
||||
### Alerts
|
||||
|
||||
Recommended alerts:
|
||||
|
||||
- Headlamp pod not ready
|
||||
- High error rate (4xx/5xx)
|
||||
- Polaris dashboard unavailable
|
||||
- RBAC denials (403 errors)
|
||||
|
||||
Example PrometheusRule:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: headlamp-alerts
|
||||
namespace: kube-system
|
||||
spec:
|
||||
groups:
|
||||
- name: headlamp
|
||||
interval: 30s
|
||||
rules:
|
||||
- alert: HeadlampPodNotReady
|
||||
expr: kube_pod_status_ready{namespace="kube-system", pod=~"headlamp-.*"} == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: 'Headlamp pod not ready'
|
||||
description: 'Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} has been not ready for 5 minutes.'
|
||||
```
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### Plugin Refresh Interval
|
||||
|
||||
The plugin auto-refreshes Polaris data at a configurable interval (default: 5 minutes).
|
||||
|
||||
**Recommendations:**
|
||||
|
||||
- **High-traffic clusters:** 10-30 minutes (reduces API server load)
|
||||
- **Low-traffic clusters:** 1-5 minutes (more real-time data)
|
||||
|
||||
Configure via **Settings → Plugins → Polaris** in Headlamp UI.
|
||||
|
||||
### Browser Caching
|
||||
|
||||
The plugin uses localStorage for settings. Browser cache can affect plugin loading.
|
||||
|
||||
**Best Practice:** Instruct users to hard refresh after plugin updates (**Cmd+Shift+R** / **Ctrl+Shift+R**).
|
||||
|
||||
### Resource Limits
|
||||
|
||||
Recommended resource limits for Headlamp with plugin:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
```
|
||||
|
||||
Adjust based on cluster size and user count.
|
||||
|
||||
## Disaster Recovery
|
||||
|
||||
### Backup Considerations
|
||||
|
||||
**What to back up:**
|
||||
|
||||
- Headlamp Helm values or Kubernetes manifests
|
||||
- RBAC manifests (Role, RoleBinding)
|
||||
- Plugin configuration (ConfigMap if using sidecar method)
|
||||
|
||||
**What NOT to back up:**
|
||||
|
||||
- Plugin tarball (available on GitHub releases)
|
||||
- Polaris audit data (regenerated by Polaris)
|
||||
- Browser localStorage (user-specific settings)
|
||||
|
||||
### Recovery Procedure
|
||||
|
||||
If Headlamp or plugin becomes unavailable:
|
||||
|
||||
1. **Verify Polaris is running:**
|
||||
|
||||
```bash
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
```
|
||||
|
||||
2. **Redeploy Headlamp:**
|
||||
|
||||
```bash
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
3. **Reapply RBAC:**
|
||||
|
||||
```bash
|
||||
kubectl apply -f polaris-plugin-rbac.yaml
|
||||
```
|
||||
|
||||
4. **Verify plugin files:**
|
||||
|
||||
```bash
|
||||
kubectl -n kube-system exec deployment/headlamp -- \
|
||||
ls /headlamp/plugins/headlamp-polaris-plugin/
|
||||
```
|
||||
|
||||
5. **Hard refresh browser:**
|
||||
**Cmd+Shift+R** / **Ctrl+Shift+R**
|
||||
|
||||
## Known Issues
|
||||
|
||||
### Skipped Count Limitation
|
||||
|
||||
**Symptom:** "Skipped" count in UI is lower than native Polaris dashboard
|
||||
|
||||
**Cause:** Plugin only counts checks with `Severity: "ignore"` from API response
|
||||
|
||||
**Explanation:**
|
||||
|
||||
Polaris omits annotation-based exemptions (e.g., `polaris.fairwinds.com/*-exempt`) from the `results.json` endpoint. The native Polaris dashboard computes skipped count by querying raw Kubernetes resources and parsing annotations.
|
||||
|
||||
**Workaround:** Use "View in Polaris Dashboard" link for accurate exemption count.
|
||||
|
||||
**Future Enhancement:** Would require cluster-wide read access to all workload types (significant RBAC expansion).
|
||||
|
||||
### ArtifactHub Sync Delay
|
||||
|
||||
**Symptom:** New plugin version not appearing in Headlamp catalog
|
||||
|
||||
**Cause:** ArtifactHub syncs from GitHub every 30 minutes (no webhook/push mechanism)
|
||||
|
||||
**Solution:** Wait 30 minutes after GitHub release for new version to appear in catalog.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For production issues, see:
|
||||
|
||||
- **[Troubleshooting Guide](../troubleshooting/README.md)** - Comprehensive troubleshooting
|
||||
- **[RBAC Issues](../troubleshooting/rbac-issues.md)** - Permission debugging
|
||||
- **[Network Problems](../troubleshooting/network-problems.md)** - Connectivity issues
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Kubernetes Deployment](kubernetes.md)** - Raw manifest deployment
|
||||
- **[Helm Deployment](helm.md)** - Helm chart deployment
|
||||
- **[Troubleshooting](../troubleshooting/README.md)** - Issue resolution
|
||||
|
||||
## References
|
||||
|
||||
- [Kubernetes Production Best Practices](https://kubernetes.io/docs/setup/best-practices/)
|
||||
- [Headlamp Security](https://headlamp.dev/docs/latest/installation/in-cluster/#security)
|
||||
- [Polaris Configuration](https://polaris.docs.fairwinds.com/customization/checks/)
|
||||
@@ -0,0 +1,703 @@
|
||||
# Testing Guide
|
||||
|
||||
Comprehensive guide to testing the Headlamp Polaris Plugin, covering unit tests, E2E tests, and CI/CD integration.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Unit Testing](#unit-testing)
|
||||
- [E2E Testing](#e2e-testing)
|
||||
- [CI/CD Integration](#cicd-integration)
|
||||
- [Test Coverage](#test-coverage)
|
||||
- [Best Practices](#best-practices)
|
||||
- [Debugging](#debugging)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Headlamp Polaris Plugin uses a multi-layered testing approach:
|
||||
|
||||
| Test Type | Framework | Purpose | Location |
|
||||
| ----------------- | ---------- | ------------------------------------------------------- | ----------------------- |
|
||||
| **Unit Tests** | Vitest | Test individual functions and components in isolation | `src/**/*.test.ts(x)` |
|
||||
| **E2E Tests** | Playwright | Test complete user flows against live Headlamp instance | `e2e/*.spec.ts` |
|
||||
| **Type Checking** | TypeScript | Ensure type safety across codebase | `tsc --noEmit` |
|
||||
| **Linting** | ESLint | Enforce code style and catch common errors | `eslint src/` |
|
||||
| **Formatting** | Prettier | Maintain consistent code formatting | `prettier --check src/` |
|
||||
|
||||
### Test Philosophy
|
||||
|
||||
1. **Unit tests** focus on business logic (data parsing, score calculation, filtering)
|
||||
2. **E2E tests** validate user-facing functionality (navigation, rendering, interactions)
|
||||
3. **Both** run automatically in CI on every commit
|
||||
4. **Coverage** targets meaningful tests, not arbitrary percentages
|
||||
|
||||
---
|
||||
|
||||
## Unit Testing
|
||||
|
||||
### Framework: Vitest
|
||||
|
||||
Vitest is a fast, modern testing framework compatible with Jest APIs but optimized for Vite-based projects.
|
||||
|
||||
### Running Unit Tests
|
||||
|
||||
```bash
|
||||
# Run all unit tests
|
||||
npm test
|
||||
|
||||
# Run in watch mode (re-runs on file changes)
|
||||
npm run test:watch
|
||||
|
||||
# Run specific test file
|
||||
npx vitest src/api/polaris.test.ts
|
||||
|
||||
# Run with coverage
|
||||
npx vitest --coverage
|
||||
```
|
||||
|
||||
### Test Structure
|
||||
|
||||
Unit tests are colocated with source files:
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/
|
||||
│ ├── polaris.ts
|
||||
│ ├── polaris.test.ts # Unit tests for polaris.ts
|
||||
│ ├── PolarisDataContext.tsx
|
||||
│ └── PolarisDataContext.test.tsx
|
||||
└── components/
|
||||
├── DashboardView.tsx
|
||||
└── DashboardView.test.tsx
|
||||
```
|
||||
|
||||
### Example: Testing Utility Functions
|
||||
|
||||
**File:** `src/api/polaris.test.ts`
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { countResults, computeScore, getNamespaces, filterResultsByNamespace } from './polaris';
|
||||
|
||||
describe('countResults', () => {
|
||||
it('counts passing, warning, danger, and skipped results correctly', () => {
|
||||
const data = {
|
||||
Results: [
|
||||
{
|
||||
Name: 'test-deployment',
|
||||
Namespace: 'default',
|
||||
Kind: 'Deployment',
|
||||
Results: {
|
||||
'check-1': { Success: true, Severity: 'warning' },
|
||||
'check-2': { Success: false, Severity: 'danger' },
|
||||
'check-3': { Success: false, Severity: 'ignore' }, // skipped
|
||||
},
|
||||
CreatedTime: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const counts = countResults(data);
|
||||
|
||||
expect(counts).toEqual({
|
||||
total: 3,
|
||||
pass: 1,
|
||||
warning: 0,
|
||||
danger: 1,
|
||||
skipped: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('handles empty results', () => {
|
||||
const data = { Results: [] };
|
||||
const counts = countResults(data);
|
||||
|
||||
expect(counts).toEqual({
|
||||
total: 0,
|
||||
pass: 0,
|
||||
warning: 0,
|
||||
danger: 0,
|
||||
skipped: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeScore', () => {
|
||||
it('returns 0 for zero total checks', () => {
|
||||
expect(computeScore({ total: 0, pass: 0, warning: 0, danger: 0, skipped: 0 })).toBe(0);
|
||||
});
|
||||
|
||||
it('calculates percentage correctly', () => {
|
||||
expect(computeScore({ total: 100, pass: 75, warning: 20, danger: 5, skipped: 0 })).toBe(75);
|
||||
});
|
||||
|
||||
it('rounds to nearest integer', () => {
|
||||
expect(computeScore({ total: 3, pass: 2, warning: 1, danger: 0, skipped: 0 })).toBe(67);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Example: Testing React Components
|
||||
|
||||
**File:** `src/components/DashboardView.test.tsx`
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { DashboardView } from './DashboardView';
|
||||
import * as PolarisDataContext from '../api/PolarisDataContext';
|
||||
|
||||
describe('DashboardView', () => {
|
||||
it('renders loading state', () => {
|
||||
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
||||
data: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders error state', () => {
|
||||
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: '403 Forbidden',
|
||||
refresh: vi.fn(),
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByText(/403 Forbidden/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays cluster score when data is loaded', () => {
|
||||
const mockData = {
|
||||
DisplayName: 'test-cluster',
|
||||
ClusterInfo: { Version: '1.27', Nodes: 3, Pods: 100, Namespaces: 10, Controllers: 50 },
|
||||
Results: [
|
||||
/* ... */
|
||||
],
|
||||
};
|
||||
|
||||
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
||||
data: mockData,
|
||||
loading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByText(/Cluster Score/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### What to Unit Test
|
||||
|
||||
✅ **Do test:**
|
||||
|
||||
- Pure functions (score calculation, filtering, data transformation)
|
||||
- Data parsing and validation
|
||||
- Utility functions
|
||||
- Error handling logic
|
||||
- Edge cases (empty arrays, null values, invalid input)
|
||||
|
||||
❌ **Don't test:**
|
||||
|
||||
- Third-party libraries (Headlamp, React)
|
||||
- Simple prop passing
|
||||
- Trivial getters/setters
|
||||
- Implementation details (internal state)
|
||||
|
||||
---
|
||||
|
||||
## E2E Testing
|
||||
|
||||
### Framework: Playwright
|
||||
|
||||
Playwright provides cross-browser testing with auto-wait, network interception, and screenshot/video capture.
|
||||
|
||||
### Running E2E Tests
|
||||
|
||||
```bash
|
||||
# Run all E2E tests (headless)
|
||||
npm run e2e
|
||||
|
||||
# Run with browser visible (headed mode)
|
||||
npm run e2e:headed
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test e2e/polaris.spec.ts
|
||||
|
||||
# Debug mode (step through tests)
|
||||
npx playwright test --debug
|
||||
|
||||
# Generate trace for debugging
|
||||
npx playwright test --trace on
|
||||
npx playwright show-trace test-results/<test-name>/trace.zip
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
|
||||
**1. Headlamp Instance**
|
||||
|
||||
E2E tests require a running Headlamp instance with:
|
||||
|
||||
- Polaris plugin installed (version being tested)
|
||||
- Polaris dashboard deployed and accessible
|
||||
- RBAC configured (service proxy permissions)
|
||||
|
||||
**2. Authentication**
|
||||
|
||||
Choose one of two authentication methods:
|
||||
|
||||
**Option A: OIDC via Authentik**
|
||||
|
||||
```bash
|
||||
export AUTHENTIK_USERNAME="user@example.com"
|
||||
export AUTHENTIK_PASSWORD="password"
|
||||
export HEADLAMP_URL="https://headlamp.example.com"
|
||||
npm run e2e
|
||||
```
|
||||
|
||||
**Option B: Kubernetes Token**
|
||||
|
||||
```bash
|
||||
# Create token
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
|
||||
# Port-forward for local testing
|
||||
kubectl port-forward -n kube-system svc/headlamp 4466:80
|
||||
|
||||
# Run tests
|
||||
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
||||
```
|
||||
|
||||
**3. Environment Variables**
|
||||
|
||||
Create `.env` file (optional, for persistent config):
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env`:
|
||||
|
||||
```bash
|
||||
HEADLAMP_URL=https://headlamp.example.com
|
||||
HEADLAMP_TOKEN=eyJhbGciOi...
|
||||
# OR
|
||||
AUTHENTIK_USERNAME=user@example.com
|
||||
AUTHENTIK_PASSWORD=secret
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
|
||||
#### Current E2E Tests
|
||||
|
||||
**File:** `e2e/polaris.spec.ts`
|
||||
|
||||
| Test | Description | Validates |
|
||||
| --------------------------------------------- | ------------------------------- | ----------------------------- |
|
||||
| `sidebar contains Polaris entry` | Polaris appears in sidebar | Plugin registration |
|
||||
| `overview page renders cluster score` | Score displayed on overview | Data fetching, rendering |
|
||||
| `namespaces page renders table` | Namespace table loads | Data parsing, table rendering |
|
||||
| `namespace detail drawer opens` | Clicking namespace shows drawer | Navigation, drawer UI |
|
||||
| `namespace detail drawer closes with Escape` | Keyboard shortcut works | Keyboard navigation |
|
||||
| `namespace detail drawer opens from URL hash` | Direct URL navigation | URL routing, deep linking |
|
||||
|
||||
**File:** `e2e/settings.spec.ts`
|
||||
|
||||
| Test | Description | Validates |
|
||||
| ------------------------------------ | ------------------- | --------------------------- |
|
||||
| `plugin settings page is accessible` | Settings page loads | Settings registration |
|
||||
| `refresh interval can be changed` | Dropdown works | User preference persistence |
|
||||
| `dashboard URL can be customized` | Input field works | URL configuration |
|
||||
| `connection test button works` | Test functionality | API connectivity validation |
|
||||
|
||||
**File:** `e2e/appbar.spec.ts`
|
||||
|
||||
| Test | Description | Validates |
|
||||
| -------------------------------------- | ------------------------------- | ------------------- |
|
||||
| `app bar displays Polaris badge` | Badge visible in header | App bar integration |
|
||||
| `badge shows cluster score` | Score matches dashboard | Data consistency |
|
||||
| `clicking badge navigates to overview` | Navigation works | App bar action |
|
||||
| `badge color reflects score` | Red/yellow/green based on score | Visual feedback |
|
||||
|
||||
### Writing E2E Tests
|
||||
|
||||
**Example: Testing User Flow**
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('user can view namespace details and navigate back', async ({ page }) => {
|
||||
// Navigate to namespaces page
|
||||
await page.goto('/c/main/polaris/namespaces');
|
||||
await expect(page.getByText('Polaris — Namespaces')).toBeVisible();
|
||||
|
||||
// Click first namespace
|
||||
const firstNamespace = page.locator('table tbody tr').first();
|
||||
const namespaceName = await firstNamespace.locator('td').first().textContent();
|
||||
await firstNamespace.getByRole('button').click();
|
||||
|
||||
// Drawer should open
|
||||
await expect(page.getByText(`Polaris — ${namespaceName}`)).toBeVisible();
|
||||
await expect(page).toHaveURL(new RegExp(`#${namespaceName}`));
|
||||
|
||||
// Close drawer with Escape
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByText(`Polaris — ${namespaceName}`)).not.toBeVisible();
|
||||
await expect(page).toHaveURL(/namespaces$/);
|
||||
});
|
||||
```
|
||||
|
||||
**Example: Testing Dark Mode Adaptation**
|
||||
|
||||
```typescript
|
||||
test('plugin adapts to dark mode', async ({ page }) => {
|
||||
await page.goto('/c/main/polaris/namespaces');
|
||||
|
||||
// Toggle dark mode
|
||||
await page.getByLabel(/theme/i).click();
|
||||
|
||||
// Open namespace drawer
|
||||
const firstNamespace = page.locator('table tbody tr button').first();
|
||||
await firstNamespace.click();
|
||||
|
||||
// Drawer background should be dark
|
||||
const drawer = page.locator('[style*="position: fixed"][style*="right: 0"]');
|
||||
await expect(drawer).toHaveCSS('background-color', /rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
||||
});
|
||||
```
|
||||
|
||||
### Debugging E2E Tests
|
||||
|
||||
**1. Headed Mode (See Browser)**
|
||||
|
||||
```bash
|
||||
npm run e2e:headed
|
||||
```
|
||||
|
||||
**2. Debug Mode (Step Through Tests)**
|
||||
|
||||
```bash
|
||||
npx playwright test --debug
|
||||
```
|
||||
|
||||
This opens Playwright Inspector where you can:
|
||||
|
||||
- Step through each test action
|
||||
- Inspect page state
|
||||
- Edit test selectors live
|
||||
|
||||
**3. Screenshots on Failure**
|
||||
|
||||
Screenshots are automatically saved to `test-results/` on failure:
|
||||
|
||||
```bash
|
||||
test-results/
|
||||
└── polaris-overview-page-renders-cluster-score/
|
||||
├── test-failed-1.png
|
||||
└── trace.zip
|
||||
```
|
||||
|
||||
**4. Trace Viewer**
|
||||
|
||||
Record full trace (DOM snapshots, network, console):
|
||||
|
||||
```bash
|
||||
npx playwright test --trace on
|
||||
npx playwright show-trace test-results/<test-name>/trace.zip
|
||||
```
|
||||
|
||||
**5. Verbose Logging**
|
||||
|
||||
```bash
|
||||
DEBUG=pw:api npx playwright test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Workflows
|
||||
|
||||
#### CI Workflow (`.github/workflows/ci.yaml`)
|
||||
|
||||
Runs on every push to `main` and all pull requests:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
lint-and-test:
|
||||
steps:
|
||||
- Build plugin
|
||||
- Lint (eslint)
|
||||
- Type-check (tsc)
|
||||
- Format check (prettier)
|
||||
- Run unit tests
|
||||
```
|
||||
|
||||
#### E2E Workflow (`.github/workflows/e2e.yaml`)
|
||||
|
||||
Runs E2E tests against live Headlamp instance:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
e2e:
|
||||
steps:
|
||||
- Install dependencies
|
||||
- Install Playwright browsers
|
||||
- Run auth setup
|
||||
- Run E2E tests
|
||||
- Upload artifacts on failure
|
||||
```
|
||||
|
||||
### Required GitHub Secrets
|
||||
|
||||
Configure in GitHub repository settings (Settings → Secrets and variables → Actions):
|
||||
|
||||
| Secret | Required | Description |
|
||||
| -------------------- | -------- | ------------------------------------------------------- |
|
||||
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to configured instance) |
|
||||
| `AUTHENTIK_USERNAME` | OIDC | Authentik username/email for CI user |
|
||||
| `AUTHENTIK_PASSWORD` | OIDC | Authentik password |
|
||||
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
|
||||
|
||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
||||
|
||||
### Manual Trigger
|
||||
|
||||
Trigger workflows manually from GitHub Actions UI:
|
||||
|
||||
1. Go to Actions → [Workflow Name]
|
||||
2. Click "Run workflow"
|
||||
3. Select branch and run
|
||||
|
||||
---
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### Current Coverage
|
||||
|
||||
| Category | Coverage | Notes |
|
||||
| -------------------- | -------- | ------------------------------ |
|
||||
| **API Functions** | 95% | Core utilities fully tested |
|
||||
| **React Components** | 60% | Focus on critical render paths |
|
||||
| **E2E User Flows** | 80% | Main features covered |
|
||||
|
||||
### Coverage Goals
|
||||
|
||||
- **Unit Tests**: 80%+ for `src/api/` (business logic)
|
||||
- **Component Tests**: 50%+ for `src/components/` (critical rendering)
|
||||
- **E2E Tests**: Cover all major user journeys
|
||||
|
||||
### Generating Coverage Reports
|
||||
|
||||
```bash
|
||||
# Unit test coverage
|
||||
npx vitest --coverage
|
||||
|
||||
# View HTML report
|
||||
open coverage/index.html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Unit Testing
|
||||
|
||||
1. **Test behavior, not implementation**
|
||||
|
||||
- ✅ `expect(computeScore({ total: 100, pass: 75 })).toBe(75)`
|
||||
- ❌ `expect(mockInternalFunction).toHaveBeenCalled()`
|
||||
|
||||
2. **Use descriptive test names**
|
||||
|
||||
- ✅ `it('returns 0 when total checks is zero')`
|
||||
- ❌ `it('works')`
|
||||
|
||||
3. **One assertion per test (when possible)**
|
||||
|
||||
- Makes failures easier to debug
|
||||
- Exceptions: testing multiple properties of same object
|
||||
|
||||
4. **Mock external dependencies**
|
||||
|
||||
- Mock API calls, context providers, external libraries
|
||||
- Don't mock the code you're testing
|
||||
|
||||
5. **Test edge cases**
|
||||
- Empty arrays, null values, zero counts
|
||||
- Invalid input, malformed data
|
||||
|
||||
### E2E Testing
|
||||
|
||||
1. **Use semantic selectors**
|
||||
|
||||
- ✅ `page.getByRole('button', { name: 'Close' })`
|
||||
- ✅ `page.getByText('Polaris — Overview')`
|
||||
- ❌ `page.locator('.MuiButton-root')`
|
||||
|
||||
2. **Wait for visibility, not arbitrary timeouts**
|
||||
|
||||
- ✅ `await expect(element).toBeVisible()`
|
||||
- ❌ `await page.waitForTimeout(5000)`
|
||||
|
||||
3. **Keep tests independent**
|
||||
|
||||
- Each test should work in isolation
|
||||
- Don't rely on previous tests' state
|
||||
|
||||
4. **Test complete user flows**
|
||||
|
||||
- Navigate → Interact → Verify outcome
|
||||
- Don't just test page loads
|
||||
|
||||
5. **Clean up after tests**
|
||||
|
||||
- Close drawers/modals
|
||||
- Reset state if needed
|
||||
|
||||
6. **Use storage state for auth**
|
||||
|
||||
- Reuse authenticated session across tests
|
||||
- Faster than logging in for every test
|
||||
|
||||
7. **Parallelize carefully**
|
||||
- Tests must not interfere with each other
|
||||
- Currently disabled due to shared cluster state
|
||||
|
||||
### General
|
||||
|
||||
1. **Run tests before committing**
|
||||
|
||||
```bash
|
||||
npm run build && npm run lint && npm test
|
||||
```
|
||||
|
||||
2. **Fix failing tests immediately**
|
||||
|
||||
- Don't commit failing tests
|
||||
- Don't skip tests to "fix later"
|
||||
|
||||
3. **Update tests when changing code**
|
||||
|
||||
- Tests are documentation
|
||||
- Keep them in sync with implementation
|
||||
|
||||
4. **Review test failures in CI**
|
||||
- Check artifacts (screenshots, traces)
|
||||
- Reproduce locally before fixing
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
**Issue: Mock not working**
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Mock after import
|
||||
import { usePolarisData } from './polaris';
|
||||
vi.mock('./polaris');
|
||||
|
||||
// ✅ Correct: Mock before import
|
||||
vi.mock('./polaris', () => ({
|
||||
usePolarisData: vi.fn(),
|
||||
}));
|
||||
import { usePolarisData } from './polaris';
|
||||
```
|
||||
|
||||
**Issue: "Cannot read property of undefined"**
|
||||
|
||||
Check mocks are returning expected structure:
|
||||
|
||||
```typescript
|
||||
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
||||
data: mockData, // Ensure mockData has all required fields
|
||||
loading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
});
|
||||
```
|
||||
|
||||
#### E2E Tests
|
||||
|
||||
**Issue: "Element not found"**
|
||||
|
||||
```bash
|
||||
# Enable verbose logging
|
||||
DEBUG=pw:api npx playwright test
|
||||
|
||||
# Use headed mode to see what's happening
|
||||
npm run e2e:headed
|
||||
```
|
||||
|
||||
Common causes:
|
||||
|
||||
- Element hasn't rendered yet (use `toBeVisible()` instead of `toBeDefined()`)
|
||||
- Wrong selector (use Playwright Inspector to verify)
|
||||
- Authentication failed (check token/credentials)
|
||||
|
||||
**Issue: "Test timeout"**
|
||||
|
||||
Increase timeout for slow operations:
|
||||
|
||||
```typescript
|
||||
test('slow operation', async ({ page }) => {
|
||||
test.setTimeout(60000); // 60 seconds
|
||||
|
||||
await page.goto('/c/main/polaris');
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
**Issue: Network errors in E2E tests**
|
||||
|
||||
```bash
|
||||
# Check Headlamp accessibility
|
||||
curl -I $HEADLAMP_URL
|
||||
|
||||
# Check Polaris service
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
```
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# Run specific test file
|
||||
npx vitest src/api/polaris.test.ts
|
||||
|
||||
# Run specific test case
|
||||
npx vitest -t "computes score correctly"
|
||||
|
||||
# Run E2E test by name
|
||||
npx playwright test -g "sidebar contains Polaris"
|
||||
|
||||
# Update snapshots
|
||||
npx vitest -u
|
||||
|
||||
# Clear test cache
|
||||
npx vitest --clearCache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Vitest Documentation](https://vitest.dev/)
|
||||
- [Playwright Documentation](https://playwright.dev/)
|
||||
- [Testing Library (React)](https://testing-library.com/docs/react-testing-library/intro/)
|
||||
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
- [Project Architecture](./ARCHITECTURE.md)
|
||||
- [Contributing Guide](../CONTRIBUTING.md)
|
||||
@@ -0,0 +1,422 @@
|
||||
# Installation Guide
|
||||
|
||||
This guide covers all installation methods for the Headlamp Polaris Plugin.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation Methods](#installation-methods)
|
||||
- [Option 1: Plugin Manager (Recommended)](#option-1-plugin-manager-recommended)
|
||||
- [Option 2: Sidecar Container](#option-2-sidecar-container)
|
||||
- [Option 3: Manual Tarball](#option-3-manual-tarball)
|
||||
- [Option 4: Build from Source](#option-4-build-from-source)
|
||||
- [Post-Installation](#post-installation)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before installation, ensure all [prerequisites](prerequisites.md) are met:
|
||||
|
||||
- Kubernetes v1.24+
|
||||
- Headlamp v0.26+ (v0.39+ recommended)
|
||||
- Polaris deployed with dashboard enabled
|
||||
- RBAC permissions configured
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### Option 1: Plugin Manager (Recommended)
|
||||
|
||||
**Best for:** Production deployments, managed updates, ease of use
|
||||
|
||||
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin) and can be installed via the Headlamp Plugin Manager.
|
||||
|
||||
#### Via Headlamp UI
|
||||
|
||||
1. **Navigate to Plugin Settings:**
|
||||
|
||||
- Open Headlamp in your browser
|
||||
- Go to **Settings → Plugins**
|
||||
- Click the **Catalog** tab
|
||||
|
||||
2. **Search and Install:**
|
||||
|
||||
- Search for "Polaris"
|
||||
- Find "Headlamp Polaris Plugin"
|
||||
- Click **Install**
|
||||
|
||||
3. **Hard Refresh Browser:**
|
||||
|
||||
- **Mac:** Cmd+Shift+R
|
||||
- **Windows/Linux:** Ctrl+Shift+R
|
||||
|
||||
4. **Verify Installation:**
|
||||
- Sidebar should show "Polaris" entry
|
||||
- Click **Polaris** → Overview page loads
|
||||
|
||||
#### Via Helm Configuration
|
||||
|
||||
Add to your Headlamp Helm values:
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
repositories:
|
||||
- https://artifacthub.io/packages/search?kind=4
|
||||
```
|
||||
|
||||
Deploy or update Headlamp:
|
||||
|
||||
```bash
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
Then install the plugin via Headlamp UI as described above.
|
||||
|
||||
### Option 2: Sidecar Container
|
||||
|
||||
**Best for:** Controlled plugin versions, air-gapped environments, specific version pinning
|
||||
|
||||
This method uses an init container to download and install the plugin from a tarball URL.
|
||||
|
||||
#### Helm Values Configuration
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
initContainers:
|
||||
- name: install-polaris-plugin
|
||||
image: node:lts-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
npm install -g @kinvolk/headlamp-plugin
|
||||
headlamp-plugin install --config /config/plugin.yml --plugins-dir /plugins
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /plugins
|
||||
- name: plugin-config
|
||||
mountPath: /config
|
||||
|
||||
volumes:
|
||||
- name: plugins
|
||||
emptyDir: {}
|
||||
- name: plugin-config
|
||||
configMap:
|
||||
name: headlamp-plugin-config
|
||||
```
|
||||
|
||||
#### Plugin Configuration ConfigMap
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-plugin-config
|
||||
namespace: kube-system
|
||||
data:
|
||||
plugin.yml: |
|
||||
- name: headlamp-polaris-plugin
|
||||
version: 0.3.5
|
||||
url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
#### Apply Configuration
|
||||
|
||||
```bash
|
||||
# Create ConfigMap
|
||||
kubectl apply -f headlamp-plugin-config.yaml
|
||||
|
||||
# Deploy/update Headlamp with sidecar
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
|
||||
# Wait for pod to be ready
|
||||
kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
|
||||
# Verify plugin files
|
||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
# drwxr-xr-x dist/
|
||||
# -rw-r--r-- package.json
|
||||
```
|
||||
|
||||
### Option 3: Manual Tarball
|
||||
|
||||
**Best for:** Testing specific versions, offline installations
|
||||
|
||||
Download the plugin tarball and extract it into Headlamp's plugin directory.
|
||||
|
||||
#### Download and Extract
|
||||
|
||||
```bash
|
||||
# Download latest release
|
||||
VERSION=0.3.5
|
||||
wget https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v${VERSION}/headlamp-polaris-plugin-${VERSION}.tar.gz
|
||||
|
||||
# Extract to plugin directory
|
||||
tar xzf headlamp-polaris-plugin-${VERSION}.tar.gz -C /headlamp/plugins/
|
||||
|
||||
# Verify extraction
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
# drwxr-xr-x dist/
|
||||
# -rw-r--r-- package.json
|
||||
```
|
||||
|
||||
#### Kubernetes Volume Mount
|
||||
|
||||
If Headlamp runs in Kubernetes, mount the plugin directory as a volume:
|
||||
|
||||
```yaml
|
||||
# headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: /plugins
|
||||
|
||||
volumes:
|
||||
- name: plugins
|
||||
hostPath:
|
||||
path: /path/to/plugins # Where you extracted the tarball
|
||||
type: Directory
|
||||
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /plugins
|
||||
readOnly: true
|
||||
```
|
||||
|
||||
**Note:** This method is not recommended for production (hostPath is node-specific).
|
||||
|
||||
### Option 4: Build from Source
|
||||
|
||||
**Best for:** Development, contributing, testing unreleased features
|
||||
|
||||
Clone the repository and build the plugin from source.
|
||||
|
||||
#### Clone and Build
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/privilegedescalation/headlamp-polaris-plugin.git
|
||||
cd headlamp-polaris-plugin
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build plugin
|
||||
npm run build
|
||||
|
||||
# Package tarball (optional)
|
||||
npm run package
|
||||
|
||||
# Extract to Headlamp plugin directory
|
||||
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
|
||||
```
|
||||
|
||||
#### Development Mode (Hot Reload)
|
||||
|
||||
For active development with hot reload:
|
||||
|
||||
```bash
|
||||
# Start Headlamp with plugin hot reload
|
||||
npm start
|
||||
|
||||
# Opens Headlamp at http://localhost:4466
|
||||
# Changes to src/ automatically rebuild and reload
|
||||
```
|
||||
|
||||
See [Development Workflow](../development/workflow.md) for detailed development setup.
|
||||
|
||||
## Post-Installation
|
||||
|
||||
After installing the plugin via any method:
|
||||
|
||||
### 1. Configure RBAC
|
||||
|
||||
Apply RBAC permissions for the plugin to access Polaris:
|
||||
|
||||
```bash
|
||||
# Create polaris-plugin-rbac.yaml
|
||||
cat <<EOF | kubectl apply -f -
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
EOF
|
||||
```
|
||||
|
||||
See [RBAC Permissions](../user-guide/rbac-permissions.md) for detailed RBAC configuration.
|
||||
|
||||
### 2. Restart Headlamp (if needed)
|
||||
|
||||
```bash
|
||||
# If you updated Helm values or ConfigMaps
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
|
||||
# Wait for pod to be ready
|
||||
kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
```
|
||||
|
||||
### 3. Clear Browser Cache
|
||||
|
||||
**Critical:** Hard refresh your browser to load the new plugin JavaScript:
|
||||
|
||||
- **Mac:** Cmd+Shift+R
|
||||
- **Windows/Linux:** Ctrl+Shift+R
|
||||
|
||||
### 4. Verify Installation
|
||||
|
||||
**UI Verification:**
|
||||
|
||||
1. Navigate to **Settings → Plugins**
|
||||
2. Verify "headlamp-polaris-plugin" is listed
|
||||
3. Check version matches installed version
|
||||
4. Verify **Polaris** appears in sidebar
|
||||
5. Click **Polaris** → Overview page loads
|
||||
6. Cluster score displays correctly
|
||||
|
||||
**CLI Verification:**
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
# drwxr-xr-x dist/
|
||||
# -rw-r--r-- package.json
|
||||
|
||||
# Check Headlamp logs for errors
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
|
||||
# Expected: No errors related to plugin loading
|
||||
|
||||
# Test Polaris API access
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||
|
||||
# Expected: "1.0" or similar
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin Not in Sidebar
|
||||
|
||||
**Symptom:** Plugin listed in Settings → Plugins but no "Polaris" entry in sidebar
|
||||
|
||||
**Causes:**
|
||||
|
||||
1. Browser cache not cleared
|
||||
2. Plugin JavaScript failed to load
|
||||
3. Plugin files not properly installed
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# 1. Verify plugin files exist
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected: dist/, package.json present
|
||||
|
||||
# 2. Check Headlamp logs for plugin errors
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
|
||||
# 3. Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
|
||||
|
||||
# 4. Check browser console for JavaScript errors
|
||||
# Open DevTools → Console tab
|
||||
```
|
||||
|
||||
### 403 Forbidden Error
|
||||
|
||||
**Symptom:** Error loading Polaris data, 403 in browser console
|
||||
|
||||
**Cause:** RBAC permissions missing or incorrect
|
||||
|
||||
**Solution:**
|
||||
|
||||
See [RBAC Issues](../troubleshooting/rbac-issues.md) for detailed debugging.
|
||||
|
||||
### 404 Not Found Error
|
||||
|
||||
**Symptom:** Error loading Polaris data, 404 in browser console
|
||||
|
||||
**Causes:**
|
||||
|
||||
1. Polaris not deployed
|
||||
2. Polaris service name incorrect
|
||||
3. Polaris namespace incorrect
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Verify Polaris deployment
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
|
||||
# If service doesn't exist, install Polaris:
|
||||
helm install polaris fairwinds-stable/polaris \
|
||||
--namespace polaris \
|
||||
--create-namespace \
|
||||
--set dashboard.enabled=true
|
||||
```
|
||||
|
||||
### Plugin Version Mismatch
|
||||
|
||||
**Symptom:** Settings shows old version, ArtifactHub shows new version
|
||||
|
||||
**Cause:** ArtifactHub sync delay (30 minutes) or plugin manager cache
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Wait 30 minutes for ArtifactHub sync
|
||||
# Or manually force Headlamp restart:
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Quick Start](quick-start.md)** - Get up and running in 5 minutes
|
||||
- **[Configuration](../user-guide/configuration.md)** - Customize refresh intervals, dashboard URLs
|
||||
- **[Features](../user-guide/features.md)** - Learn about all plugin features
|
||||
- **[Troubleshooting](../troubleshooting/README.md)** - Comprehensive troubleshooting guide
|
||||
|
||||
## References
|
||||
|
||||
- [Headlamp Plugin Documentation](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
- [Headlamp Helm Chart](https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp)
|
||||
- [Polaris Installation](https://polaris.docs.fairwinds.com/infrastructure-as-code/)
|
||||
- [Artifact Hub Package](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin)
|
||||
@@ -0,0 +1,224 @@
|
||||
# Prerequisites
|
||||
|
||||
Before installing the Headlamp Polaris Plugin, ensure your environment meets the following requirements.
|
||||
|
||||
## Required Components
|
||||
|
||||
| Requirement | Minimum Version | Recommended Version |
|
||||
| ------------------------------- | ------------------ | --------------------------------- |
|
||||
| **Kubernetes** | v1.24+ | v1.28+ |
|
||||
| **Headlamp** | v0.26+ | v0.39+ |
|
||||
| **Polaris** (dashboard enabled) | Any recent release | Latest stable |
|
||||
| **Browser** | Modern (ES2020+) | Latest Chrome/Firefox/Safari/Edge |
|
||||
|
||||
## Polaris Requirements
|
||||
|
||||
The plugin requires Polaris to be deployed with the dashboard component enabled:
|
||||
|
||||
- **Namespace:** `polaris` (default expected namespace)
|
||||
- **Dashboard enabled:** `dashboard.enabled: true` in Helm chart (default)
|
||||
- **Service:** `polaris-dashboard` ClusterIP service on port 80
|
||||
|
||||
### Verify Polaris Installation
|
||||
|
||||
```bash
|
||||
# Check Polaris pods are running
|
||||
kubectl -n polaris get pods
|
||||
|
||||
# Expected output:
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# polaris-dashboard-xxxxxxxxx-xxxxx 1/1 Running 0 1h
|
||||
# polaris-webhook-xxxxxxxxx-xxxxx 1/1 Running 0 1h
|
||||
|
||||
# Check Polaris dashboard service exists
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
|
||||
# Expected output:
|
||||
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
# polaris-dashboard ClusterIP 10.96.xxx.xxx <none> 80/TCP 1h
|
||||
|
||||
# Test Polaris dashboard API
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||
|
||||
# Expected output:
|
||||
# "1.0"
|
||||
```
|
||||
|
||||
### Install Polaris (if not present)
|
||||
|
||||
```bash
|
||||
# Add Fairwinds Helm repository
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm repo update
|
||||
|
||||
# Install Polaris with dashboard enabled
|
||||
helm install polaris fairwinds-stable/polaris \
|
||||
--namespace polaris \
|
||||
--create-namespace \
|
||||
--set dashboard.enabled=true
|
||||
|
||||
# Wait for pods to be ready
|
||||
kubectl -n polaris wait --for=condition=ready pod -l app.kubernetes.io/name=polaris --timeout=300s
|
||||
```
|
||||
|
||||
## Headlamp Requirements
|
||||
|
||||
### Verify Headlamp Installation
|
||||
|
||||
```bash
|
||||
# Check Headlamp is deployed
|
||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
||||
|
||||
# Expected output:
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 1h
|
||||
|
||||
# Check Headlamp version (must be v0.26+)
|
||||
kubectl -n kube-system get deployment headlamp -o jsonpath='{.spec.template.spec.containers[0].image}'
|
||||
|
||||
# Expected output:
|
||||
# ghcr.io/headlamp-k8s/headlamp:v0.39.0 (or similar)
|
||||
```
|
||||
|
||||
### Install Headlamp (if not present)
|
||||
|
||||
```bash
|
||||
# Add Headlamp Helm repository
|
||||
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
|
||||
helm repo update
|
||||
|
||||
# Install Headlamp
|
||||
helm install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--set config.pluginsDir="/headlamp/plugins" \
|
||||
--set pluginsManager.enabled=true
|
||||
|
||||
# Wait for pod to be ready
|
||||
kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
```
|
||||
|
||||
## RBAC Requirements
|
||||
|
||||
The plugin requires permissions to access the Polaris dashboard via Kubernetes service proxy.
|
||||
|
||||
### Required Permission
|
||||
|
||||
| Verb | API Group | Resource | Resource Name | Namespace |
|
||||
| ----- | ----------- | ---------------- | ------------------- | --------- |
|
||||
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
|
||||
|
||||
### Verify RBAC Permissions
|
||||
|
||||
```bash
|
||||
# Test if Headlamp service account has permission
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected output: yes
|
||||
|
||||
# If "no", you need to create RBAC (see installation guide)
|
||||
```
|
||||
|
||||
## Network Requirements
|
||||
|
||||
### Service Proxy Access
|
||||
|
||||
The plugin accesses Polaris through the Kubernetes API server's service proxy:
|
||||
|
||||
```
|
||||
Headlamp Pod → Kubernetes API Server → Polaris Dashboard Service
|
||||
```
|
||||
|
||||
**Required network paths:**
|
||||
|
||||
- Headlamp pod → Kubernetes API server (443)
|
||||
- Kubernetes API server → Polaris dashboard service (80)
|
||||
|
||||
### NetworkPolicy Considerations
|
||||
|
||||
If the `polaris` namespace has NetworkPolicies enabled, ensure the Kubernetes API server can reach the `polaris-dashboard` service on port 80.
|
||||
|
||||
### Test Network Connectivity
|
||||
|
||||
```bash
|
||||
# Test service proxy endpoint from API server
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq . > /dev/null
|
||||
|
||||
# If successful, no output
|
||||
# If failed, check NetworkPolicies and service status
|
||||
```
|
||||
|
||||
## Browser Requirements
|
||||
|
||||
The plugin uses modern JavaScript features and requires:
|
||||
|
||||
- **ES2020+ support**
|
||||
- **localStorage** enabled
|
||||
- **JavaScript** enabled
|
||||
- **Cookies** enabled (for Headlamp session)
|
||||
|
||||
### Tested Browsers
|
||||
|
||||
| Browser | Minimum Version |
|
||||
| --------------- | --------------- |
|
||||
| Chrome/Chromium | 80+ |
|
||||
| Firefox | 75+ |
|
||||
| Safari | 13.1+ |
|
||||
| Edge | 80+ |
|
||||
|
||||
## Optional Components
|
||||
|
||||
### OIDC Authentication (for multi-user deployments)
|
||||
|
||||
If using Headlamp with OIDC authentication, each user must have RBAC permissions for service proxy access (see [RBAC Permissions](../user-guide/rbac-permissions.md)).
|
||||
|
||||
### Ingress (for external access)
|
||||
|
||||
If exposing Headlamp externally, configure an Ingress with TLS:
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
hosts:
|
||||
- host: headlamp.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: headlamp-tls
|
||||
hosts:
|
||||
- headlamp.example.com
|
||||
```
|
||||
|
||||
## Pre-Installation Checklist
|
||||
|
||||
Before proceeding to installation, verify:
|
||||
|
||||
- [ ] Kubernetes cluster v1.24+ running
|
||||
- [ ] Polaris deployed in `polaris` namespace with dashboard enabled
|
||||
- [ ] Polaris dashboard service accessible via service proxy
|
||||
- [ ] Headlamp v0.26+ deployed
|
||||
- [ ] RBAC permissions configured (or ready to configure)
|
||||
- [ ] Network connectivity between API server and Polaris dashboard
|
||||
- [ ] Modern browser available
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once all prerequisites are met:
|
||||
|
||||
1. **[Installation Guide](installation.md)** - Choose installation method and deploy the plugin
|
||||
2. **[Quick Start](quick-start.md)** - Get up and running in 5 minutes
|
||||
3. **[RBAC Permissions](../user-guide/rbac-permissions.md)** - Detailed RBAC configuration
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If any prerequisite check fails, see:
|
||||
|
||||
- **[Troubleshooting Guide](../troubleshooting/README.md)** - Common issues and solutions
|
||||
- **[RBAC Issues](../troubleshooting/rbac-issues.md)** - Permission debugging
|
||||
- **[Network Problems](../troubleshooting/network-problems.md)** - Connectivity issues
|
||||
@@ -0,0 +1,278 @@
|
||||
# Quick Start
|
||||
|
||||
Get the Headlamp Polaris Plugin up and running in 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure:
|
||||
|
||||
- Kubernetes cluster is running
|
||||
- Headlamp v0.26+ is deployed
|
||||
- Polaris is installed with dashboard enabled
|
||||
|
||||
Don't have these? See [Prerequisites](prerequisites.md) for installation instructions.
|
||||
|
||||
## Step 1: Install the Plugin (2 minutes)
|
||||
|
||||
### Via Headlamp UI
|
||||
|
||||
1. Open Headlamp in your browser
|
||||
2. Go to **Settings → Plugins → Catalog**
|
||||
3. Search for "Polaris"
|
||||
4. Click **Install** on "Headlamp Polaris Plugin"
|
||||
5. Hard refresh browser: **Cmd+Shift+R** (Mac) or **Ctrl+Shift+R** (Windows/Linux)
|
||||
|
||||
### Via Helm (if using Helm-managed Headlamp)
|
||||
|
||||
```bash
|
||||
# Add plugin manager config to Headlamp values
|
||||
cat <<EOF > headlamp-values.yaml
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
|
||||
pluginsManager:
|
||||
enabled: true
|
||||
repositories:
|
||||
- https://artifacthub.io/packages/search?kind=4
|
||||
EOF
|
||||
|
||||
# Update Headlamp
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
Then install via Headlamp UI as described above.
|
||||
|
||||
## Step 2: Configure RBAC (1 minute)
|
||||
|
||||
Grant the plugin permission to access Polaris data:
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<EOF
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
EOF
|
||||
```
|
||||
|
||||
**Note:** Adjust the `namespace` in `subjects` if your Headlamp runs in a different namespace.
|
||||
|
||||
## Step 3: Verify Installation (1 minute)
|
||||
|
||||
### UI Verification
|
||||
|
||||
1. **Check Plugin is Loaded:**
|
||||
|
||||
- Go to **Settings → Plugins**
|
||||
- Verify "headlamp-polaris-plugin" is listed
|
||||
|
||||
2. **Check Sidebar:**
|
||||
|
||||
- Look for **Polaris** entry in the left sidebar
|
||||
- If not visible, hard refresh: **Cmd+Shift+R** / **Ctrl+Shift+R**
|
||||
|
||||
3. **View Overview Dashboard:**
|
||||
|
||||
- Click **Polaris** in sidebar
|
||||
- Overview page loads with:
|
||||
- Cluster score gauge
|
||||
- Check distribution charts
|
||||
- Top 10 failing checks
|
||||
- Cluster statistics
|
||||
|
||||
4. **Check App Bar Badge:**
|
||||
- Colored chip in top navigation bar shows cluster score
|
||||
- Click badge to navigate to overview
|
||||
|
||||
### CLI Verification
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \
|
||||
ls /headlamp/plugins/headlamp-polaris-plugin/dist/
|
||||
|
||||
# Expected output:
|
||||
# main.js
|
||||
|
||||
# Verify RBAC is correct
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected output: yes
|
||||
|
||||
# Test Polaris API access
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json \
|
||||
| jq .PolarisOutputVersion
|
||||
|
||||
# Expected output: "1.0" or similar
|
||||
```
|
||||
|
||||
## Step 4: Explore Features (1 minute)
|
||||
|
||||
### Overview Dashboard
|
||||
|
||||
Navigate to **Polaris → Overview**:
|
||||
|
||||
- **Cluster Score Gauge:** Overall cluster health (0-100%)
|
||||
|
||||
- Green (≥80%): Excellent
|
||||
- Yellow (50-79%): Needs improvement
|
||||
- Red (<50%): Critical issues
|
||||
|
||||
- **Check Distribution:** Pass/Warning/Danger/Skipped counts with charts
|
||||
|
||||
- **Top 10 Failing Checks:** Most common issues across the cluster
|
||||
|
||||
- **Cluster Statistics:** Nodes, pods, namespaces, controllers count
|
||||
|
||||
- **Manual Refresh:** Click refresh button to fetch latest audit data
|
||||
|
||||
### Namespaces View
|
||||
|
||||
Navigate to **Polaris → Namespaces**:
|
||||
|
||||
- Table of all namespaces with per-namespace scores
|
||||
- Click a namespace to open detailed side panel
|
||||
- Side panel shows:
|
||||
- Namespace score and check counts
|
||||
- Resource-level audit results
|
||||
- Link to external Polaris dashboard
|
||||
|
||||
### Inline Resource Audits
|
||||
|
||||
View any workload detail page (Deployment, StatefulSet, DaemonSet, Job, CronJob):
|
||||
|
||||
- **Polaris Audit** section automatically appears
|
||||
- Shows compact score and failing checks
|
||||
- Link to full report
|
||||
|
||||
### App Bar Badge
|
||||
|
||||
Cluster score badge in top navigation:
|
||||
|
||||
- Color-coded by score (green/yellow/red)
|
||||
- Click to navigate to overview
|
||||
- Always visible for quick reference
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin Not in Sidebar
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \
|
||||
ls /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# If missing, reinstall via Headlamp UI or sidecar method
|
||||
|
||||
# Hard refresh browser
|
||||
# Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
|
||||
```
|
||||
|
||||
### 403 Forbidden Error
|
||||
|
||||
```bash
|
||||
# Verify RBAC exists
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# If missing, apply RBAC from Step 2
|
||||
```
|
||||
|
||||
### 404 Not Found Error
|
||||
|
||||
```bash
|
||||
# Verify Polaris is running
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
|
||||
# If missing, install Polaris:
|
||||
helm install polaris fairwinds-stable/polaris \
|
||||
--namespace polaris \
|
||||
--create-namespace \
|
||||
--set dashboard.enabled=true
|
||||
```
|
||||
|
||||
### Data Not Loading
|
||||
|
||||
```bash
|
||||
# Check Polaris dashboard is responding
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
|
||||
# If fails, check:
|
||||
# 1. Polaris pods are running
|
||||
# 2. NetworkPolicies allow API server → Polaris dashboard
|
||||
# 3. Polaris service exists and is ClusterIP type
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Configuration](../user-guide/configuration.md)** - Customize refresh intervals, dashboard URLs
|
||||
- **[Features](../user-guide/features.md)** - Learn about all plugin features
|
||||
- **[RBAC Permissions](../user-guide/rbac-permissions.md)** - Advanced RBAC configuration (token-auth, OIDC)
|
||||
- **[Troubleshooting](../troubleshooting/README.md)** - Comprehensive troubleshooting guide
|
||||
|
||||
## Common Configuration Tasks
|
||||
|
||||
### Change Refresh Interval
|
||||
|
||||
1. Go to **Settings → Plugins → Polaris**
|
||||
2. Select refresh interval (1 / 5 / 10 / 30 minutes)
|
||||
3. Click **Save**
|
||||
|
||||
Default is 5 minutes.
|
||||
|
||||
### Use Custom Polaris URL
|
||||
|
||||
If Polaris is deployed externally or in a different namespace:
|
||||
|
||||
1. Go to **Settings → Plugins → Polaris**
|
||||
2. Update **Dashboard URL**:
|
||||
- Service proxy: `/api/v1/namespaces/custom-ns/services/polaris-dashboard:80/proxy/`
|
||||
- Full URL: `https://polaris.example.com/`
|
||||
3. Click **Test Connection** to verify
|
||||
4. Click **Save**
|
||||
|
||||
### Test Polaris Connectivity
|
||||
|
||||
1. Go to **Settings → Plugins → Polaris**
|
||||
2. Click **Test Connection**
|
||||
3. Verify green success message with Polaris version
|
||||
|
||||
If test fails, see [Troubleshooting](../troubleshooting/README.md).
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **[Full Installation Guide](installation.md)** - All installation methods (sidecar, manual, source)
|
||||
- **[Development Workflow](../development/workflow.md)** - Build from source, hot reload
|
||||
- **[RBAC Issues](../troubleshooting/rbac-issues.md)** - Permission debugging
|
||||
- **[Network Problems](../troubleshooting/network-problems.md)** - Connectivity troubleshooting
|
||||
|
||||
---
|
||||
|
||||
You're now running the Headlamp Polaris Plugin. Visit the **Polaris** section in Headlamp to explore your cluster's security, reliability, and efficiency audit results.
|
||||
@@ -0,0 +1,163 @@
|
||||
# Troubleshooting
|
||||
|
||||
Quick diagnosis guide and common issues for the Headlamp Polaris Plugin.
|
||||
|
||||
## Quick Diagnosis
|
||||
|
||||
| Symptom | Likely Cause | Quick Fix | Details |
|
||||
| ------------------------------- | -------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **Plugin not in sidebar** | Browser cache or plugin not installed | Hard refresh browser (Cmd+Shift+R) and verify plugin files exist | [Common Issues](common-issues.md#plugin-not-in-sidebar) |
|
||||
| **403 Access Denied** | Missing RBAC binding for `services/proxy` | Apply Role + RoleBinding from RBAC section | [RBAC Issues](rbac-issues.md) |
|
||||
| **404 or 503** | Polaris not installed, or dashboard disabled | Install Polaris with `dashboard.enabled: true` in `polaris` namespace | [Common Issues](common-issues.md#404-not-found) |
|
||||
| **Dark mode white backgrounds** | Old plugin version | Upgrade to v0.3.5+ and hard refresh browser | [Common Issues](common-issues.md#dark-mode-issues) |
|
||||
| **Settings page empty** | Old plugin version | Upgrade to v0.3.3+ | [Common Issues](common-issues.md#settings-page-empty) |
|
||||
| **No data / infinite spinner** | Network policy or Polaris pod down | Check network policies and `kubectl get pods -n polaris` | [Network Problems](network-problems.md) |
|
||||
| **Namespace drawer white** | CSS variable issue | Update to v0.3.5+ with `--mui-palette-background-paper` | [Common Issues](common-issues.md#dark-mode-issues) |
|
||||
| **Cluster score not updating** | Auto-refresh disabled or interval too long | Check Settings → Plugins → Polaris refresh interval | [Common Issues](common-issues.md#data-not-refreshing) |
|
||||
| **Custom URL not working** | CORS or incorrect URL format | Test with curl, check CORS headers | [Network Problems](network-problems.md#cors-issues) |
|
||||
|
||||
## Detailed Guides
|
||||
|
||||
- **[Common Issues](common-issues.md)** - Comprehensive guide to frequent problems and solutions
|
||||
- **[RBAC Issues](rbac-issues.md)** - Permission debugging, 403 errors, token-auth mode
|
||||
- **[Network Problems](network-problems.md)** - NetworkPolicies, connectivity, proxy issues, CORS
|
||||
|
||||
## Diagnostic Commands
|
||||
|
||||
### Quick Health Check
|
||||
|
||||
```bash
|
||||
# 1. Verify Polaris is running
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
|
||||
# 2. Test Polaris API access
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||
|
||||
# Expected output: "1.0" or similar
|
||||
|
||||
# 3. Verify RBAC permissions
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected output: yes
|
||||
|
||||
# 4. Check Headlamp pod is running
|
||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
||||
|
||||
# 5. Check Headlamp logs for plugin errors
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
|
||||
# Expected: No errors
|
||||
```
|
||||
|
||||
### Plugin Loading Verification
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
# drwxr-xr-x dist/
|
||||
# -rw-r--r-- package.json
|
||||
```
|
||||
|
||||
### RBAC Verification
|
||||
|
||||
```bash
|
||||
# Check Role exists
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
|
||||
# Check RoleBinding exists
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission (service account mode)
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Test permission (user token mode, replace with your user)
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=user@example.com \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
```
|
||||
|
||||
### Network Debugging
|
||||
|
||||
```bash
|
||||
# Test from kubectl (uses same service proxy)
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
|
||||
# Check NetworkPolicies
|
||||
kubectl -n polaris get networkpolicy
|
||||
|
||||
# Test direct service access (from within cluster)
|
||||
kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \
|
||||
curl http://polaris-dashboard.polaris/results.json
|
||||
```
|
||||
|
||||
## Browser Debugging
|
||||
|
||||
### Check Browser Console
|
||||
|
||||
1. Open browser DevTools (F12)
|
||||
2. Go to **Console** tab
|
||||
3. Look for errors containing "polaris" or "plugin"
|
||||
|
||||
**Common errors:**
|
||||
|
||||
- `createSvgIcon is not defined` → MUI import issue (plugin bug)
|
||||
- `403 Forbidden` → RBAC permission denied
|
||||
- `404 Not Found` → Polaris not installed or wrong URL
|
||||
- `Failed to fetch` → Network policy or CORS issue
|
||||
|
||||
### Clear Browser Cache
|
||||
|
||||
**Critical:** After plugin updates, hard refresh to clear cached JavaScript:
|
||||
|
||||
- **Mac:** Cmd+Shift+R
|
||||
- **Windows/Linux:** Ctrl+Shift+R
|
||||
- **Alternatively:** Clear all browser data for Headlamp URL
|
||||
|
||||
### Check localStorage
|
||||
|
||||
```javascript
|
||||
// Open browser console and run:
|
||||
localStorage.getItem('polaris-plugin-refresh-interval');
|
||||
localStorage.getItem('polaris-plugin-dashboard-url');
|
||||
|
||||
// Reset to defaults:
|
||||
localStorage.removeItem('polaris-plugin-refresh-interval');
|
||||
localStorage.removeItem('polaris-plugin-dashboard-url');
|
||||
```
|
||||
|
||||
## Still Having Issues?
|
||||
|
||||
If the quick diagnosis doesn't resolve your issue:
|
||||
|
||||
1. **Check detailed guides:**
|
||||
|
||||
- [Common Issues](common-issues.md)
|
||||
- [RBAC Issues](rbac-issues.md)
|
||||
- [Network Problems](network-problems.md)
|
||||
|
||||
2. **Review documentation:**
|
||||
|
||||
- [Installation Guide](../getting-started/installation.md)
|
||||
- [RBAC Permissions](../user-guide/rbac-permissions.md)
|
||||
- [Deployment Guide](../deployment/kubernetes.md)
|
||||
|
||||
3. **Open a GitHub issue:**
|
||||
- [GitHub Issues](https://github.com/privilegedescalation/headlamp-polaris-plugin/issues)
|
||||
- Include: Headlamp version, plugin version, error messages, logs
|
||||
|
||||
## References
|
||||
|
||||
- [Headlamp Troubleshooting](https://headlamp.dev/docs/latest/troubleshooting/)
|
||||
- [Polaris Troubleshooting](https://polaris.docs.fairwinds.com/troubleshooting/)
|
||||
- [Kubernetes RBAC Troubleshooting](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#troubleshooting)
|
||||
@@ -0,0 +1,715 @@
|
||||
# Troubleshooting Guide
|
||||
|
||||
This guide covers common issues encountered when using the Headlamp Polaris Plugin and their solutions.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Plugin Not Showing in Sidebar](#plugin-not-showing-in-sidebar)
|
||||
- [403 Forbidden Error](#403-forbidden-error)
|
||||
- [404 Not Found Error](#404-not-found-error)
|
||||
- [Plugin Settings Page Empty](#plugin-settings-page-empty)
|
||||
- [Dark Mode Issues](#dark-mode-issues)
|
||||
- [Data Not Loading / Infinite Spinner](#data-not-loading--infinite-spinner)
|
||||
- [Browser Console Errors](#browser-console-errors)
|
||||
- [Network and RBAC Debugging](#network-and-rbac-debugging)
|
||||
- [Plugin Installation Issues](#plugin-installation-issues)
|
||||
- [ArtifactHub Sync Delays](#artifacthub-sync-delays)
|
||||
|
||||
---
|
||||
|
||||
## Plugin Not Showing in Sidebar
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Plugin appears in Settings → Plugins but sidebar entry is missing
|
||||
- No "Polaris" section in navigation
|
||||
- Routes like `/polaris` return 404 or blank page
|
||||
|
||||
### Common Causes
|
||||
|
||||
**1. Plugin Not Installed**
|
||||
|
||||
**Check plugin installation**:
|
||||
|
||||
```bash
|
||||
# View Headlamp pod logs (plugin sidecar)
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
||||
|
||||
# Expected output:
|
||||
# Installing plugin from https://github.com/.../headlamp-polaris-plugin-X.Y.Z.tar.gz
|
||||
# Plugin installed successfully
|
||||
```
|
||||
|
||||
**Verify plugin files exist**:
|
||||
|
||||
```bash
|
||||
kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
||||
# Should show: headlamp-polaris-plugin/
|
||||
```
|
||||
|
||||
**2. JavaScript Cached by Browser**
|
||||
|
||||
After upgrading the plugin, old JavaScript may be cached.
|
||||
|
||||
**Solution**:
|
||||
|
||||
- Hard refresh: Cmd+Shift+R (macOS) or Ctrl+Shift+R (Linux/Windows)
|
||||
- Clear browser cache for Headlamp domain
|
||||
- Open DevTools → Application → Clear Storage → Clear all
|
||||
|
||||
**3. Plugin Disabled in Settings**
|
||||
|
||||
**Check Settings → Plugins**:
|
||||
|
||||
- Navigate to Headlamp Settings → Plugins
|
||||
- Ensure "Polaris" plugin is enabled (toggle should be ON)
|
||||
- If disabled, enable it and refresh the page
|
||||
|
||||
---
|
||||
|
||||
## 403 Forbidden Error
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Error message: "Error loading Polaris audit data: 403 Forbidden"
|
||||
- Browser console shows 403 response from API proxy
|
||||
- Plugin sidebar shows but data fails to load
|
||||
|
||||
### Root Cause
|
||||
|
||||
User or service account lacks `services/proxy` permission on `polaris-dashboard` service in the `polaris` namespace.
|
||||
|
||||
### Solution
|
||||
|
||||
**1. Verify RBAC Configuration**
|
||||
|
||||
Check if Role exists:
|
||||
|
||||
```bash
|
||||
kubectl get role polaris-proxy-reader -n polaris -o yaml
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
```
|
||||
|
||||
**2. Verify RoleBinding**
|
||||
|
||||
For service account mode:
|
||||
|
||||
```bash
|
||||
kubectl get rolebinding headlamp-polaris-proxy -n polaris -o yaml
|
||||
```
|
||||
|
||||
Expected subjects:
|
||||
|
||||
```yaml
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
```
|
||||
|
||||
For OIDC mode:
|
||||
|
||||
```bash
|
||||
kubectl get rolebinding -n polaris -o yaml | grep -A 5 polaris-proxy-reader
|
||||
```
|
||||
|
||||
Ensure your user or group is bound to the `polaris-proxy-reader` role.
|
||||
|
||||
**3. Create Missing RBAC**
|
||||
|
||||
If RBAC is missing, apply the minimal configuration:
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
EOF
|
||||
```
|
||||
|
||||
**4. Test RBAC Permissions**
|
||||
|
||||
Service account mode:
|
||||
|
||||
```bash
|
||||
# Impersonate Headlamp service account
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
--resource-name=polaris-dashboard \
|
||||
-n polaris
|
||||
# Expected: yes
|
||||
```
|
||||
|
||||
OIDC mode (test as yourself):
|
||||
|
||||
```bash
|
||||
kubectl auth can-i get services/proxy \
|
||||
--resource-name=polaris-dashboard \
|
||||
-n polaris
|
||||
# Expected: yes (if you have proper RoleBinding)
|
||||
```
|
||||
|
||||
**5. Restart Headlamp**
|
||||
|
||||
After applying RBAC changes:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment headlamp -n kube-system
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 404 Not Found Error
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Error message: "Error loading Polaris audit data: 404 Not Found"
|
||||
- Service proxy request returns 404
|
||||
- Polaris dashboard not reachable
|
||||
|
||||
### Root Cause
|
||||
|
||||
Polaris dashboard service doesn't exist or is in a different namespace.
|
||||
|
||||
### Solution
|
||||
|
||||
**1. Verify Polaris Installation**
|
||||
|
||||
Check if Polaris is installed:
|
||||
|
||||
```bash
|
||||
kubectl get pods -n polaris
|
||||
# Expected: polaris-dashboard-* pod running
|
||||
```
|
||||
|
||||
Check if service exists:
|
||||
|
||||
```bash
|
||||
kubectl get service polaris-dashboard -n polaris
|
||||
# Expected: ClusterIP service on port 80
|
||||
```
|
||||
|
||||
**2. Verify Service Name and Port**
|
||||
|
||||
The plugin expects:
|
||||
|
||||
- **Namespace**: `polaris`
|
||||
- **Service Name**: `polaris-dashboard`
|
||||
- **Port**: `80` (or named port `dashboard`)
|
||||
|
||||
If your service has a different name or is in a different namespace, you'll need to modify the plugin source or redeploy Polaris with standard naming.
|
||||
|
||||
**3. Test Service Proxy Manually**
|
||||
|
||||
```bash
|
||||
kubectl proxy &
|
||||
curl http://localhost:8001/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
```
|
||||
|
||||
If this returns JSON, the service proxy works and the issue is elsewhere.
|
||||
If this returns 404, Polaris service is not configured correctly.
|
||||
|
||||
**4. Check Polaris Dashboard Configuration**
|
||||
|
||||
Verify Polaris is running with dashboard enabled:
|
||||
|
||||
```bash
|
||||
kubectl get deployment polaris-dashboard -n polaris -o yaml | grep -A 5 dashboard
|
||||
```
|
||||
|
||||
If `dashboard.enabled: false` in Helm values, enable it:
|
||||
|
||||
```yaml
|
||||
# values.yaml
|
||||
dashboard:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
**5. Reinstall Polaris**
|
||||
|
||||
If Polaris is missing or misconfigured:
|
||||
|
||||
```bash
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm upgrade --install polaris fairwinds-stable/polaris \
|
||||
--namespace polaris \
|
||||
--create-namespace \
|
||||
--set dashboard.enabled=true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plugin Settings Page Empty
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Settings → Polaris shows title but no content
|
||||
- Refresh interval and dashboard URL fields not visible
|
||||
|
||||
### Root Cause (Fixed in v0.3.3)
|
||||
|
||||
Plugin settings registration name didn't match `package.json` name.
|
||||
|
||||
### Solution
|
||||
|
||||
Upgrade to v0.3.3 or later:
|
||||
|
||||
```bash
|
||||
# Via Headlamp UI: Settings → Plugins → Update
|
||||
# Or redeploy with latest version
|
||||
```
|
||||
|
||||
If manually installing, ensure plugin name matches `package.json`:
|
||||
|
||||
```typescript
|
||||
registerPluginSettings('headlamp-polaris-plugin', PolarisSettings, true);
|
||||
// NOT 'polaris' — must match package.json name
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode Issues
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Drawer background remains white in dark mode
|
||||
- Text is hard to read in dark mode
|
||||
- Theme colors don't match Headlamp UI
|
||||
|
||||
### Solution (Fixed in v0.3.5)
|
||||
|
||||
Upgrade to v0.3.5 or later for complete dark mode support.
|
||||
|
||||
**Verify CSS Variables**:
|
||||
The plugin uses MUI CSS variables for theming:
|
||||
|
||||
- `--mui-palette-background-default` (drawer background)
|
||||
- `--mui-palette-text-primary` (text color)
|
||||
- `--mui-palette-primary-main` (links, buttons)
|
||||
- `--mui-palette-error-main` (danger states)
|
||||
|
||||
These automatically adapt to Headlamp's theme (light/dark/system).
|
||||
|
||||
**Hard Refresh Required**:
|
||||
After upgrading from v0.3.4 or earlier, hard refresh your browser:
|
||||
|
||||
- macOS: Cmd+Shift+R
|
||||
- Linux/Windows: Ctrl+Shift+R
|
||||
|
||||
**Clear Browser Cache**:
|
||||
If hard refresh doesn't help, clear cache for Headlamp domain.
|
||||
|
||||
---
|
||||
|
||||
## Data Not Loading / Infinite Spinner
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Plugin shows "Loading Polaris audit data..." forever
|
||||
- No error message in UI
|
||||
- Data never appears
|
||||
|
||||
### Debugging Steps
|
||||
|
||||
**1. Check Browser Console**
|
||||
|
||||
Open DevTools (F12) → Console tab.
|
||||
|
||||
Look for:
|
||||
|
||||
- Network errors (CORS, timeouts, 5xx responses)
|
||||
- JavaScript errors
|
||||
- Failed API requests
|
||||
|
||||
**2. Check Network Tab**
|
||||
|
||||
Open DevTools → Network tab → Filter by "results.json"
|
||||
|
||||
Expected request:
|
||||
|
||||
```
|
||||
GET /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
Status: 200
|
||||
Response: JSON data
|
||||
```
|
||||
|
||||
Common issues:
|
||||
|
||||
- **Status 0 / Failed**: Network policy blocking request
|
||||
- **Status 403**: RBAC issue (see [403 Forbidden Error](#403-forbidden-error))
|
||||
- **Status 404**: Service not found (see [404 Not Found Error](#404-not-found-error))
|
||||
- **Status 500**: Polaris dashboard error
|
||||
- **Status 502/504**: Service unreachable (network policy or pod down)
|
||||
|
||||
**3. Check Polaris Dashboard Health**
|
||||
|
||||
```bash
|
||||
# Check if Polaris pod is running
|
||||
kubectl get pods -n polaris
|
||||
|
||||
# Check Polaris logs
|
||||
kubectl logs -n polaris deployment/polaris-dashboard
|
||||
|
||||
# Test direct access to Polaris
|
||||
kubectl port-forward -n polaris svc/polaris-dashboard 8080:80
|
||||
curl http://localhost:8080/results.json
|
||||
```
|
||||
|
||||
**4. Check Network Policies**
|
||||
|
||||
If your cluster uses NetworkPolicies:
|
||||
|
||||
```bash
|
||||
kubectl get networkpolicy -n polaris
|
||||
```
|
||||
|
||||
Ensure API server (or Headlamp pod) can reach Polaris dashboard.
|
||||
|
||||
**Example fix**:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-api-server-to-polaris
|
||||
namespace: polaris
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: polaris
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector: {} # Allow from all namespaces (API server)
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
```
|
||||
|
||||
**5. Increase Timeout / Disable Auto-Refresh**
|
||||
|
||||
If Polaris responds slowly:
|
||||
|
||||
- Open Settings → Polaris
|
||||
- Increase refresh interval to 10+ minutes
|
||||
- Or set to "Manual only" to disable auto-refresh
|
||||
|
||||
---
|
||||
|
||||
## Browser Console Errors
|
||||
|
||||
### Common Errors and Solutions
|
||||
|
||||
**Error: "Failed to fetch"**
|
||||
|
||||
**Cause**: Network request failed (CORS, network policy, timeout)
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Check Network tab for actual HTTP status
|
||||
2. Verify network policies allow API server → Polaris
|
||||
3. Check Polaris pod is running
|
||||
|
||||
---
|
||||
|
||||
**Error: "Unexpected token < in JSON"**
|
||||
|
||||
**Cause**: API returned HTML (error page) instead of JSON
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Check Network tab response body (likely 404 or 500 error page)
|
||||
2. Verify Polaris service exists and is healthy
|
||||
3. Check service proxy URL is correct
|
||||
|
||||
---
|
||||
|
||||
**Error: "registerPluginSettings is not a function"**
|
||||
|
||||
**Cause**: Headlamp version too old (< v0.26)
|
||||
|
||||
**Solution**: Upgrade Headlamp to v0.26 or later.
|
||||
|
||||
---
|
||||
|
||||
**Error: "Cannot read property 'AuditData' of undefined"**
|
||||
|
||||
**Cause**: Polaris returned empty or malformed response
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Check Polaris logs for errors
|
||||
2. Verify Polaris is scanning the cluster (check audit timestamp)
|
||||
3. Test `/results.json` endpoint directly
|
||||
|
||||
---
|
||||
|
||||
## Network and RBAC Debugging
|
||||
|
||||
### Comprehensive RBAC Test
|
||||
|
||||
Run this script to test all RBAC components:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
NS="polaris"
|
||||
SA="headlamp"
|
||||
SA_NS="kube-system"
|
||||
|
||||
echo "=== Testing RBAC for Polaris Plugin ==="
|
||||
|
||||
# 1. Check if service exists
|
||||
echo "1. Service check:"
|
||||
kubectl get svc polaris-dashboard -n $NS || echo "❌ Service not found"
|
||||
|
||||
# 2. Check if Role exists
|
||||
echo "2. Role check:"
|
||||
kubectl get role polaris-proxy-reader -n $NS || echo "❌ Role not found"
|
||||
|
||||
# 3. Check if RoleBinding exists
|
||||
echo "3. RoleBinding check:"
|
||||
kubectl get rolebinding headlamp-polaris-proxy -n $NS || echo "❌ RoleBinding not found"
|
||||
|
||||
# 4. Test service account permissions
|
||||
echo "4. Permission test (service account):"
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:$SA_NS:$SA \
|
||||
--resource-name=polaris-dashboard \
|
||||
-n $NS
|
||||
|
||||
# 5. Test actual proxy request (requires kubectl proxy)
|
||||
echo "5. Proxy test:"
|
||||
kubectl proxy &
|
||||
PROXY_PID=$!
|
||||
sleep 2
|
||||
curl -s http://localhost:8001/api/v1/namespaces/$NS/services/polaris-dashboard:80/proxy/results.json | jq '.DisplayName' || echo "❌ Proxy request failed"
|
||||
kill $PROXY_PID
|
||||
|
||||
echo "=== Test complete ==="
|
||||
```
|
||||
|
||||
### Network Policy Debugging
|
||||
|
||||
Test connectivity from Headlamp to Polaris:
|
||||
|
||||
```bash
|
||||
# Create debug pod in kube-system namespace
|
||||
kubectl run netdebug -n kube-system --rm -it --image=nicolaka/netshoot -- bash
|
||||
|
||||
# Inside pod, test DNS and HTTP
|
||||
nslookup polaris-dashboard.polaris.svc.cluster.local
|
||||
curl -v http://polaris-dashboard.polaris.svc.cluster.local/results.json
|
||||
```
|
||||
|
||||
If this fails, network policies are blocking traffic.
|
||||
|
||||
### API Server Audit Logs
|
||||
|
||||
If you have audit logging enabled, check for denied requests:
|
||||
|
||||
```bash
|
||||
# View recent audit logs (location varies by cluster)
|
||||
kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
|
||||
|
||||
# Look for lines with:
|
||||
# "reason": "Forbidden"
|
||||
# "user": "system:serviceaccount:kube-system:headlamp"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plugin Installation Issues
|
||||
|
||||
### Sidecar Fails to Install Plugin
|
||||
|
||||
**Symptoms**:
|
||||
|
||||
- Plugin sidecar logs show download errors
|
||||
- Plugin directory is empty
|
||||
- Settings → Plugins shows nothing
|
||||
|
||||
**Check sidecar logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
||||
```
|
||||
|
||||
**Common errors**:
|
||||
|
||||
**1. Network timeout downloading tarball**
|
||||
|
||||
```
|
||||
Error: connect ETIMEDOUT
|
||||
```
|
||||
|
||||
**Solution**: Check cluster egress network policies allow HTTPS to GitHub.
|
||||
|
||||
---
|
||||
|
||||
**2. Invalid tarball URL**
|
||||
|
||||
```
|
||||
Error: 404 Not Found
|
||||
```
|
||||
|
||||
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
|
||||
|
||||
```bash
|
||||
kubectl get configmap headlamp-plugin-config -n kube-system -o yaml
|
||||
```
|
||||
|
||||
Expected format:
|
||||
|
||||
```
|
||||
https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**3. Permission denied writing to /headlamp/plugins**
|
||||
|
||||
**Solution**: Ensure volume mount is writable:
|
||||
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /headlamp/plugins
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Plugin Manager Not Working
|
||||
|
||||
**Symptoms**:
|
||||
|
||||
- Headlamp → Settings → Plugins shows "Catalog" tab but plugins don't install
|
||||
- "Install" button does nothing
|
||||
|
||||
**Root Cause**: Plugin manager requires `config.pluginsDir` to be set.
|
||||
|
||||
**Solution**: Configure Headlamp for plugin manager:
|
||||
|
||||
```yaml
|
||||
# HelmRelease values
|
||||
config:
|
||||
pluginsDir: /headlamp/plugins
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ArtifactHub Sync Delays
|
||||
|
||||
### Symptoms
|
||||
|
||||
- New version released on GitHub but not showing in ArtifactHub
|
||||
- Headlamp plugin catalog shows old version
|
||||
|
||||
### Root Cause
|
||||
|
||||
ArtifactHub pulls metadata every 30 minutes. There is no webhook or push mechanism.
|
||||
|
||||
### Solution
|
||||
|
||||
**Wait 30 minutes** after pushing a GitHub release, then check:
|
||||
|
||||
```
|
||||
https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin
|
||||
```
|
||||
|
||||
**Verify metadata**:
|
||||
|
||||
1. Check `artifacthub-pkg.yml` is in repository root
|
||||
2. Check `headlamp/plugin/archive-url` points to GitHub release
|
||||
3. Check `headlamp/plugin/archive-checksum` matches tarball SHA256
|
||||
|
||||
**Force sync** (ArtifactHub UI):
|
||||
|
||||
- Log in to ArtifactHub as package maintainer
|
||||
- Go to package settings
|
||||
- Click "Reindex now"
|
||||
|
||||
**Note**: First sync after repository registration may take up to 1 hour.
|
||||
|
||||
---
|
||||
|
||||
## Still Having Issues?
|
||||
|
||||
If none of these solutions work, gather debugging information and open an issue:
|
||||
|
||||
### Required Information
|
||||
|
||||
1. **Version Information**:
|
||||
|
||||
```bash
|
||||
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
||||
```
|
||||
|
||||
2. **Plugin Version**:
|
||||
|
||||
- Check Settings → Plugins in Headlamp UI
|
||||
- Or: `kubectl exec -n kube-system deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
|
||||
|
||||
3. **Browser Console Output**:
|
||||
|
||||
- Open DevTools (F12) → Console
|
||||
- Screenshot or copy errors
|
||||
|
||||
4. **Network Tab**:
|
||||
|
||||
- Open DevTools → Network
|
||||
- Screenshot failed requests to `results.json`
|
||||
|
||||
5. **Pod Logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100
|
||||
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
|
||||
```
|
||||
|
||||
6. **RBAC Configuration**:
|
||||
```bash
|
||||
kubectl get role,rolebinding -n polaris
|
||||
```
|
||||
|
||||
### Where to Get Help
|
||||
|
||||
- **GitHub Issues**: [https://github.com/privilegedescalation/headlamp-polaris-plugin/issues](https://github.com/privilegedescalation/headlamp-polaris-plugin/issues)
|
||||
- **GitHub Discussions**: [https://github.com/privilegedescalation/headlamp-polaris-plugin/discussions](https://github.com/privilegedescalation/headlamp-polaris-plugin/discussions)
|
||||
|
||||
Include the debugging information above when opening an issue.
|
||||
@@ -0,0 +1,106 @@
|
||||
# Network Problems
|
||||
|
||||
Troubleshooting network connectivity issues for the Headlamp Polaris Plugin.
|
||||
|
||||
## Overview
|
||||
|
||||
The plugin accesses Polaris through the Kubernetes service proxy. Network issues can occur at multiple points in this chain:
|
||||
|
||||
```
|
||||
Headlamp Pod → K8s API Server → Polaris Dashboard Service
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### NetworkPolicy Blocking Access
|
||||
|
||||
**Symptom:** Timeout or connection errors despite correct RBAC
|
||||
|
||||
**Cause:** NetworkPolicy in `polaris` namespace blocking API server ingress
|
||||
|
||||
**Solution:**
|
||||
|
||||
Allow ingress from the Kubernetes API server to Polaris dashboard:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-apiserver-to-polaris
|
||||
namespace: polaris
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: polaris
|
||||
app.kubernetes.io/component: dashboard
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow from API server
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: kube-apiserver
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
```
|
||||
|
||||
**Note:** The API server performs the proxy hop, not the Headlamp pod directly.
|
||||
|
||||
### Test Network Connectivity
|
||||
|
||||
```bash
|
||||
# 1. Test service proxy endpoint
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
||||
|
||||
# If successful: JSON output
|
||||
# If failed: Check NetworkPolicies and service status
|
||||
|
||||
# 2. Check NetworkPolicies
|
||||
kubectl -n polaris get networkpolicy
|
||||
|
||||
# 3. Test direct service access (from within cluster)
|
||||
kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \
|
||||
curl http://polaris-dashboard.polaris/results.json
|
||||
|
||||
# If this works but service proxy doesn't, check API server network access
|
||||
```
|
||||
|
||||
### CORS Issues (Custom URL)
|
||||
|
||||
**Symptom:** Error when using custom Polaris URL in settings
|
||||
|
||||
**Cause:** CORS not configured on external Polaris deployment
|
||||
|
||||
**Solution:**
|
||||
|
||||
Configure Polaris dashboard to allow Headlamp origin:
|
||||
|
||||
```yaml
|
||||
# Polaris Helm values
|
||||
dashboard:
|
||||
enabled: true
|
||||
env:
|
||||
- name: CORS_ALLOWED_ORIGINS
|
||||
value: 'https://headlamp.example.com'
|
||||
```
|
||||
|
||||
Test CORS headers:
|
||||
|
||||
```bash
|
||||
curl -v -H "Origin: https://headlamp.example.com" \
|
||||
https://my-polaris.example.com/results.json
|
||||
|
||||
# Check for:
|
||||
# Access-Control-Allow-Origin: https://headlamp.example.com
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Kubernetes NetworkPolicies](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
|
||||
- [Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/)
|
||||
@@ -0,0 +1,104 @@
|
||||
# RBAC Issues
|
||||
|
||||
Troubleshooting RBAC permissions and 403 errors for the Headlamp Polaris Plugin.
|
||||
|
||||
## Overview
|
||||
|
||||
The plugin requires `get` permission on `services/proxy` resource for the `polaris-dashboard` service in the `polaris` namespace. Without this permission, you'll see 403 Forbidden errors.
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### 403 Forbidden Error
|
||||
|
||||
**Symptom:** Error loading Polaris data, "Access denied (403)" in UI
|
||||
|
||||
**Cause:** Missing or incorrect RBAC binding
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# 1. Verify RBAC resources exist
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# If missing, apply RBAC:
|
||||
kubectl apply -f - <<EOF
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["polaris-dashboard"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
EOF
|
||||
```
|
||||
|
||||
### Token-Auth Mode
|
||||
|
||||
**Symptom:** 403 error when using Headlamp with user-supplied tokens
|
||||
|
||||
**Cause:** User's own identity lacks the RoleBinding
|
||||
|
||||
**Solution:**
|
||||
|
||||
Bind the Role to authenticated users or specific users/groups:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: users-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: system:authenticated # All authenticated users
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### Testing Permissions
|
||||
|
||||
```bash
|
||||
# Test service account (in-cluster mode)
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Test user (token-auth mode)
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=user@example.com \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected output: yes
|
||||
```
|
||||
|
||||
For detailed RBAC configuration, see [RBAC Permissions](../user-guide/rbac-permissions.md).
|
||||
|
||||
## References
|
||||
|
||||
- [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
|
||||
- [Service Proxy RBAC](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/)
|
||||
@@ -0,0 +1,412 @@
|
||||
# Configuration Guide
|
||||
|
||||
Customize the Headlamp Polaris Plugin to fit your environment.
|
||||
|
||||
## Plugin Settings
|
||||
|
||||
Access plugin settings via **Settings → Plugins → Polaris** in the Headlamp UI.
|
||||
|
||||
## Refresh Interval
|
||||
|
||||
**What it does:** Controls how often the plugin fetches the latest audit data from Polaris.
|
||||
|
||||
### Available Options
|
||||
|
||||
- **1 minute** - Most frequent updates, highest API load
|
||||
- **5 minutes** - **Default**, balanced load and freshness
|
||||
- **10 minutes** - Moderate refresh rate
|
||||
- **30 minutes** - Light load, best for large clusters
|
||||
|
||||
### How to Change
|
||||
|
||||
1. Navigate to **Settings → Plugins → Polaris**
|
||||
2. Click the **Refresh Interval** dropdown
|
||||
3. Select your desired interval
|
||||
4. Click **Save**
|
||||
5. Changes take effect immediately (no browser refresh needed)
|
||||
|
||||
### Impact
|
||||
|
||||
**Affects:**
|
||||
|
||||
- Dashboard overview page
|
||||
- Namespace list and detail views
|
||||
- Inline audit sections on resource pages
|
||||
- App bar score badge
|
||||
|
||||
**API Load:**
|
||||
|
||||
- Each refresh triggers one HTTP GET to Polaris dashboard
|
||||
- Each request is logged in Kubernetes audit logs
|
||||
- Longer intervals reduce API server and audit log pressure
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
**For small clusters (<100 pods):**
|
||||
|
||||
- Recommended: 5 minutes (default)
|
||||
- Acceptable: 1 minute (if real-time data is critical)
|
||||
|
||||
**For large clusters (>1000 pods):**
|
||||
|
||||
- Recommended: 10-30 minutes
|
||||
- Reason: Reduces audit log volume and API server load
|
||||
- Example: 10 users × 1-minute refresh = ~14,400 audit logs/day
|
||||
- Example: 10 users × 30-minute refresh = ~480 audit logs/day
|
||||
|
||||
**For production environments:**
|
||||
|
||||
- Start with 5 minutes
|
||||
- Monitor API server metrics and audit log volume
|
||||
- Increase interval if needed
|
||||
|
||||
## Dashboard URL
|
||||
|
||||
**What it does:** Specifies which Polaris instance the plugin connects to.
|
||||
|
||||
### Default Configuration
|
||||
|
||||
**Service proxy path (default):**
|
||||
|
||||
```
|
||||
/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/
|
||||
```
|
||||
|
||||
This uses the Kubernetes API server to proxy requests to the Polaris dashboard service in the `polaris` namespace.
|
||||
|
||||
**Advantages:**
|
||||
|
||||
- Uses existing Headlamp authentication (service account or user token)
|
||||
- Works with Headlamp's OIDC and token-auth modes
|
||||
- No additional RBAC or network configuration needed
|
||||
- Respects Kubernetes NetworkPolicies
|
||||
|
||||
### Custom URL Scenarios
|
||||
|
||||
#### External Polaris (HTTPS)
|
||||
|
||||
If Polaris is deployed outside the cluster with an external URL:
|
||||
|
||||
```
|
||||
https://polaris.example.com/
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Polaris dashboard must be accessible from browser
|
||||
- CORS must be configured on Polaris to allow Headlamp origin
|
||||
- HTTPS recommended for production
|
||||
|
||||
#### Custom Namespace
|
||||
|
||||
If Polaris is deployed in a different namespace:
|
||||
|
||||
```
|
||||
/api/v1/namespaces/custom-namespace/services/polaris-dashboard:80/proxy/
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Update RBAC Role namespace to match
|
||||
- Service name must still be `polaris-dashboard` (or adjust in URL)
|
||||
|
||||
#### Non-Standard Port
|
||||
|
||||
If Polaris dashboard uses a different port:
|
||||
|
||||
```
|
||||
/api/v1/namespaces/polaris/services/polaris-dashboard:8080/proxy/
|
||||
```
|
||||
|
||||
#### Local Development
|
||||
|
||||
For local Polaris development instance:
|
||||
|
||||
```
|
||||
http://localhost:8080/
|
||||
```
|
||||
|
||||
**Note:** Browser may block mixed content (HTTPS Headlamp → HTTP Polaris).
|
||||
|
||||
### How to Change Dashboard URL
|
||||
|
||||
1. Navigate to **Settings → Plugins → Polaris**
|
||||
2. Update the **Dashboard URL** field
|
||||
3. Click **Test Connection** to verify (recommended)
|
||||
4. Click **Save** if connection test succeeds
|
||||
|
||||
### Connection Testing
|
||||
|
||||
**What it does:** Verifies the plugin can reach the Polaris dashboard and fetch audit data.
|
||||
|
||||
**To test:**
|
||||
|
||||
1. Enter Dashboard URL in settings
|
||||
2. Click **Test Connection**
|
||||
3. Wait for response (2-5 seconds)
|
||||
|
||||
**Success Response:**
|
||||
|
||||
```
|
||||
✓ Connected to Polaris v4.2.0
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
|
||||
| Error | Meaning | Solution |
|
||||
| --------------------------- | ------------------------- | ----------------------------------------------------------- |
|
||||
| **403 Forbidden** | RBAC permission denied | Check RBAC bindings (see [RBAC Guide](rbac-permissions.md)) |
|
||||
| **404 Not Found** | Polaris service not found | Verify Polaris is running: `kubectl get svc -n polaris` |
|
||||
| **503 Service Unavailable** | Polaris pod not ready | Check pod status: `kubectl get pods -n polaris` |
|
||||
| **Network Error** | Cannot reach URL | Check URL format, CORS (for external), NetworkPolicies |
|
||||
| **CORS Error** | Cross-origin blocked | Configure Polaris dashboard CORS headers |
|
||||
|
||||
### CORS Configuration (External Polaris)
|
||||
|
||||
If using an external Polaris URL, configure CORS to allow Headlamp origin.
|
||||
|
||||
**Polaris Helm values:**
|
||||
|
||||
```yaml
|
||||
dashboard:
|
||||
enabled: true
|
||||
env:
|
||||
- name: CORS_ALLOWED_ORIGINS
|
||||
value: 'https://headlamp.example.com'
|
||||
```
|
||||
|
||||
**Test CORS:**
|
||||
|
||||
```bash
|
||||
curl -v -H "Origin: https://headlamp.example.com" \
|
||||
https://polaris.example.com/results.json \
|
||||
| grep -i "access-control"
|
||||
|
||||
# Expected:
|
||||
# Access-Control-Allow-Origin: https://headlamp.example.com
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Persistent Settings Storage
|
||||
|
||||
Plugin settings are stored in browser **localStorage**:
|
||||
|
||||
**Keys:**
|
||||
|
||||
- `polaris-plugin-refresh-interval` - Refresh interval in seconds (number)
|
||||
- `polaris-plugin-dashboard-url` - Dashboard URL (string)
|
||||
|
||||
**View settings:**
|
||||
|
||||
```javascript
|
||||
// Open browser DevTools Console (F12)
|
||||
console.log('Refresh Interval:', localStorage.getItem('polaris-plugin-refresh-interval'));
|
||||
console.log('Dashboard URL:', localStorage.getItem('polaris-plugin-dashboard-url'));
|
||||
```
|
||||
|
||||
**Reset to defaults:**
|
||||
|
||||
```javascript
|
||||
// Open browser DevTools Console (F12)
|
||||
localStorage.removeItem('polaris-plugin-refresh-interval');
|
||||
localStorage.removeItem('polaris-plugin-dashboard-url');
|
||||
// Hard refresh browser: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Settings are per-browser, per-user
|
||||
- Private/incognito mode may clear settings on browser close
|
||||
- Settings are NOT synced across devices
|
||||
|
||||
## Configuration Best Practices
|
||||
|
||||
### For Development Clusters
|
||||
|
||||
**Recommended Settings:**
|
||||
|
||||
- **Refresh Interval:** 1-5 minutes (faster feedback loop)
|
||||
- **Dashboard URL:** Service proxy (default)
|
||||
|
||||
**Why:** Development clusters are typically small, so API load is minimal. Faster refresh helps catch issues quickly during development.
|
||||
|
||||
### For Staging Clusters
|
||||
|
||||
**Recommended Settings:**
|
||||
|
||||
- **Refresh Interval:** 5-10 minutes (balanced)
|
||||
- **Dashboard URL:** Service proxy (default)
|
||||
|
||||
**Why:** Staging should mirror production configuration. 5-10 minutes provides reasonable freshness without excessive load.
|
||||
|
||||
### For Production Clusters
|
||||
|
||||
**Recommended Settings:**
|
||||
|
||||
- **Refresh Interval:** 10-30 minutes (reduce load)
|
||||
- **Dashboard URL:** Service proxy (default)
|
||||
|
||||
**Why:** Production clusters are larger and more critical. Longer intervals reduce audit log volume and API pressure. Polaris audits typically run every 10-30 minutes anyway, so more frequent plugin refreshes don't provide much value.
|
||||
|
||||
### For Multi-Tenant Environments
|
||||
|
||||
**Recommended Settings:**
|
||||
|
||||
- **Refresh Interval:** 10-30 minutes (minimize per-user load)
|
||||
- **Dashboard URL:** Service proxy with per-namespace RBAC
|
||||
|
||||
**Why:** Many concurrent Headlamp users can create significant API load. Longer intervals prevent thundering herd issues.
|
||||
|
||||
### For External Polaris
|
||||
|
||||
**Recommended Settings:**
|
||||
|
||||
- **Refresh Interval:** 5-10 minutes (depends on network latency)
|
||||
- **Dashboard URL:** `https://polaris.example.com/`
|
||||
- **CORS:** Must be configured on Polaris side
|
||||
|
||||
**Why:** External Polaris avoids Kubernetes service proxy overhead but requires CORS configuration and network accessibility.
|
||||
|
||||
## Troubleshooting Configuration
|
||||
|
||||
### Settings Not Saving
|
||||
|
||||
**Symptom:** Changes to settings revert after clicking Save
|
||||
|
||||
**Possible Causes:**
|
||||
|
||||
1. Browser blocks localStorage (privacy mode)
|
||||
2. Browser extension interfering
|
||||
3. JavaScript error in console
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Open browser DevTools Console (F12)
|
||||
2. Check for JavaScript errors
|
||||
3. Disable privacy mode or try different browser
|
||||
4. Check if localStorage is enabled:
|
||||
```javascript
|
||||
console.log('localStorage available:', typeof localStorage !== 'undefined');
|
||||
```
|
||||
|
||||
### Settings Lost After Browser Restart
|
||||
|
||||
**Symptom:** Settings reset to defaults when you reopen browser
|
||||
|
||||
**Cause:** Browser privacy settings clear localStorage on exit
|
||||
|
||||
**Solution:**
|
||||
|
||||
- Use normal browsing mode (not private/incognito)
|
||||
- Check browser settings for "Clear data on exit"
|
||||
- Consider requesting ConfigMap-based settings (future feature)
|
||||
|
||||
### Connection Test Fails
|
||||
|
||||
**Symptom:** Test Connection button shows error
|
||||
|
||||
**Solutions by error type:**
|
||||
|
||||
**403 Forbidden:**
|
||||
|
||||
```bash
|
||||
# Verify RBAC exists
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
```
|
||||
|
||||
**404 Not Found:**
|
||||
|
||||
```bash
|
||||
# Verify Polaris is running
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
|
||||
# If missing, install Polaris
|
||||
helm install polaris fairwinds-stable/polaris \
|
||||
--namespace polaris \
|
||||
--create-namespace \
|
||||
--set dashboard.enabled=true
|
||||
```
|
||||
|
||||
**503 Service Unavailable:**
|
||||
|
||||
```bash
|
||||
# Check pod status
|
||||
kubectl -n polaris get pods
|
||||
|
||||
# Check pod logs
|
||||
kubectl -n polaris logs deployment/polaris-dashboard
|
||||
```
|
||||
|
||||
**Network Error / CORS:**
|
||||
|
||||
```bash
|
||||
# For external Polaris, test CORS
|
||||
curl -v -H "Origin: https://headlamp.example.com" \
|
||||
https://polaris.example.com/results.json
|
||||
|
||||
# Check for Access-Control-Allow-Origin header
|
||||
```
|
||||
|
||||
### Refresh Interval Not Working
|
||||
|
||||
**Symptom:** Data doesn't refresh automatically
|
||||
|
||||
**Check:**
|
||||
|
||||
1. Verify setting is saved (localStorage key exists)
|
||||
2. Check browser console for errors
|
||||
3. Verify Polaris is returning data (manual refresh works)
|
||||
4. Ensure you're on a Polaris plugin page (not other Headlamp pages)
|
||||
|
||||
**Debug:**
|
||||
|
||||
```javascript
|
||||
// Check refresh interval
|
||||
console.log(localStorage.getItem('polaris-plugin-refresh-interval'));
|
||||
|
||||
// Should return: "300" (5 minutes), "600" (10 minutes), etc.
|
||||
```
|
||||
|
||||
## Configuration Checklist
|
||||
|
||||
Before going to production, verify:
|
||||
|
||||
- [ ] Refresh interval set appropriately (10-30 min for large clusters)
|
||||
- [ ] Dashboard URL tested and working
|
||||
- [ ] Connection test passes
|
||||
- [ ] RBAC permissions granted (see [RBAC Guide](rbac-permissions.md))
|
||||
- [ ] NetworkPolicies allow API server → Polaris (if using network policies)
|
||||
- [ ] CORS configured (if using external Polaris)
|
||||
- [ ] Browser localStorage enabled
|
||||
- [ ] Settings persist across browser restarts
|
||||
|
||||
## Future Configuration Options
|
||||
|
||||
**Planned features:**
|
||||
|
||||
- ConfigMap-based settings (server-side, not localStorage)
|
||||
- Per-cluster settings (multi-cluster Headlamp support)
|
||||
- Webhook notifications for score changes
|
||||
- Custom check severity overrides
|
||||
- Exemption management UI (requires RBAC PATCH permission)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Features Guide](features.md)** - Learn about all plugin features
|
||||
- **[RBAC Permissions](rbac-permissions.md)** - Configure advanced RBAC for token-auth, OIDC
|
||||
- **[Troubleshooting](../troubleshooting/README.md)** - Diagnose common configuration issues
|
||||
|
||||
## References
|
||||
|
||||
- [Polaris Configuration](https://polaris.docs.fairwinds.com/customization/checks/)
|
||||
- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/)
|
||||
- [CORS Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
|
||||
@@ -0,0 +1,288 @@
|
||||
# Features Guide
|
||||
|
||||
Learn about all features in the Headlamp Polaris Plugin.
|
||||
|
||||
## Overview Dashboard
|
||||
|
||||
The main dashboard provides cluster-wide visibility. Navigate to **Polaris → Overview**.
|
||||
|
||||
### Cluster Score Gauge
|
||||
|
||||
Overall cluster health score (0-100%) with color-coded status:
|
||||
|
||||
- **Green (≥80%):** Excellent - cluster follows best practices
|
||||
- **Yellow (50-79%):** Needs attention - some issues present
|
||||
- **Red (<50%):** Critical - significant security/reliability concerns
|
||||
|
||||
The score is calculated as: `(passing checks / total checks) × 100`
|
||||
|
||||
### Check Distribution
|
||||
|
||||
Visual breakdown of all Polaris checks across the cluster:
|
||||
|
||||
- **Pass** - Checks that passed (green)
|
||||
- **Warning** - Failed checks with warning severity (yellow)
|
||||
- **Danger** - Failed checks with danger severity (red)
|
||||
- **Skipped** - Checks with severity "ignore" (gray)
|
||||
|
||||
**Note:** Skipped count only reflects checks with `Severity: "ignore"` from Polaris config. Annotation-based exemptions (e.g., `polaris.fairwinds.com/cpuLimitsMissing-exempt: "true"`) are not included. See "View in Polaris Dashboard" link for full exemption count.
|
||||
|
||||
### Top 10 Failing Checks
|
||||
|
||||
Most common issues across the entire cluster:
|
||||
|
||||
- Grouped by check type (e.g., "CPU Limits Missing", "Host IPC Set")
|
||||
- Shows count and severity
|
||||
- Helps identify cluster-wide patterns
|
||||
- Click check name for details
|
||||
|
||||
### Cluster Statistics
|
||||
|
||||
Quick cluster metadata:
|
||||
|
||||
- **Polaris Version** - e.g., "4.2.0"
|
||||
- **Last Audit** - ISO 8601 timestamp of most recent audit
|
||||
- **Nodes** - Total node count
|
||||
- **Pods** - Total pod count
|
||||
- **Namespaces** - Total namespace count
|
||||
- **Controllers** - Total workload controller count
|
||||
|
||||
### Manual Refresh
|
||||
|
||||
Click the refresh button to fetch the latest audit data immediately (bypasses auto-refresh interval).
|
||||
|
||||
## Namespace Views
|
||||
|
||||
### Namespaces List
|
||||
|
||||
Navigate to **Polaris → Namespaces** to see all namespaces with audit results.
|
||||
|
||||
**Table Columns:**
|
||||
|
||||
- **Namespace** - Clickable namespace name (opens detail panel)
|
||||
- **Score** - Per-namespace score with color coding
|
||||
- **Pass** - Passing checks count
|
||||
- **Warning** - Warning severity failures
|
||||
- **Danger** - Danger severity failures
|
||||
- **Skipped** - Skipped checks count
|
||||
|
||||
**Sorting:** Table is sortable by any column. Default sort is by score (lowest first) to surface problematic namespaces.
|
||||
|
||||
### Namespace Detail Panel
|
||||
|
||||
Click any namespace to open a 1000px-wide side panel with detailed information.
|
||||
|
||||
**Features:**
|
||||
|
||||
- **Namespace Score** - Color-coded score gauge
|
||||
- **Check Counts** - Pass/Warning/Danger/Skipped breakdown
|
||||
- **Resource Table** - Per-resource audit results:
|
||||
- Resource name
|
||||
- Resource kind (Deployment, StatefulSet, DaemonSet, Job, CronJob)
|
||||
- Pass/Warning/Danger counts per resource
|
||||
- **External Link** - "View in Polaris Dashboard" button for full Polaris UI
|
||||
- **URL Hash Navigation** - Browser back/forward works with drawer state
|
||||
- **Keyboard Shortcut** - Press **Escape** to close panel
|
||||
- **Click-to-Close** - Click backdrop to close panel
|
||||
|
||||
The drawer respects Headlamp's theme (light/dark mode).
|
||||
|
||||
## Inline Resource Audits
|
||||
|
||||
Polaris audit results automatically appear on resource detail pages.
|
||||
|
||||
### Supported Resources
|
||||
|
||||
Inline audit sections appear on:
|
||||
|
||||
- Deployments
|
||||
- StatefulSets
|
||||
- DaemonSets
|
||||
- Jobs
|
||||
- CronJobs
|
||||
|
||||
### What's Shown
|
||||
|
||||
**Compact Audit Section:**
|
||||
|
||||
- **Score Badge** - Color-coded score
|
||||
- **Check Counts** - Pass/Warning/Danger summary
|
||||
- **Failing Checks Table** - Only failed checks listed:
|
||||
- Check name (human-readable)
|
||||
- Severity badge (Warning/Danger)
|
||||
- Message describing the issue
|
||||
- **Link to Full Report** - Navigate to namespace detail for complete audit
|
||||
|
||||
**If no audit data:** Shows "No audit data available for this resource" message.
|
||||
|
||||
## App Bar Score Badge
|
||||
|
||||
Top-right corner of Headlamp shows a persistent cluster score badge.
|
||||
|
||||
**Features:**
|
||||
|
||||
- **Color-Coded Chip** - Green/Yellow/Red based on score
|
||||
- **Shield Icon** - Visual indicator
|
||||
- **Score Percentage** - e.g., "85%"
|
||||
- **Clickable** - Click to navigate to Polaris overview
|
||||
- **Real-Time Updates** - Updates on auto-refresh interval
|
||||
- **Always Visible** - Appears on all Headlamp pages
|
||||
|
||||
**Example:** Shield icon with "85%" (green chip)
|
||||
|
||||
## Settings & Configuration
|
||||
|
||||
Access plugin settings via **Settings → Plugins → Polaris**.
|
||||
|
||||
### Refresh Interval
|
||||
|
||||
Controls how often the plugin fetches new audit data.
|
||||
|
||||
**Options:**
|
||||
|
||||
- 1 minute - Most frequent (highest API load)
|
||||
- 5 minutes - **Default** (recommended)
|
||||
- 10 minutes - Moderate refresh rate
|
||||
- 30 minutes - Light load (large clusters)
|
||||
|
||||
**Impact:**
|
||||
|
||||
- Affects all views (dashboard, namespaces, inline audits, app bar badge)
|
||||
- Longer intervals reduce Kubernetes API audit logging
|
||||
- Changes take effect immediately (no restart required)
|
||||
|
||||
See [Configuration Guide](configuration.md) for details.
|
||||
|
||||
### Dashboard URL
|
||||
|
||||
Specifies which Polaris instance to connect to.
|
||||
|
||||
**Default:** Kubernetes service proxy path
|
||||
|
||||
```
|
||||
/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/
|
||||
```
|
||||
|
||||
**Custom Options:**
|
||||
|
||||
- External Polaris: `https://polaris.example.com/`
|
||||
- Different namespace: `/api/v1/namespaces/custom-ns/services/polaris-dashboard:80/proxy/`
|
||||
|
||||
**Test Connection Button:** Verifies connectivity before saving.
|
||||
|
||||
See [Configuration Guide](configuration.md) for advanced setup.
|
||||
|
||||
## Dark Mode Support
|
||||
|
||||
Full theme adaptation for Headlamp's light and dark modes.
|
||||
|
||||
**Features:**
|
||||
|
||||
- **Auto Dark Mode** - Respects system preference when Headlamp uses it
|
||||
- **Theme Toggle** - Adapts when you change Headlamp theme
|
||||
- **All UI Elements** - Drawer backgrounds, tables, buttons, badges, score gauge
|
||||
- **CSS Variables** - Uses MUI theme variables (`--mui-palette-*`)
|
||||
|
||||
**No configuration required** - works automatically with Headlamp's theme.
|
||||
|
||||
## Exemption Management
|
||||
|
||||
**Status:** Planned feature (UI components exist but not fully integrated)
|
||||
|
||||
**Future Capability:**
|
||||
|
||||
- View current exemptions on resources
|
||||
- Add exemptions for specific failing checks
|
||||
- Remove exemptions
|
||||
- Apply via annotation patches (`polaris.fairwinds.com/*-exempt`)
|
||||
|
||||
This feature requires additional RBAC permissions (PATCH on workload resources) and is not yet enabled by default.
|
||||
|
||||
## Data Refresh Behavior
|
||||
|
||||
**Initial Load:**
|
||||
|
||||
- Data fetched when you first navigate to any Polaris view
|
||||
- Shared across all views via React Context (no duplicate fetches)
|
||||
- Loading spinner displayed during initial fetch
|
||||
|
||||
**Auto-Refresh:**
|
||||
|
||||
- Configured via Settings → Plugins → Polaris
|
||||
- Default: 5 minutes
|
||||
- Triggers background fetch without disrupting UI
|
||||
|
||||
**Manual Refresh:**
|
||||
|
||||
- Click refresh button on overview dashboard
|
||||
- Forces immediate data fetch
|
||||
- Updates all views simultaneously
|
||||
|
||||
**Error Handling:**
|
||||
|
||||
- 403 errors show RBAC permission guidance
|
||||
- 404/503 errors indicate Polaris not installed
|
||||
- Network errors show generic failure with retry suggestion
|
||||
|
||||
## Browser Requirements
|
||||
|
||||
**Supported Browsers:**
|
||||
|
||||
- Chrome/Chromium 80+
|
||||
- Firefox 75+
|
||||
- Safari 13.1+
|
||||
- Edge 80+
|
||||
|
||||
**Required:**
|
||||
|
||||
- JavaScript enabled
|
||||
- localStorage enabled (for settings persistence)
|
||||
- Cookies enabled (for Headlamp session)
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
**Bundle Size:** ~27 KB minified (gzip: ~7.6 KB)
|
||||
|
||||
**Data Volume:** Depends on cluster size. Example:
|
||||
|
||||
- Small cluster (50 resources): ~100 KB JSON
|
||||
- Medium cluster (500 resources): ~1 MB JSON
|
||||
- Large cluster (5000 resources): ~10 MB JSON
|
||||
|
||||
**Rendering Performance:**
|
||||
|
||||
- Handles up to 100 namespaces without virtual scrolling
|
||||
- Namespace detail drawer renders instantly for up to 500 resources
|
||||
- React Context prevents unnecessary re-fetches
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Skipped Count Incomplete
|
||||
|
||||
The "Skipped" count only reflects checks with `Severity: "ignore"` in Polaris configuration. Annotation-based exemptions are not counted because:
|
||||
|
||||
- Polaris API omits exempted checks from `results.json`
|
||||
- Native Polaris dashboard computes skipped count by querying raw Kubernetes resources
|
||||
- Plugin only has access to processed audit results (not raw resources)
|
||||
|
||||
**Workaround:** Use "View in Polaris Dashboard" link for accurate exemption count.
|
||||
|
||||
### Single Cluster Support
|
||||
|
||||
Plugin shows data for the current Headlamp cluster only. Multi-cluster aggregation is not supported.
|
||||
|
||||
### No Real-Time Updates
|
||||
|
||||
Data refreshes on interval (1-30 minutes), not real-time. Polaris dashboard doesn't support WebSocket/SSE.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Configuration Guide](configuration.md)** - Customize refresh intervals, dashboard URLs, test connections
|
||||
- **[RBAC Permissions](rbac-permissions.md)** - Advanced RBAC setup for token-auth, OIDC, multi-user
|
||||
- **[Troubleshooting](../troubleshooting/README.md)** - Quick diagnosis for common issues
|
||||
|
||||
## References
|
||||
|
||||
- [Fairwinds Polaris Documentation](https://polaris.docs.fairwinds.com/)
|
||||
- [Headlamp Documentation](https://headlamp.dev/docs/)
|
||||
- [Kubernetes Best Practices](https://kubernetes.io/docs/concepts/configuration/overview/)
|
||||
@@ -0,0 +1,641 @@
|
||||
# RBAC Permissions Guide
|
||||
|
||||
Understanding and configuring RBAC for the Headlamp Polaris Plugin.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
The plugin requires **one permission** to function:
|
||||
|
||||
| Verb | API Group | Resource | Resource Name | Namespace |
|
||||
| ----- | ----------- | ---------------- | ------------------- | --------- |
|
||||
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
|
||||
|
||||
This allows the plugin to fetch audit results via the Kubernetes service proxy.
|
||||
|
||||
**Why this permission?**
|
||||
|
||||
- Plugin accesses Polaris through Kubernetes API server's service proxy
|
||||
- Service proxy requires `get` verb on `services/proxy` resource
|
||||
- Scoped to specific service (`polaris-dashboard`) for security
|
||||
- Read-only (no write operations)
|
||||
|
||||
## Standard Setup (Service Account Mode)
|
||||
|
||||
**Best for:** Headlamp running with a fixed service account in the cluster (in-cluster mode)
|
||||
|
||||
This is the most common deployment pattern for production Headlamp instances.
|
||||
|
||||
### Step 1: Create Role
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: polaris
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp-polaris-plugin
|
||||
app.kubernetes.io/component: rbac
|
||||
rules:
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
|
||||
- **Role** (not ClusterRole) - Scoped to `polaris` namespace only
|
||||
- **resourceNames** - Restricts access to `polaris-dashboard` service only
|
||||
- **verbs: ["get"]** - Read-only permission
|
||||
|
||||
### Step 2: Create RoleBinding
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: polaris
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp-polaris-plugin
|
||||
app.kubernetes.io/component: rbac
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp # Adjust to your Headlamp SA name
|
||||
namespace: kube-system # Adjust to Headlamp's namespace
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
**Adjust for your environment:**
|
||||
|
||||
- `subjects[0].name` - Your Headlamp service account name (often `headlamp`)
|
||||
- `subjects[0].namespace` - Namespace where Headlamp runs (often `kube-system`)
|
||||
|
||||
### Step 3: Apply and Verify
|
||||
|
||||
```bash
|
||||
# Apply RBAC manifests
|
||||
kubectl apply -f polaris-rbac.yaml
|
||||
|
||||
# Verify Role exists
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
|
||||
# Verify RoleBinding exists
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected output: yes
|
||||
```
|
||||
|
||||
## Token-Auth Mode (Per-User Permissions)
|
||||
|
||||
**Best for:** Headlamp configured for user-supplied tokens, OIDC, or external authentication
|
||||
|
||||
In token-auth mode, **each user's own identity** is used for Kubernetes API requests (not a shared service account).
|
||||
|
||||
### Why Per-User RBAC?
|
||||
|
||||
With service account mode:
|
||||
|
||||
- Single RoleBinding grants access to all Headlamp users
|
||||
- Kubernetes sees all requests as `system:serviceaccount:kube-system:headlamp`
|
||||
|
||||
With token-auth mode:
|
||||
|
||||
- Each user's own token (OIDC, kubeconfig) is used
|
||||
- Kubernetes sees requests as `user@example.com` or `system:serviceaccount:team-ns:user-sa`
|
||||
- **Each user needs individual RBAC permissions**
|
||||
|
||||
### Option 1: Grant to All Authenticated Users
|
||||
|
||||
**Use case:** Everyone with cluster access should see Polaris data
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: authenticated-users-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: system:authenticated # All authenticated users
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
**Security consideration:** This grants Polaris access to **everyone** with cluster access. Ensure Polaris data is not sensitive in your environment.
|
||||
|
||||
### Option 2: Grant to Specific Users
|
||||
|
||||
**Use case:** Fine-grained control, only SRE/DevOps teams
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: sre-team-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: User
|
||||
name: alice@example.com
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
- kind: User
|
||||
name: bob@example.com
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
**Maintenance:** Add/remove users as team membership changes.
|
||||
|
||||
### Option 3: Grant to OIDC Groups
|
||||
|
||||
**Use case:** OIDC provider with group claims (most flexible)
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: oidc-group-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: sre-team # OIDC group claim
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
- kind: Group
|
||||
name: devops-team
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- OIDC provider must include group claims in token
|
||||
- Headlamp must be configured to extract groups from OIDC token
|
||||
- Group names must match exactly (case-sensitive)
|
||||
|
||||
**Example OIDC group claim:**
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "user@example.com",
|
||||
"groups": ["sre-team", "developers"]
|
||||
}
|
||||
```
|
||||
|
||||
### Verify User Permission
|
||||
|
||||
```bash
|
||||
# Test specific user
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=user@example.com \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Test OIDC group
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=user@example.com \
|
||||
--as-group=sre-team \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected output: yes (if bound correctly)
|
||||
```
|
||||
|
||||
## Multi-Namespace Polaris Deployments
|
||||
|
||||
**Scenario:** Polaris deployed in multiple namespaces (e.g., per-team Polaris instances)
|
||||
|
||||
### Create Role per Namespace
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: team-a-polaris # First Polaris instance
|
||||
rules:
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-proxy-reader
|
||||
namespace: team-b-polaris # Second Polaris instance
|
||||
rules:
|
||||
- apiGroups: ['']
|
||||
resources: ['services/proxy']
|
||||
resourceNames: ['polaris-dashboard']
|
||||
verbs: ['get']
|
||||
```
|
||||
|
||||
### Create RoleBindings per Namespace
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: team-a-polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: headlamp-polaris-proxy
|
||||
namespace: team-b-polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
**Plugin configuration:**
|
||||
Users can switch between instances via **Settings → Plugins → Polaris → Dashboard URL**.
|
||||
|
||||
## Network Security
|
||||
|
||||
### NetworkPolicy Requirements
|
||||
|
||||
If the `polaris` namespace enforces NetworkPolicies, ensure the Kubernetes API server can reach Polaris dashboard.
|
||||
|
||||
**Why?** The Kubernetes API server proxies plugin requests, so it needs network access to Polaris.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-apiserver-to-polaris
|
||||
namespace: polaris
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: polaris
|
||||
app.kubernetes.io/component: dashboard
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow from Kubernetes API server
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: kube-apiserver
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
```
|
||||
|
||||
**Note:** Headlamp pod itself does NOT need direct network access to Polaris (API server does the proxying).
|
||||
|
||||
### Service Mesh Considerations
|
||||
|
||||
If using Istio, Linkerd, or other service meshes:
|
||||
|
||||
**No special configuration needed** - Service proxy requests bypass the mesh (go through API server).
|
||||
|
||||
## OAuth2 / OIDC Integration
|
||||
|
||||
When using OAuth2/OIDC authentication with Headlamp:
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **User authenticates** with OIDC provider (e.g., Google, Okta, Keycloak)
|
||||
2. **OIDC provider issues token** with user identity and group claims
|
||||
3. **Headlamp receives token** and passes it to Kubernetes API
|
||||
4. **Plugin makes request** using user's token (not service account)
|
||||
5. **Kubernetes RBAC evaluates** user's permissions against RoleBinding
|
||||
|
||||
### Required Configuration
|
||||
|
||||
**Headlamp Helm values:**
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: HEADLAMP_CONFIG_OIDC_CLIENT_ID
|
||||
value: 'headlamp'
|
||||
- name: HEADLAMP_CONFIG_OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: headlamp-oidc
|
||||
key: client-secret
|
||||
- name: HEADLAMP_CONFIG_OIDC_ISSUER_URL
|
||||
value: 'https://auth.example.com/realms/kubernetes'
|
||||
- name: HEADLAMP_CONFIG_OIDC_SCOPES
|
||||
value: 'openid,profile,email,groups'
|
||||
```
|
||||
|
||||
**RBAC for OIDC users:**
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: oidc-polaris-proxy
|
||||
namespace: polaris
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: kubernetes-admins # OIDC group claim
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### Testing OIDC Permissions
|
||||
|
||||
```bash
|
||||
# Simulate OIDC user with group
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=user@example.com \
|
||||
--as-group=kubernetes-admins \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected: yes
|
||||
```
|
||||
|
||||
## Audit Logging Considerations
|
||||
|
||||
Every plugin data fetch creates a Kubernetes API audit log entry.
|
||||
|
||||
### Example Audit Log
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "Event",
|
||||
"apiVersion": "audit.k8s.io/v1",
|
||||
"level": "Metadata",
|
||||
"verb": "get",
|
||||
"user": {
|
||||
"username": "system:serviceaccount:kube-system:headlamp"
|
||||
},
|
||||
"sourceIPs": ["10.96.0.1"],
|
||||
"objectRef": {
|
||||
"resource": "services",
|
||||
"subresource": "proxy",
|
||||
"namespace": "polaris",
|
||||
"name": "polaris-dashboard",
|
||||
"apiVersion": "v1"
|
||||
},
|
||||
"responseStatus": {
|
||||
"code": 200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Volume Estimates
|
||||
|
||||
**Per user:**
|
||||
|
||||
- 1 refresh per 5 minutes = 288 requests/day
|
||||
- 1 refresh per 30 minutes = 48 requests/day
|
||||
|
||||
**Cluster-wide:**
|
||||
|
||||
- 10 concurrent users × 5-minute refresh = 2,880 audit logs/day
|
||||
- 100 concurrent users × 30-minute refresh = 4,800 audit logs/day
|
||||
|
||||
### Reducing Audit Volume
|
||||
|
||||
**Option 1: Increase refresh interval**
|
||||
|
||||
```
|
||||
Settings → Plugins → Polaris → Refresh Interval → 30 minutes
|
||||
```
|
||||
|
||||
**Option 2: Adjust audit policy level**
|
||||
|
||||
```yaml
|
||||
# kube-apiserver audit policy
|
||||
apiVersion: audit.k8s.io/v1
|
||||
kind: Policy
|
||||
rules:
|
||||
- level: Metadata # Log metadata only, not full request/response
|
||||
verbs: ['get']
|
||||
resources:
|
||||
- group: ''
|
||||
resources: ['services/proxy']
|
||||
namespaces: ['polaris']
|
||||
```
|
||||
|
||||
**Option 3: Filter audit logs**
|
||||
If using a log aggregator (e.g., Elasticsearch), create filters to exclude or downsample Polaris proxy requests.
|
||||
|
||||
## Troubleshooting RBAC
|
||||
|
||||
### "403 Forbidden" Error in Plugin
|
||||
|
||||
**Symptom:** Plugin shows "Access denied (403)" error when loading data
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. **Check Role exists:**
|
||||
|
||||
```bash
|
||||
kubectl -n polaris get role polaris-proxy-reader
|
||||
```
|
||||
|
||||
If missing: Apply Role manifest
|
||||
|
||||
2. **Check RoleBinding exists:**
|
||||
|
||||
```bash
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
```
|
||||
|
||||
If missing: Apply RoleBinding manifest
|
||||
|
||||
3. **Test permission:**
|
||||
|
||||
```bash
|
||||
# Service account mode
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Token-auth mode (replace with your username)
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=user@example.com \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
```
|
||||
|
||||
Expected: `yes`
|
||||
|
||||
4. **Verify RoleBinding subjects match:**
|
||||
```bash
|
||||
kubectl -n polaris get rolebinding headlamp-polaris-proxy -o yaml
|
||||
```
|
||||
Check `subjects[].name` and `subjects[].namespace` match your Headlamp SA or user
|
||||
|
||||
### "404 Not Found" Error
|
||||
|
||||
**This is NOT an RBAC issue.** 404 means Polaris service doesn't exist.
|
||||
|
||||
**Check:**
|
||||
|
||||
```bash
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
```
|
||||
|
||||
If missing, install Polaris with dashboard enabled.
|
||||
|
||||
### Permission Test Passes but Plugin Still Shows 403
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
1. **Wrong namespace in RoleBinding:**
|
||||
|
||||
- RoleBinding must be in `polaris` namespace (where the service is)
|
||||
- Common mistake: Creating RoleBinding in `kube-system`
|
||||
|
||||
2. **Wrong resourceName:**
|
||||
|
||||
- Must match service name exactly: `polaris-dashboard`
|
||||
- Check: `kubectl -n polaris get svc`
|
||||
|
||||
3. **Browser caching old 403:**
|
||||
|
||||
- Hard refresh browser: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
|
||||
|
||||
4. **Token expired (OIDC mode):**
|
||||
- Re-authenticate with OIDC provider
|
||||
- Check token expiration in browser DevTools (Application → Session Storage)
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### 1. Use Namespaced Roles (Not ClusterRoles)
|
||||
|
||||
✅ **Good:**
|
||||
|
||||
```yaml
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: polaris
|
||||
```
|
||||
|
||||
❌ **Bad:**
|
||||
|
||||
```yaml
|
||||
kind: ClusterRole
|
||||
# Grants access to all namespaces
|
||||
```
|
||||
|
||||
**Why:** Namespaced Roles limit scope to `polaris` namespace only. ClusterRoles would allow access to service proxies in all namespaces.
|
||||
|
||||
### 2. Always Specify resourceNames
|
||||
|
||||
✅ **Good:**
|
||||
|
||||
```yaml
|
||||
resourceNames: ['polaris-dashboard']
|
||||
```
|
||||
|
||||
❌ **Bad:**
|
||||
|
||||
```yaml
|
||||
resourceNames: [] # Allows access to ALL services
|
||||
```
|
||||
|
||||
**Why:** `resourceNames` restricts permission to a specific service. Without it, the binding grants access to proxy all services in the namespace.
|
||||
|
||||
### 3. Use Read-Only Verb
|
||||
|
||||
✅ **Good:**
|
||||
|
||||
```yaml
|
||||
verbs: ['get']
|
||||
```
|
||||
|
||||
❌ **Bad:**
|
||||
|
||||
```yaml
|
||||
verbs: ['get', 'create', 'update', 'delete']
|
||||
```
|
||||
|
||||
**Why:** Plugin only needs `get` to fetch audit results. Additional verbs violate principle of least privilege.
|
||||
|
||||
### 4. Review Bindings Quarterly
|
||||
|
||||
- Remove users who no longer need access
|
||||
- Update OIDC group bindings when org structure changes
|
||||
- Audit who has access: `kubectl -n polaris get rolebindings -o yaml`
|
||||
|
||||
### 5. Monitor Audit Logs
|
||||
|
||||
Set alerts for:
|
||||
|
||||
- Unusual access patterns (e.g., 403 spikes = permission issues)
|
||||
- High request volume (e.g., misconfigured refresh interval)
|
||||
- Access from unexpected users (security monitoring)
|
||||
|
||||
### 6. Avoid Wildcard Permissions
|
||||
|
||||
❌ **Never do this:**
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
- apiGroups: ['*']
|
||||
resources: ['*']
|
||||
verbs: ['*']
|
||||
```
|
||||
|
||||
This grants cluster-admin equivalent permissions. Always use specific resources and verbs.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Features Guide](features.md)** - Learn about plugin capabilities
|
||||
- **[Configuration Guide](configuration.md)** - Configure refresh intervals and dashboard URL
|
||||
- **[Troubleshooting RBAC](../troubleshooting/rbac-issues.md)** - Detailed RBAC debugging
|
||||
|
||||
## References
|
||||
|
||||
- [Kubernetes RBAC Documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
|
||||
- [Service Proxy Authorization](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/)
|
||||
- [Headlamp OIDC Configuration](https://headlamp.dev/docs/latest/installation/in-cluster/configuration/#oidc-configuration)
|
||||
- [Kubernetes Audit Logging](https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/)
|
||||
+242
-6
@@ -4,14 +4,20 @@ Playwright-based smoke tests that validate the Polaris plugin against a live Hea
|
||||
|
||||
## CI
|
||||
|
||||
E2E tests run automatically in Gitea Actions on pushes to `main` and pull requests. The workflow (`.gitea/workflows/e2e.yaml`) uses Authentik OIDC for authentication via repo secrets.
|
||||
E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`) uses either Authentik OIDC or token-based authentication via repository secrets.
|
||||
|
||||
### Required Gitea secrets
|
||||
### Required GitHub Secrets
|
||||
|
||||
| Secret | Description |
|
||||
| -------------------- | -------------------------------------------------------------- |
|
||||
| `AUTHENTIK_USERNAME` | Authentik email or username for a CI user with Headlamp access |
|
||||
| `AUTHENTIK_PASSWORD` | Password for that user |
|
||||
Configure these in GitHub repository settings (Settings → Secrets and variables → Actions):
|
||||
|
||||
| Secret | Required | Description |
|
||||
| -------------------- | -------- | -------------------------------------------------------------- |
|
||||
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to `https://headlamp.animaniacs.farh.net`) |
|
||||
| `AUTHENTIK_USERNAME` | OIDC | Authentik email or username for a CI user with Headlamp access |
|
||||
| `AUTHENTIK_PASSWORD` | OIDC | Password for that user |
|
||||
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
|
||||
|
||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
||||
|
||||
## Running Locally
|
||||
|
||||
@@ -56,3 +62,233 @@ Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` or `HEADLAMP_TOKEN`. OIDC
|
||||
- **Namespace detail** — Clicking a namespace shows its score and resource table
|
||||
|
||||
These are smoke tests against real cluster data. They verify the plugin loads and renders without errors, not specific data values.
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### Current Tests (`polaris.spec.ts`)
|
||||
|
||||
1. **`sidebar contains Polaris entry`**
|
||||
- Verifies Polaris appears in the navigation sidebar
|
||||
- Ensures plugin successfully registered sidebar entry
|
||||
|
||||
2. **`overview page renders cluster score`**
|
||||
- Navigates to `/c/main/polaris`
|
||||
- Checks for "Polaris — Overview" heading
|
||||
- Verifies cluster score percentage is displayed
|
||||
- Validates data fetching and rendering
|
||||
|
||||
3. **`namespaces page renders table with namespace buttons`**
|
||||
- Navigates to `/c/main/polaris/namespaces`
|
||||
- Checks for "Polaris — Namespaces" heading
|
||||
- Verifies table is visible with at least one row
|
||||
- Ensures namespace buttons are clickable
|
||||
|
||||
4. **`namespace detail drawer opens from table button`**
|
||||
- Clicks first namespace button in table
|
||||
- Verifies drawer opens with namespace name in heading
|
||||
- Checks "Namespace Score" section is visible
|
||||
- Confirms "Resources" table is displayed
|
||||
- Validates URL hash is updated with namespace name
|
||||
|
||||
5. **`namespace detail drawer closes with Escape key`**
|
||||
- Opens namespace drawer
|
||||
- Presses Escape key
|
||||
- Verifies drawer closes
|
||||
- Checks URL hash is cleared
|
||||
|
||||
6. **`namespace detail drawer opens from URL hash`**
|
||||
- Navigates directly to `/c/main/polaris/namespaces#<namespace>`
|
||||
- Verifies drawer automatically opens
|
||||
- Checks namespace details are displayed
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Cluster Requirements
|
||||
|
||||
1. **Polaris Deployment**
|
||||
```bash
|
||||
# Verify Polaris is running
|
||||
kubectl -n polaris get pods
|
||||
kubectl -n polaris get svc polaris-dashboard
|
||||
```
|
||||
|
||||
2. **Polaris Audit Data**
|
||||
```bash
|
||||
# Check if Polaris has generated audit results
|
||||
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq '.AuditTime'
|
||||
```
|
||||
|
||||
3. **RBAC Permissions**
|
||||
- Headlamp service account (or test user) needs `get` on `services/proxy` for `polaris-dashboard`
|
||||
- See main README for RBAC setup
|
||||
|
||||
### Local Setup
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies
|
||||
npm install
|
||||
npx playwright install chromium
|
||||
|
||||
# 2. Create .env file (optional, for persistent config)
|
||||
cp .env.example .env
|
||||
|
||||
# 3. Set environment variables
|
||||
export HEADLAMP_URL=https://your-headlamp-instance.com
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system)
|
||||
|
||||
# 4. Run tests
|
||||
npm run e2e
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### Run in Headed Mode
|
||||
|
||||
See the browser UI while tests run:
|
||||
|
||||
```bash
|
||||
npm run e2e:headed
|
||||
```
|
||||
|
||||
### Enable Debug Mode
|
||||
|
||||
Step through tests with Playwright Inspector:
|
||||
|
||||
```bash
|
||||
npx playwright test --debug
|
||||
```
|
||||
|
||||
### Generate Trace
|
||||
|
||||
Record full trace for failed tests:
|
||||
|
||||
```bash
|
||||
npx playwright test --trace on
|
||||
npx playwright show-trace test-results/<test-name>/trace.zip
|
||||
```
|
||||
|
||||
### Screenshot on Failure
|
||||
|
||||
Tests automatically capture screenshots on failure in `test-results/`
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Auth fails with "Sign In button not found":**
|
||||
- Check HEADLAMP_URL is correct
|
||||
- Verify Headlamp is accessible
|
||||
- Ensure OIDC is configured if using Authentik
|
||||
|
||||
**Polaris sidebar entry not found:**
|
||||
- Plugin may not be installed: Check Settings → Plugins in Headlamp
|
||||
- Plugin may have failed to load: Check browser console
|
||||
- Clear browser cache and hard refresh
|
||||
|
||||
**Cluster score not displayed:**
|
||||
- Polaris may not have audit data yet
|
||||
- Check Polaris is running: `kubectl -n polaris get pods`
|
||||
- Verify service proxy: `kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
|
||||
|
||||
**Namespace table empty:**
|
||||
- Polaris hasn't run audit yet (wait a few minutes)
|
||||
- Check Polaris logs: `kubectl -n polaris logs -l app.kubernetes.io/name=polaris`
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
### Example: Testing Plugin Settings
|
||||
|
||||
```typescript
|
||||
test('plugin settings page shows Polaris configuration', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Find and click Polaris plugin
|
||||
await page.getByText('headlamp-polaris-plugin').click();
|
||||
|
||||
// Check settings are visible
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible();
|
||||
await expect(page.getByText('Refresh Interval')).toBeVisible();
|
||||
await expect(page.getByText('Dashboard URL')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
### Example: Testing App Bar Badge
|
||||
|
||||
```typescript
|
||||
test('app bar displays Polaris score badge', async ({ page }) => {
|
||||
await page.goto('/c/main');
|
||||
|
||||
// Badge should be visible in app bar
|
||||
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
|
||||
await expect(badge).toBeVisible();
|
||||
|
||||
// Clicking should navigate to overview
|
||||
await badge.click();
|
||||
await expect(page).toHaveURL(/\/c\/main\/polaris$/);
|
||||
});
|
||||
```
|
||||
|
||||
### Example: Testing Dark Mode
|
||||
|
||||
```typescript
|
||||
test('plugin UI adapts to dark mode', async ({ page }) => {
|
||||
await page.goto('/c/main/polaris');
|
||||
|
||||
// Toggle dark mode
|
||||
await page.getByRole('button', { name: /theme/i }).click();
|
||||
|
||||
// Check background color changes
|
||||
const body = page.locator('body');
|
||||
await expect(body).toHaveCSS('background-color', 'rgb(18, 18, 18)');
|
||||
|
||||
// Plugin components should adapt
|
||||
const sectionBox = page.locator('[class*="MuiPaper"]').first();
|
||||
await expect(sectionBox).not.toHaveCSS('background-color', 'rgb(255, 255, 255)');
|
||||
});
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
Tests run automatically in GitHub Actions on pushes to `main` and pull requests. See `.github/workflows/e2e.yaml` for workflow configuration.
|
||||
|
||||
### Required Secrets
|
||||
|
||||
Configure these in GitHub repository settings (Settings → Secrets and variables → Actions):
|
||||
|
||||
- `HEADLAMP_URL` (optional): Headlamp instance URL
|
||||
- `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` (for OIDC auth)
|
||||
- OR `HEADLAMP_TOKEN` (for token-based auth)
|
||||
|
||||
### Workflow Overview
|
||||
|
||||
1. Checkout code
|
||||
2. Setup Node.js 20 with npm cache
|
||||
3. Install dependencies (`npm ci`)
|
||||
4. Install Playwright browsers (`chromium` only)
|
||||
5. Run auth setup (creates session in `e2e/.auth/state.json`)
|
||||
6. Run all E2E tests
|
||||
7. Upload artifacts on failure:
|
||||
- `playwright-report/` - HTML test report
|
||||
- `test-results/` - Screenshots, traces, videos
|
||||
|
||||
### Manual Trigger
|
||||
|
||||
You can manually trigger E2E tests from GitHub Actions:
|
||||
1. Go to Actions → E2E Tests
|
||||
2. Click "Run workflow"
|
||||
3. Select branch and run
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use semantic selectors**: `getByRole`, `getByText` over CSS selectors
|
||||
2. **Wait for visibility**: Use `await expect(...).toBeVisible()` instead of `waitForTimeout`
|
||||
3. **Keep tests independent**: Each test should work in isolation
|
||||
4. **Test user flows**: Complete journeys, not just page loads
|
||||
5. **Clean up state**: Close drawers/modals after tests
|
||||
6. **Use storage state**: Reuse auth across tests (already configured)
|
||||
7. **Parallelize carefully**: Currently disabled due to shared state
|
||||
|
||||
## Resources
|
||||
|
||||
- [Playwright Documentation](https://playwright.dev/)
|
||||
- [Playwright Best Practices](https://playwright.dev/docs/best-practices)
|
||||
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
- [Project Main README](../README.md)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Polaris app bar badge', () => {
|
||||
test('badge displays cluster score in app bar', async ({ page }) => {
|
||||
await page.goto('/c/main');
|
||||
|
||||
// Wait for page to load
|
||||
await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible();
|
||||
|
||||
// Badge should be visible in app bar with score percentage
|
||||
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
|
||||
await expect(badge).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Badge should show shield emoji
|
||||
await expect(badge).toContainText('🛡️');
|
||||
});
|
||||
|
||||
test('clicking badge navigates to overview page', async ({ page }) => {
|
||||
await page.goto('/c/main');
|
||||
|
||||
// Find and click the badge
|
||||
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
|
||||
await expect(badge).toBeVisible({ timeout: 15_000 });
|
||||
await badge.click();
|
||||
|
||||
// Should navigate to Polaris overview
|
||||
await expect(page).toHaveURL(/\/c\/main\/polaris$/);
|
||||
await expect(page.getByRole('heading', { name: 'Polaris — Overview' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('badge color reflects score level', async ({ page }) => {
|
||||
await page.goto('/c/main');
|
||||
|
||||
// Get the badge
|
||||
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
|
||||
await expect(badge).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Extract score from button text
|
||||
const badgeText = await badge.textContent();
|
||||
const scoreMatch = badgeText?.match(/(\d+)%/);
|
||||
expect(scoreMatch).toBeTruthy();
|
||||
|
||||
const score = parseInt(scoreMatch![1]);
|
||||
|
||||
// Check background color matches score level
|
||||
const bgColor = await badge.evaluate(el =>
|
||||
window.getComputedStyle(el).backgroundColor
|
||||
);
|
||||
|
||||
if (score >= 80) {
|
||||
// Green: rgb(76, 175, 80) or #4caf50
|
||||
expect(bgColor).toMatch(/rgb\(76,\s*175,\s*80\)/);
|
||||
} else if (score >= 50) {
|
||||
// Orange: rgb(255, 152, 0) or #ff9800
|
||||
expect(bgColor).toMatch(/rgb\(255,\s*152,\s*0\)/);
|
||||
} else {
|
||||
// Red: rgb(244, 67, 54) or #f44336
|
||||
expect(bgColor).toMatch(/rgb\(244,\s*67,\s*54\)/);
|
||||
}
|
||||
});
|
||||
|
||||
test('badge updates when navigating between clusters', async ({ page }) => {
|
||||
// This test assumes multi-cluster setup; skip if only one cluster
|
||||
await page.goto('/c/main');
|
||||
|
||||
// Get initial badge score
|
||||
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
|
||||
await expect(badge).toBeVisible({ timeout: 15_000 });
|
||||
const initialScore = await badge.textContent();
|
||||
|
||||
// Try to switch clusters (if available)
|
||||
const clusterSelector = page.getByRole('button', { name: /cluster/i });
|
||||
if (await clusterSelector.isVisible()) {
|
||||
// Note: This part will only work in multi-cluster setups
|
||||
// For single-cluster, this test will just verify badge persists
|
||||
await clusterSelector.click();
|
||||
|
||||
// Select different cluster if available
|
||||
const clusterOptions = page.getByRole('menuitem');
|
||||
const count = await clusterOptions.count();
|
||||
|
||||
if (count > 1) {
|
||||
await clusterOptions.nth(1).click();
|
||||
|
||||
// Badge should update or disappear (if new cluster doesn't have Polaris)
|
||||
// This is just verifying no crash occurs
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Badge should still be functional
|
||||
await expect(badge).toBeEnabled();
|
||||
});
|
||||
});
|
||||
+64
-15
@@ -20,42 +20,91 @@ test.describe('Polaris plugin smoke tests', () => {
|
||||
await expect(page.getByText(/%/)).toBeVisible();
|
||||
});
|
||||
|
||||
test('namespaces page renders table with links', async ({ page }) => {
|
||||
test('namespaces page renders table with namespace buttons', async ({ page }) => {
|
||||
await page.goto('/c/main/polaris/namespaces');
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Polaris \u2014 Namespaces' })).toBeVisible();
|
||||
|
||||
// Table should have at least one row with a namespace link
|
||||
// Table should have at least one row with a namespace button
|
||||
const table = page.locator('table');
|
||||
await expect(table).toBeVisible();
|
||||
const rows = table.locator('tbody tr');
|
||||
await expect(rows.first()).toBeVisible();
|
||||
|
||||
// Each namespace row should contain a link
|
||||
const firstLink = rows.first().locator('a');
|
||||
await expect(firstLink).toBeVisible();
|
||||
// Each namespace row should contain a button (now buttons instead of links for drawer)
|
||||
const firstButton = rows.first().locator('button');
|
||||
await expect(firstButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('namespace detail page renders from table link', async ({ page }) => {
|
||||
test('namespace detail drawer opens from table button', async ({ page }) => {
|
||||
await page.goto('/c/main/polaris/namespaces');
|
||||
|
||||
// Click the first namespace link in the table
|
||||
// Click the first namespace button in the table
|
||||
const table = page.locator('table');
|
||||
await expect(table).toBeVisible();
|
||||
const firstLink = table.locator('tbody tr').first().locator('a');
|
||||
const namespaceName = await firstLink.textContent();
|
||||
await firstLink.click();
|
||||
const firstButton = table.locator('tbody tr').first().locator('button');
|
||||
const namespaceName = await firstButton.textContent();
|
||||
await firstButton.click();
|
||||
|
||||
// Detail page should show the namespace name in the heading
|
||||
// Drawer should open and show the namespace name in the heading
|
||||
await expect(
|
||||
page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` })
|
||||
).toBeVisible();
|
||||
|
||||
// "Namespace Score" section should be present in drawer
|
||||
await expect(page.getByText('Namespace Score')).toBeVisible();
|
||||
|
||||
// Resources table should exist in drawer
|
||||
await expect(page.getByText('Resources')).toBeVisible();
|
||||
|
||||
// URL hash should be updated with namespace name
|
||||
await expect(page).toHaveURL(/\/polaris\/namespaces#/);
|
||||
});
|
||||
|
||||
test('namespace detail drawer closes with Escape key', async ({ page }) => {
|
||||
await page.goto('/c/main/polaris/namespaces');
|
||||
|
||||
// Open the drawer by clicking a namespace button
|
||||
const table = page.locator('table');
|
||||
await expect(table).toBeVisible();
|
||||
const firstButton = table.locator('tbody tr').first().locator('button');
|
||||
const namespaceName = await firstButton.textContent();
|
||||
await firstButton.click();
|
||||
|
||||
// Verify drawer is open
|
||||
await expect(
|
||||
page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` })
|
||||
).toBeVisible();
|
||||
|
||||
// Press Escape key
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// Drawer should close (heading should not be visible anymore)
|
||||
await expect(
|
||||
page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` })
|
||||
).not.toBeVisible();
|
||||
|
||||
// URL hash should be cleared
|
||||
await expect(page).toHaveURL(/\/polaris\/namespaces$/);
|
||||
});
|
||||
|
||||
test('namespace detail drawer opens from URL hash', async ({ page }) => {
|
||||
// Get a namespace name first
|
||||
await page.goto('/c/main/polaris/namespaces');
|
||||
const table = page.locator('table');
|
||||
await expect(table).toBeVisible();
|
||||
const firstButton = table.locator('tbody tr').first().locator('button');
|
||||
const namespaceName = await firstButton.textContent();
|
||||
|
||||
// Navigate directly to URL with hash
|
||||
await page.goto(`/c/main/polaris/namespaces#${namespaceName}`);
|
||||
|
||||
// Drawer should automatically open with the namespace details
|
||||
await expect(
|
||||
page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` })
|
||||
).toBeVisible();
|
||||
|
||||
// "Namespace Score" section should be present
|
||||
await expect(page.getByText('Namespace Score')).toBeVisible();
|
||||
|
||||
// Resources table should exist
|
||||
await expect(page.getByText('Resources')).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Polaris plugin settings', () => {
|
||||
test('settings page shows configuration options', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Find Polaris plugin in the list
|
||||
const pluginCard = page.locator('text=headlamp-polaris-plugin').first();
|
||||
await expect(pluginCard).toBeVisible();
|
||||
|
||||
// Click to view settings (if settings are displayed inline, they should already be visible)
|
||||
// Note: Headlamp v0.39.0+ shows settings inline on the plugins page
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
});
|
||||
|
||||
test('refresh interval setting is configurable', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Navigate to Polaris settings
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Find the refresh interval dropdown
|
||||
const intervalSelect = page.locator('select').filter({ hasText: /minute|second/ });
|
||||
await expect(intervalSelect).toBeVisible();
|
||||
|
||||
// Get current value
|
||||
const currentValue = await intervalSelect.inputValue();
|
||||
|
||||
// Change to a different value
|
||||
const newValue = currentValue === '300' ? '600' : '300';
|
||||
await intervalSelect.selectOption(newValue);
|
||||
|
||||
// Value should be updated
|
||||
await expect(intervalSelect).toHaveValue(newValue);
|
||||
});
|
||||
|
||||
test('dashboard URL setting is configurable', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Navigate to Polaris settings
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Find the dashboard URL input
|
||||
const urlInput = page.getByPlaceholder(/polaris-dashboard/);
|
||||
await expect(urlInput).toBeVisible();
|
||||
|
||||
// Input should have the default proxy URL or custom URL
|
||||
const currentUrl = await urlInput.inputValue();
|
||||
expect(currentUrl).toBeTruthy();
|
||||
|
||||
// Examples text should be visible
|
||||
await expect(page.getByText('Examples:')).toBeVisible();
|
||||
await expect(page.getByText(/K8s proxy:/)).toBeVisible();
|
||||
});
|
||||
|
||||
test('connection test button is available', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Navigate to Polaris settings
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Find and verify test connection button
|
||||
const testButton = page.getByRole('button', { name: /test connection/i });
|
||||
await expect(testButton).toBeVisible();
|
||||
await expect(testButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test('connection test works with valid URL', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Navigate to Polaris settings
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Click test connection
|
||||
const testButton = page.getByRole('button', { name: /test connection/i });
|
||||
await testButton.click();
|
||||
|
||||
// Wait for either success or error message
|
||||
// Note: This will succeed if Polaris is accessible, fail otherwise
|
||||
await page.waitForSelector('text=/Connected successfully|Connection failed/', {
|
||||
timeout: 15_000,
|
||||
});
|
||||
|
||||
// Either success or failure is acceptable (depends on environment)
|
||||
const result = await page.textContent('body');
|
||||
expect(result).toMatch(/(Connected successfully|Connection failed)/);
|
||||
});
|
||||
});
|
||||
Generated
+5
-4
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "headlamp-polaris-plugin",
|
||||
"version": "0.1.3",
|
||||
"name": "polaris",
|
||||
"version": "0.5.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "headlamp-polaris-plugin",
|
||||
"version": "0.1.3",
|
||||
"name": "polaris",
|
||||
"version": "0.5.2",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@kinvolk/headlamp-plugin": "^0.13.0",
|
||||
"@playwright/test": "^1.58.2"
|
||||
|
||||
+12
-2
@@ -1,7 +1,17 @@
|
||||
{
|
||||
"name": "headlamp-polaris-plugin",
|
||||
"version": "0.1.6",
|
||||
"name": "polaris",
|
||||
"version": "0.5.2",
|
||||
"description": "Headlamp plugin for Fairwinds Polaris audit results",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/privilegedescalation/headlamp-polaris-plugin.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/privilegedescalation/headlamp-polaris-plugin/issues"
|
||||
},
|
||||
"homepage": "https://github.com/privilegedescalation/headlamp-polaris-plugin#readme",
|
||||
"author": "privilegedescalation",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"start": "headlamp-plugin start",
|
||||
"build": "headlamp-plugin build",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"]
|
||||
}
|
||||
@@ -1,21 +1,87 @@
|
||||
import React from 'react';
|
||||
import { AuditData, getRefreshInterval, usePolarisData } from './polaris';
|
||||
|
||||
/**
|
||||
* Shared Polaris data context value provided to all plugin components.
|
||||
*/
|
||||
interface PolarisDataContextValue {
|
||||
/** Polaris audit data (null if not loaded or error) */
|
||||
data: AuditData | null;
|
||||
/** Whether data is currently being loaded */
|
||||
loading: boolean;
|
||||
/** Error message (null if no error) */
|
||||
error: string | null;
|
||||
/** Function to manually trigger a data refresh */
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
const PolarisDataContext = React.createContext<PolarisDataContextValue | null>(null);
|
||||
|
||||
/**
|
||||
* React Context provider for shared Polaris data across all plugin components.
|
||||
*
|
||||
* Fetches data once and shares it with all consuming components to avoid
|
||||
* duplicate API requests. Auto-refreshes based on user's configured interval.
|
||||
*
|
||||
* @param props - Component props
|
||||
* @param props.children - Child components that will consume the context
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* <PolarisDataProvider>
|
||||
* <DashboardView />
|
||||
* <NamespacesListView />
|
||||
* </PolarisDataProvider>
|
||||
* ```
|
||||
*/
|
||||
export function PolarisDataProvider(props: { children: React.ReactNode }) {
|
||||
const interval = getRefreshInterval();
|
||||
const state = usePolarisData(interval);
|
||||
// Re-read interval on every render to pick up changes from settings
|
||||
const [refreshInterval, setRefreshInterval] = React.useState(getRefreshInterval());
|
||||
|
||||
return <PolarisDataContext.Provider value={state}>{props.children}</PolarisDataContext.Provider>;
|
||||
// Poll for interval changes (localStorage changes from settings)
|
||||
React.useEffect(() => {
|
||||
const intervalId = window.setInterval(() => {
|
||||
const newInterval = getRefreshInterval();
|
||||
setRefreshInterval(prev => (prev !== newInterval ? newInterval : prev));
|
||||
}, 1000); // Check every second
|
||||
|
||||
return () => window.clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
const state = usePolarisData(refreshInterval);
|
||||
|
||||
// Rename triggerRefresh to refresh for consistency
|
||||
const value = React.useMemo(
|
||||
() => ({
|
||||
data: state.data,
|
||||
loading: state.loading,
|
||||
error: state.error,
|
||||
refresh: state.triggerRefresh,
|
||||
}),
|
||||
[state]
|
||||
);
|
||||
|
||||
return <PolarisDataContext.Provider value={value}>{props.children}</PolarisDataContext.Provider>;
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook to access the shared Polaris data context.
|
||||
*
|
||||
* Must be used within a PolarisDataProvider. Throws an error if used outside.
|
||||
*
|
||||
* @returns Polaris data context value (data, loading, error, refresh)
|
||||
* @throws Error if used outside PolarisDataProvider
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* function MyComponent() {
|
||||
* const { data, loading, error, refresh } = usePolarisDataContext();
|
||||
* if (loading) return <Loader />;
|
||||
* if (error) return <Error message={error} />;
|
||||
* return <div>{data.DisplayName}</div>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function usePolarisDataContext(): PolarisDataContextValue {
|
||||
const ctx = React.useContext(PolarisDataContext);
|
||||
if (ctx === null) {
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Mapping of Polaris check IDs to human-readable names and descriptions
|
||||
* Sourced from Polaris documentation
|
||||
*/
|
||||
|
||||
export interface CheckInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
category: 'Security' | 'Efficiency' | 'Reliability';
|
||||
defaultSeverity: 'danger' | 'warning' | 'ignore';
|
||||
}
|
||||
|
||||
export const CHECK_MAPPING: Record<string, CheckInfo> = {
|
||||
// Security checks
|
||||
hostIPCSet: {
|
||||
name: 'Host IPC',
|
||||
description: 'Host IPC should not be configured',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
hostPIDSet: {
|
||||
name: 'Host PID',
|
||||
description: 'Host PID should not be configured',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
hostNetworkSet: {
|
||||
name: 'Host Network',
|
||||
description: 'Host network should not be configured',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
hostPortSet: {
|
||||
name: 'Host Port',
|
||||
description: 'Host port should not be configured',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
runAsRootAllowed: {
|
||||
name: 'Run as Root',
|
||||
description: 'Should not be allowed to run as root',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
runAsPrivileged: {
|
||||
name: 'Privileged Container',
|
||||
description: 'Should not run as privileged',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
notReadOnlyRootFilesystem: {
|
||||
name: 'Read-Only Root Filesystem',
|
||||
description: 'Filesystem should be read-only',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
privilegeEscalationAllowed: {
|
||||
name: 'Privilege Escalation',
|
||||
description: 'Privilege escalation should not be allowed',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
dangerousCapabilities: {
|
||||
name: 'Dangerous Capabilities',
|
||||
description: 'Dangerous capabilities should not be allowed',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
insecureCapabilities: {
|
||||
name: 'Insecure Capabilities',
|
||||
description: 'Insecure capabilities should not be allowed',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
sensitiveContainerEnvVar: {
|
||||
name: 'Sensitive Environment Variables',
|
||||
description: 'Sensitive env vars detected',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
sensitiveConfigmapContent: {
|
||||
name: 'Sensitive ConfigMap',
|
||||
description: 'Sensitive ConfigMap content detected',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
automountServiceAccountToken: {
|
||||
name: 'Service Account Token Auto-mount',
|
||||
description: 'Service account token auto-mount',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
tlsSettingsMissing: {
|
||||
name: 'TLS Settings',
|
||||
description: 'TLS settings missing',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
missingNetworkPolicy: {
|
||||
name: 'Network Policy',
|
||||
description: 'Missing NetworkPolicy',
|
||||
category: 'Security',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
|
||||
// Reliability checks
|
||||
tagNotSpecified: {
|
||||
name: 'Image Tag',
|
||||
description: 'Image tag should be specified',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'danger',
|
||||
},
|
||||
pullPolicyNotAlways: {
|
||||
name: 'Pull Policy',
|
||||
description: 'Pull policy should be Always',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
readinessProbeMissing: {
|
||||
name: 'Readiness Probe',
|
||||
description: 'Readiness probe should be configured',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
livenessProbeMissing: {
|
||||
name: 'Liveness Probe',
|
||||
description: 'Liveness probe should be configured',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
deploymentMissingReplicas: {
|
||||
name: 'Deployment Replicas',
|
||||
description: 'Deployment should have multiple replicas',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
priorityClassNotSet: {
|
||||
name: 'Priority Class',
|
||||
description: 'Priority class should be set',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
metadataAndNameMismatched: {
|
||||
name: 'Metadata Mismatch',
|
||||
description: 'Metadata and name should match',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
missingPodDisruptionBudget: {
|
||||
name: 'Pod Disruption Budget',
|
||||
description: 'PodDisruptionBudget should exist',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
pdbDisruptionsIsZero: {
|
||||
name: 'PDB Disruptions',
|
||||
description: 'PDB maxUnavailable should not be zero',
|
||||
category: 'Reliability',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
|
||||
// Efficiency checks
|
||||
cpuRequestsMissing: {
|
||||
name: 'CPU Requests',
|
||||
description: 'CPU requests should be set',
|
||||
category: 'Efficiency',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
cpuLimitsMissing: {
|
||||
name: 'CPU Limits',
|
||||
description: 'CPU limits should be set',
|
||||
category: 'Efficiency',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
memoryRequestsMissing: {
|
||||
name: 'Memory Requests',
|
||||
description: 'Memory requests should be set',
|
||||
category: 'Efficiency',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
memoryLimitsMissing: {
|
||||
name: 'Memory Limits',
|
||||
description: 'Memory limits should be set',
|
||||
category: 'Efficiency',
|
||||
defaultSeverity: 'warning',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get human-readable name for a check ID
|
||||
*/
|
||||
export function getCheckName(checkId: string): string {
|
||||
return CHECK_MAPPING[checkId]?.name || checkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get check description
|
||||
*/
|
||||
export function getCheckDescription(checkId: string): string {
|
||||
return CHECK_MAPPING[checkId]?.description || 'Unknown check';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get check category
|
||||
*/
|
||||
export function getCheckCategory(checkId: string): 'Security' | 'Efficiency' | 'Reliability' {
|
||||
return CHECK_MAPPING[checkId]?.category || 'Security';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for severity
|
||||
*/
|
||||
export function getSeverityColor(severity: string): string {
|
||||
switch (severity) {
|
||||
case 'danger':
|
||||
return '#f44336';
|
||||
case 'warning':
|
||||
return '#ff9800';
|
||||
case 'ignore':
|
||||
return '#9e9e9e';
|
||||
default:
|
||||
return '#9e9e9e';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status for StatusLabel component
|
||||
*/
|
||||
export function getSeverityStatus(severity: string): 'error' | 'warning' | 'success' {
|
||||
switch (severity) {
|
||||
case 'danger':
|
||||
return 'error';
|
||||
case 'warning':
|
||||
return 'warning';
|
||||
default:
|
||||
return 'success';
|
||||
}
|
||||
}
|
||||
+237
-23
@@ -3,64 +3,125 @@ import React from 'react';
|
||||
|
||||
// --- Polaris AuditData schema (matches pkg/validator/output.go) ---
|
||||
|
||||
/**
|
||||
* Severity level for a Polaris check result.
|
||||
*/
|
||||
type Severity = 'ignore' | 'warning' | 'danger';
|
||||
|
||||
/**
|
||||
* A single Polaris check result message.
|
||||
*/
|
||||
interface ResultMessage {
|
||||
/** Unique identifier for the check */
|
||||
ID: string;
|
||||
/** Human-readable message describing the check */
|
||||
Message: string;
|
||||
/** Additional details or context for the check */
|
||||
Details: string[];
|
||||
/** Whether the check passed (true) or failed (false) */
|
||||
Success: boolean;
|
||||
/** Severity level of the check */
|
||||
Severity: Severity;
|
||||
/** Category/group this check belongs to (e.g., "Security", "Efficiency") */
|
||||
Category: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of check results keyed by check ID.
|
||||
*/
|
||||
type ResultSet = Record<string, ResultMessage>;
|
||||
|
||||
/**
|
||||
* Polaris audit results for a single container within a pod.
|
||||
*/
|
||||
interface ContainerResult {
|
||||
/** Container name */
|
||||
Name: string;
|
||||
/** Check results for this container */
|
||||
Results: ResultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Polaris audit results for a pod and its containers.
|
||||
*/
|
||||
interface PodResult {
|
||||
/** Pod name */
|
||||
Name: string;
|
||||
/** Pod-level check results */
|
||||
Results: ResultSet;
|
||||
/** Per-container check results */
|
||||
ContainerResults: ContainerResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Polaris audit result for a single Kubernetes resource.
|
||||
*/
|
||||
export interface Result {
|
||||
/** Resource name */
|
||||
Name: string;
|
||||
/** Kubernetes namespace */
|
||||
Namespace: string;
|
||||
/** Kubernetes resource kind (e.g., "Deployment", "StatefulSet") */
|
||||
Kind: string;
|
||||
/** Resource-level check results */
|
||||
Results: ResultSet;
|
||||
/** Pod-level results (for workload controllers) */
|
||||
PodResult?: PodResult;
|
||||
/** ISO 8601 timestamp when resource was created */
|
||||
CreatedTime: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cluster metadata from Polaris audit.
|
||||
*/
|
||||
interface ClusterInfo {
|
||||
/** Kubernetes version */
|
||||
Version: string;
|
||||
/** Number of nodes in cluster */
|
||||
Nodes: number;
|
||||
/** Number of pods in cluster */
|
||||
Pods: number;
|
||||
/** Number of namespaces in cluster */
|
||||
Namespaces: number;
|
||||
/** Number of controllers (workloads) in cluster */
|
||||
Controllers: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete Polaris audit data structure returned by the dashboard API.
|
||||
*/
|
||||
export interface AuditData {
|
||||
/** Polaris output schema version */
|
||||
PolarisOutputVersion: string;
|
||||
/** ISO 8601 timestamp of when audit was performed */
|
||||
AuditTime: string;
|
||||
/** Source type (e.g., "Cluster") */
|
||||
SourceType: string;
|
||||
/** Source identifier */
|
||||
SourceName: string;
|
||||
/** Human-readable cluster name */
|
||||
DisplayName: string;
|
||||
/** Cluster statistics */
|
||||
ClusterInfo: ClusterInfo;
|
||||
/** All audit results across the cluster */
|
||||
Results: Result[];
|
||||
}
|
||||
|
||||
// --- Result counting ---
|
||||
|
||||
/**
|
||||
* Aggregated counts of check results by status.
|
||||
*/
|
||||
export interface ResultCounts {
|
||||
/** Total number of checks performed */
|
||||
total: number;
|
||||
/** Number of checks that passed */
|
||||
pass: number;
|
||||
/** Number of checks with warning severity that failed */
|
||||
warning: number;
|
||||
/** Number of checks with danger severity that failed */
|
||||
danger: number;
|
||||
/** Number of checks with severity "ignore" that failed (skipped by Polaris config) */
|
||||
skipped: number;
|
||||
}
|
||||
|
||||
@@ -94,14 +155,32 @@ function countResultItems(results: Result[]): ResultCounts {
|
||||
return counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts check results by status across all resources in the audit data.
|
||||
*
|
||||
* @param data - Complete Polaris audit data
|
||||
* @returns Aggregated counts (total, pass, warning, danger, skipped)
|
||||
*/
|
||||
export function countResults(data: AuditData): ResultCounts {
|
||||
return countResultItems(data.Results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts check results for a specific set of resources.
|
||||
*
|
||||
* @param results - Array of Polaris audit results
|
||||
* @returns Aggregated counts (total, pass, warning, danger, skipped)
|
||||
*/
|
||||
export function countResultsForItems(results: Result[]): ResultCounts {
|
||||
return countResultItems(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts unique namespaces from audit data, sorted alphabetically.
|
||||
*
|
||||
* @param data - Complete Polaris audit data
|
||||
* @returns Sorted array of namespace names
|
||||
*/
|
||||
export function getNamespaces(data: AuditData): string[] {
|
||||
const namespaces = new Set<string>();
|
||||
for (const result of data.Results) {
|
||||
@@ -112,12 +191,22 @@ export function getNamespaces(data: AuditData): string[] {
|
||||
return Array.from(namespaces).sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters audit results to only those in a specific namespace.
|
||||
*
|
||||
* @param data - Complete Polaris audit data
|
||||
* @param namespace - Target namespace name
|
||||
* @returns Array of results matching the namespace
|
||||
*/
|
||||
export function filterResultsByNamespace(data: AuditData, namespace: string): Result[] {
|
||||
return data.Results.filter(r => r.Namespace === namespace);
|
||||
}
|
||||
|
||||
// --- Settings ---
|
||||
|
||||
/**
|
||||
* Predefined refresh interval options for the plugin settings UI.
|
||||
*/
|
||||
export const INTERVAL_OPTIONS = [
|
||||
{ label: '1 minute', value: 60 },
|
||||
{ label: '5 minutes', value: 300 },
|
||||
@@ -125,11 +214,19 @@ export const INTERVAL_OPTIONS = [
|
||||
{ label: '30 minutes', value: 1800 },
|
||||
];
|
||||
|
||||
const STORAGE_KEY = 'polaris-plugin-refresh-interval';
|
||||
const REFRESH_STORAGE_KEY = 'polaris-plugin-refresh-interval';
|
||||
const DEFAULT_INTERVAL_SECONDS = 300; // 5 minutes
|
||||
|
||||
const URL_STORAGE_KEY = 'polaris-plugin-dashboard-url';
|
||||
const DEFAULT_DASHBOARD_URL = '/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/';
|
||||
|
||||
/**
|
||||
* Retrieves the configured refresh interval from localStorage.
|
||||
*
|
||||
* @returns Refresh interval in seconds (default: 300)
|
||||
*/
|
||||
export function getRefreshInterval(): number {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
const stored = localStorage.getItem(REFRESH_STORAGE_KEY);
|
||||
if (stored !== null) {
|
||||
const parsed = parseInt(stored, 10);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
@@ -139,17 +236,58 @@ export function getRefreshInterval(): number {
|
||||
return DEFAULT_INTERVAL_SECONDS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the refresh interval to localStorage.
|
||||
*
|
||||
* @param seconds - Refresh interval in seconds
|
||||
*/
|
||||
export function setRefreshInterval(seconds: number): void {
|
||||
localStorage.setItem(STORAGE_KEY, String(seconds));
|
||||
localStorage.setItem(REFRESH_STORAGE_KEY, String(seconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configured Polaris dashboard URL from localStorage.
|
||||
*
|
||||
* @returns Dashboard URL (default: Kubernetes service proxy path)
|
||||
*/
|
||||
export function getDashboardUrl(): string {
|
||||
const stored = localStorage.getItem(URL_STORAGE_KEY);
|
||||
if (stored !== null && stored.trim() !== '') {
|
||||
return stored.trim();
|
||||
}
|
||||
return DEFAULT_DASHBOARD_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Polaris dashboard URL to localStorage.
|
||||
*
|
||||
* @param url - Dashboard URL (service proxy path or full URL)
|
||||
*/
|
||||
export function setDashboardUrl(url: string): void {
|
||||
localStorage.setItem(URL_STORAGE_KEY, url.trim());
|
||||
}
|
||||
|
||||
// --- Polaris dashboard proxy URL ---
|
||||
|
||||
export const POLARIS_DASHBOARD_PROXY =
|
||||
'/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/';
|
||||
/**
|
||||
* Returns the base URL for the Polaris dashboard proxy.
|
||||
*
|
||||
* @returns Dashboard base URL (without /results.json)
|
||||
*/
|
||||
export function getPolarisProxyUrl(): string {
|
||||
return getDashboardUrl();
|
||||
}
|
||||
|
||||
// --- Score computation ---
|
||||
|
||||
/**
|
||||
* Computes the Polaris score as a percentage (0-100).
|
||||
*
|
||||
* Formula: (pass / total) * 100, rounded to nearest integer.
|
||||
*
|
||||
* @param counts - Result counts to compute score from
|
||||
* @returns Score percentage (0 if total is 0)
|
||||
*/
|
||||
export function computeScore(counts: ResultCounts): number {
|
||||
if (counts.total === 0) return 0;
|
||||
return Math.round((counts.pass / counts.total) * 100);
|
||||
@@ -157,27 +295,89 @@ export function computeScore(counts: ResultCounts): number {
|
||||
|
||||
// --- Data fetching hook ---
|
||||
|
||||
const POLARIS_API_PATH =
|
||||
'/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json';
|
||||
|
||||
interface PolarisDataState {
|
||||
data: AuditData | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
/**
|
||||
* Constructs the full API path for fetching Polaris results.
|
||||
*
|
||||
* @returns Full path to results.json endpoint
|
||||
*/
|
||||
function getPolarisApiPath(): string {
|
||||
const baseUrl = getDashboardUrl();
|
||||
return baseUrl.endsWith('/') ? `${baseUrl}results.json` : `${baseUrl}/results.json`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a URL is a full URL (http:// or https://) vs. a relative path.
|
||||
*
|
||||
* @param url - URL to check
|
||||
* @returns true if full URL, false if relative path
|
||||
*/
|
||||
function isFullUrl(url: string): boolean {
|
||||
return url.startsWith('http://') || url.startsWith('https://');
|
||||
}
|
||||
|
||||
/**
|
||||
* State returned by the usePolarisData hook.
|
||||
*/
|
||||
interface PolarisDataState {
|
||||
/** Polaris audit data (null if not yet loaded or error occurred) */
|
||||
data: AuditData | null;
|
||||
/** Whether data is currently being loaded */
|
||||
loading: boolean;
|
||||
/** Error message (null if no error) */
|
||||
error: string | null;
|
||||
/** Function to manually trigger a data refresh */
|
||||
triggerRefresh: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook for fetching and auto-refreshing Polaris audit data.
|
||||
*
|
||||
* Handles both Kubernetes service proxy paths and full URLs.
|
||||
* Automatically refreshes data at the specified interval.
|
||||
*
|
||||
* @param refreshIntervalSeconds - How often to refresh data (0 to disable auto-refresh)
|
||||
* @returns Polaris data state (data, loading, error, triggerRefresh)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { data, loading, error, triggerRefresh } = usePolarisData(300);
|
||||
* if (loading) return <Loader />;
|
||||
* if (error) return <Error message={error} />;
|
||||
* return <Dashboard data={data} onRefresh={triggerRefresh} />;
|
||||
* ```
|
||||
*/
|
||||
export function usePolarisData(refreshIntervalSeconds: number): PolarisDataState {
|
||||
const [data, setData] = React.useState<AuditData | null>(null);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const [tick, setTick] = React.useState(0);
|
||||
|
||||
const triggerRefresh = React.useCallback(() => {
|
||||
setTick(t => t + 1);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const result: AuditData = await ApiProxy.request(POLARIS_API_PATH);
|
||||
const apiPath = getPolarisApiPath();
|
||||
let result: AuditData;
|
||||
|
||||
if (isFullUrl(apiPath)) {
|
||||
// Direct fetch for full URLs
|
||||
const response = await fetch(apiPath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
result = await response.json();
|
||||
} else {
|
||||
// Kubernetes proxy for relative URLs
|
||||
result = await ApiProxy.request(apiPath, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
if (!cancelled) {
|
||||
setData(result);
|
||||
setError(null);
|
||||
@@ -185,17 +385,31 @@ export function usePolarisData(refreshIntervalSeconds: number): PolarisDataState
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
if (cancelled) return;
|
||||
const apiPath = getPolarisApiPath();
|
||||
const status = (err as { status?: number }).status;
|
||||
if (status === 403) {
|
||||
setError(
|
||||
'Access denied (403). Check that your RBAC permissions allow proxying to the Polaris service.'
|
||||
);
|
||||
} else if (status === 404 || status === 503) {
|
||||
setError(
|
||||
'Polaris dashboard not reachable. Ensure Polaris is installed in the polaris namespace.'
|
||||
);
|
||||
|
||||
if (isFullUrl(apiPath)) {
|
||||
// Full URL errors
|
||||
if (status === 403) {
|
||||
setError('Access denied (403). Check authentication and CORS configuration.');
|
||||
} else if (status === 404) {
|
||||
setError('Polaris dashboard not found (404). Verify the URL is correct.');
|
||||
} else {
|
||||
setError(`Failed to fetch from ${apiPath}: ${String(err)}`);
|
||||
}
|
||||
} else {
|
||||
setError(`Failed to fetch Polaris data: ${String(err)}`);
|
||||
// Kubernetes proxy errors
|
||||
if (status === 403) {
|
||||
setError(
|
||||
'Access denied (403). Check that your RBAC permissions allow proxying to the Polaris service.'
|
||||
);
|
||||
} else if (status === 404 || status === 503) {
|
||||
setError(
|
||||
'Polaris dashboard not reachable. Ensure Polaris is installed in the configured namespace.'
|
||||
);
|
||||
} else {
|
||||
setError(`Failed to fetch Polaris data: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -216,5 +430,5 @@ export function usePolarisData(refreshIntervalSeconds: number): PolarisDataState
|
||||
return () => window.clearInterval(intervalId);
|
||||
}, [refreshIntervalSeconds]);
|
||||
|
||||
return { data, loading, error };
|
||||
return { data, loading, error, triggerRefresh };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { getCheckCategory, getCheckName } from './checkMapping';
|
||||
import { AuditData } from './polaris';
|
||||
|
||||
export interface TopIssue {
|
||||
checkId: string;
|
||||
checkName: string;
|
||||
category: 'Security' | 'Efficiency' | 'Reliability';
|
||||
severity: 'danger' | 'warning';
|
||||
count: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the most common failing checks across the cluster
|
||||
* Returns top 10 issues sorted by severity then count
|
||||
*/
|
||||
export function getTopIssues(data: AuditData): TopIssue[] {
|
||||
const issueCounts = new Map<string, { severity: 'danger' | 'warning'; count: number }>();
|
||||
|
||||
// Aggregate all failing checks
|
||||
for (const result of data.Results) {
|
||||
// Pod-level checks
|
||||
if (result.PodResult?.Results) {
|
||||
for (const [checkId, checkResult] of Object.entries(result.PodResult.Results)) {
|
||||
if (!checkResult.Success && checkResult.Severity !== 'ignore') {
|
||||
const existing = issueCounts.get(checkId);
|
||||
issueCounts.set(checkId, {
|
||||
severity: checkResult.Severity as 'danger' | 'warning',
|
||||
count: (existing?.count || 0) + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Container-level checks
|
||||
if (result.PodResult?.ContainerResults) {
|
||||
for (const container of result.PodResult.ContainerResults) {
|
||||
for (const [checkId, checkResult] of Object.entries(container.Results)) {
|
||||
if (!checkResult.Success && checkResult.Severity !== 'ignore') {
|
||||
const existing = issueCounts.get(checkId);
|
||||
issueCounts.set(checkId, {
|
||||
severity: checkResult.Severity as 'danger' | 'warning',
|
||||
count: (existing?.count || 0) + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Controller-level checks (if any)
|
||||
if (result.Results) {
|
||||
for (const [checkId, checkResult] of Object.entries(result.Results)) {
|
||||
if (!checkResult.Success && checkResult.Severity !== 'ignore') {
|
||||
const existing = issueCounts.get(checkId);
|
||||
issueCounts.set(checkId, {
|
||||
severity: checkResult.Severity as 'danger' | 'warning',
|
||||
count: (existing?.count || 0) + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to array and format
|
||||
const issues: TopIssue[] = Array.from(issueCounts.entries()).map(([checkId, data]) => ({
|
||||
checkId,
|
||||
checkName: getCheckName(checkId),
|
||||
category: getCheckCategory(checkId),
|
||||
severity: data.severity,
|
||||
count: data.count,
|
||||
}));
|
||||
|
||||
// Sort by severity (danger first) then by count (descending)
|
||||
issues.sort((a, b) => {
|
||||
if (a.severity === 'danger' && b.severity !== 'danger') return -1;
|
||||
if (a.severity !== 'danger' && b.severity === 'danger') return 1;
|
||||
return b.count - a.count;
|
||||
});
|
||||
|
||||
// Return top 10
|
||||
return issues.slice(0, 10);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { computeScore, countResults } from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
|
||||
/**
|
||||
* App bar badge showing cluster Polaris score
|
||||
* Clicking navigates to the overview dashboard
|
||||
*/
|
||||
export default function AppBarScoreBadge() {
|
||||
const { data, loading } = usePolarisDataContext();
|
||||
const history = useHistory();
|
||||
|
||||
if (loading || !data) {
|
||||
return null; // Graceful degradation when Polaris unavailable
|
||||
}
|
||||
|
||||
const counts = countResults(data);
|
||||
const score = computeScore(counts);
|
||||
|
||||
// Color based on score
|
||||
const getColor = (score: number): string => {
|
||||
if (score >= 80) return '#4caf50'; // green
|
||||
if (score >= 50) return '#ff9800'; // orange
|
||||
return '#f44336'; // red
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
history.push('/polaris');
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleClick}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
marginRight: '8px',
|
||||
padding: '4px 12px',
|
||||
borderRadius: '16px',
|
||||
border: 'none',
|
||||
backgroundColor: getColor(score),
|
||||
color: 'white',
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
}}
|
||||
aria-label={`Polaris cluster score: ${score}%`}
|
||||
>
|
||||
<span>🛡️</span>
|
||||
<span>Polaris: {score}%</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -34,6 +34,17 @@ vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
SimpleTable: ({ data }: { data: Array<any> }) => (
|
||||
<table data-testid="simple-table">
|
||||
<tbody>
|
||||
{data.map((item, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>{JSON.stringify(item)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
PercentageCircle: ({ label }: { label: string }) => (
|
||||
<div data-testid="percentage-circle">{label}</div>
|
||||
),
|
||||
|
||||
@@ -5,11 +5,14 @@ import {
|
||||
PercentageCircle,
|
||||
SectionBox,
|
||||
SectionHeader,
|
||||
SimpleTable,
|
||||
StatusLabel,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
import { getSeverityStatus } from '../api/checkMapping';
|
||||
import { AuditData, computeScore, countResults, ResultCounts } from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
import { getTopIssues, TopIssue } from '../api/topIssues';
|
||||
|
||||
const COLORS = {
|
||||
pass: '#4caf50',
|
||||
@@ -26,7 +29,6 @@ function OverviewSection(props: { data: AuditData; counts: ResultCounts }) {
|
||||
{ name: 'Pass', value: counts.pass, fill: COLORS.pass },
|
||||
{ name: 'Warning', value: counts.warning, fill: COLORS.warning },
|
||||
{ name: 'Danger', value: counts.danger, fill: COLORS.danger },
|
||||
{ name: 'Skipped', value: counts.skipped, fill: COLORS.skipped },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -51,14 +53,6 @@ function OverviewSection(props: { data: AuditData; counts: ResultCounts }) {
|
||||
name: 'Danger',
|
||||
value: <StatusLabel status="error">{counts.danger}</StatusLabel>,
|
||||
},
|
||||
{
|
||||
name: 'Skipped',
|
||||
value: (
|
||||
<span title="Only counts checks with Severity=ignore. Annotation-based exemptions are not included.">
|
||||
{counts.skipped}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
@@ -76,18 +70,70 @@ function OverviewSection(props: { data: AuditData; counts: ResultCounts }) {
|
||||
);
|
||||
}
|
||||
|
||||
function formatAuditTime(auditTime: string): string {
|
||||
const date = new Date(auditTime);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
|
||||
if (diffMins < 1) return 'just now';
|
||||
if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
|
||||
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
||||
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
|
||||
export default function DashboardView() {
|
||||
const { data, loading, error } = usePolarisDataContext();
|
||||
const { data, loading, error, refresh } = usePolarisDataContext();
|
||||
|
||||
if (loading) {
|
||||
return <Loader title="Loading Polaris audit data..." />;
|
||||
}
|
||||
|
||||
const counts = data ? countResults(data) : null;
|
||||
const topIssues = data ? getTopIssues(data) : [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionHeader title="Polaris — Overview" />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
>
|
||||
<SectionHeader title="Polaris — Overview" />
|
||||
{data && (
|
||||
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
||||
<span style={{ fontSize: '14px', color: 'var(--mui-palette-text-secondary, #666)' }}>
|
||||
Last updated: {formatAuditTime(data.AuditTime)}
|
||||
</span>
|
||||
<button
|
||||
onClick={refresh}
|
||||
style={{
|
||||
padding: '6px 16px',
|
||||
backgroundColor: 'transparent',
|
||||
color: 'var(--mui-palette-primary-main, #1976d2)',
|
||||
border: '1px solid var(--mui-palette-primary-main, #1976d2)',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
}}
|
||||
>
|
||||
<span>🔄</span>
|
||||
<span>Refresh</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<SectionBox title="Error">
|
||||
@@ -102,7 +148,35 @@ export default function DashboardView() {
|
||||
</SectionBox>
|
||||
)}
|
||||
|
||||
{data && counts && <OverviewSection data={data} counts={counts} />}
|
||||
{data && counts && (
|
||||
<>
|
||||
<OverviewSection data={data} counts={counts} />
|
||||
|
||||
{topIssues.length > 0 && (
|
||||
<SectionBox title="Top Issues">
|
||||
<SimpleTable
|
||||
columns={[
|
||||
{ label: 'Check', getter: (issue: TopIssue) => issue.checkName },
|
||||
{ label: 'Category', getter: (issue: TopIssue) => issue.category },
|
||||
{
|
||||
label: 'Severity',
|
||||
getter: (issue: TopIssue) => (
|
||||
<StatusLabel status={getSeverityStatus(issue.severity)}>
|
||||
{issue.severity}
|
||||
</StatusLabel>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Affected Workloads',
|
||||
getter: (issue: TopIssue) => String(issue.count),
|
||||
},
|
||||
]}
|
||||
data={topIssues}
|
||||
/>
|
||||
</SectionBox>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!data && !error && (
|
||||
<SectionBox title="No Data">
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
|
||||
import { Dialog, NameValueTable, SectionBox } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
import { getCheckName } from '../api/checkMapping';
|
||||
import { Result } from '../api/polaris';
|
||||
|
||||
interface ExemptionManagerProps {
|
||||
workloadResult: Result;
|
||||
namespace: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface CheckFailure {
|
||||
checkId: string;
|
||||
checkName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exemption management UI for adding/removing Polaris exemptions
|
||||
* Uses annotation patches on the workload resource
|
||||
*/
|
||||
export default function ExemptionManager({
|
||||
workloadResult,
|
||||
namespace,
|
||||
kind,
|
||||
name,
|
||||
}: ExemptionManagerProps) {
|
||||
const [dialogOpen, setDialogOpen] = React.useState(false);
|
||||
const [selectedChecks, setSelectedChecks] = React.useState<Set<string>>(new Set());
|
||||
const [exemptAll, setExemptAll] = React.useState(false);
|
||||
const [applying, setApplying] = React.useState(false);
|
||||
|
||||
// Extract current exemptions from workload metadata
|
||||
const getExemptions = (): string[] => {
|
||||
// This would need to fetch the actual workload from K8s API
|
||||
// For now, return empty array as placeholder
|
||||
return [];
|
||||
};
|
||||
|
||||
// Extract failing checks for this workload
|
||||
const getFailingChecks = (): CheckFailure[] => {
|
||||
const failures: CheckFailure[] = [];
|
||||
|
||||
// Pod-level checks
|
||||
if (workloadResult.PodResult?.Results) {
|
||||
for (const [checkId, checkResult] of Object.entries(workloadResult.PodResult.Results)) {
|
||||
if (!checkResult.Success && checkResult.Severity !== 'ignore') {
|
||||
failures.push({
|
||||
checkId,
|
||||
checkName: getCheckName(checkId),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Container checks
|
||||
if (workloadResult.PodResult?.ContainerResults) {
|
||||
for (const container of workloadResult.PodResult.ContainerResults) {
|
||||
for (const [checkId, checkResult] of Object.entries(container.Results)) {
|
||||
if (!checkResult.Success && checkResult.Severity !== 'ignore') {
|
||||
// Avoid duplicates
|
||||
if (!failures.some(f => f.checkId === checkId)) {
|
||||
failures.push({
|
||||
checkId,
|
||||
checkName: getCheckName(checkId),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return failures;
|
||||
};
|
||||
|
||||
const failingChecks = getFailingChecks();
|
||||
const currentExemptions = getExemptions();
|
||||
|
||||
const handleCheckToggle = (checkId: string) => {
|
||||
const newSelected = new Set(selectedChecks);
|
||||
if (newSelected.has(checkId)) {
|
||||
newSelected.delete(checkId);
|
||||
} else {
|
||||
newSelected.add(checkId);
|
||||
}
|
||||
setSelectedChecks(newSelected);
|
||||
};
|
||||
|
||||
const applyExemptions = async () => {
|
||||
setApplying(true);
|
||||
|
||||
try {
|
||||
// Construct the API path based on kind
|
||||
const apiGroup = getApiGroup(kind);
|
||||
const apiVersion = 'v1'; // This would need to be dynamic based on kind
|
||||
const plural = getPlural(kind);
|
||||
|
||||
const patchPath = apiGroup
|
||||
? `/apis/${apiGroup}/${apiVersion}/namespaces/${namespace}/${plural}/${name}`
|
||||
: `/api/v1/namespaces/${namespace}/${plural}/${name}`;
|
||||
|
||||
// Build annotations patch
|
||||
const annotations: Record<string, string> = {};
|
||||
|
||||
if (exemptAll) {
|
||||
annotations['polaris.fairwinds.com/exempt'] = 'true';
|
||||
} else {
|
||||
for (const checkId of selectedChecks) {
|
||||
annotations[`polaris.fairwinds.com/${checkId}-exempt`] = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
const patch = {
|
||||
metadata: {
|
||||
annotations,
|
||||
},
|
||||
};
|
||||
|
||||
await ApiProxy.request(patchPath, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/strategic-merge-patch+json',
|
||||
},
|
||||
body: JSON.stringify(patch),
|
||||
});
|
||||
|
||||
setDialogOpen(false);
|
||||
setSelectedChecks(new Set());
|
||||
setExemptAll(false);
|
||||
|
||||
// Show success message (would need notistack integration)
|
||||
alert('Exemptions applied successfully');
|
||||
} catch (err) {
|
||||
alert(`Failed to apply exemptions: ${String(err)}`);
|
||||
} finally {
|
||||
setApplying(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionBox title="Exemptions">
|
||||
{currentExemptions.length > 0 ? (
|
||||
<NameValueTable
|
||||
rows={currentExemptions.map(exemption => ({
|
||||
name: exemption,
|
||||
value: (
|
||||
<button
|
||||
style={{
|
||||
padding: '4px 12px',
|
||||
backgroundColor: 'var(--mui-palette-error-main, #f44336)',
|
||||
color: 'var(--mui-palette-error-contrastText, #fff)',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
onClick={() => {
|
||||
// Remove exemption logic
|
||||
alert('Remove exemption: ' + exemption);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
) : (
|
||||
<p>No exemptions configured</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => setDialogOpen(true)}
|
||||
disabled={failingChecks.length === 0}
|
||||
style={{
|
||||
marginTop: '8px',
|
||||
padding: '6px 16px',
|
||||
backgroundColor:
|
||||
failingChecks.length === 0
|
||||
? 'var(--mui-palette-action-disabledBackground, #e0e0e0)'
|
||||
: 'transparent',
|
||||
color:
|
||||
failingChecks.length === 0
|
||||
? 'var(--mui-palette-action-disabled, #9e9e9e)'
|
||||
: 'var(--mui-palette-primary-main, #1976d2)',
|
||||
border: '1px solid',
|
||||
borderColor:
|
||||
failingChecks.length === 0
|
||||
? 'var(--mui-palette-divider, #e0e0e0)'
|
||||
: 'var(--mui-palette-primary-main, #1976d2)',
|
||||
borderRadius: '4px',
|
||||
cursor: failingChecks.length === 0 ? 'not-allowed' : 'pointer',
|
||||
fontSize: '13px',
|
||||
}}
|
||||
>
|
||||
Add Exemption
|
||||
</button>
|
||||
</SectionBox>
|
||||
|
||||
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} title="Add Exemptions">
|
||||
<div style={{ padding: '16px', minWidth: '400px' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={exemptAll}
|
||||
onChange={e => setExemptAll(e.target.checked)}
|
||||
/>
|
||||
<span>Exempt from all checks</span>
|
||||
</label>
|
||||
|
||||
{!exemptAll && (
|
||||
<>
|
||||
<div style={{ marginTop: '16px', marginBottom: '8px', fontWeight: 600 }}>
|
||||
Select checks to exempt:
|
||||
</div>
|
||||
<div>
|
||||
{failingChecks.map(check => (
|
||||
<label
|
||||
key={check.checkId}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginBottom: '8px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedChecks.has(check.checkId)}
|
||||
onChange={() => handleCheckToggle(check.checkId)}
|
||||
/>
|
||||
<span>{check.checkName}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{ marginTop: '16px', display: 'flex', gap: '8px', justifyContent: 'flex-end' }}
|
||||
>
|
||||
<button
|
||||
onClick={() => setDialogOpen(false)}
|
||||
style={{
|
||||
padding: '6px 16px',
|
||||
backgroundColor: 'transparent',
|
||||
color: 'var(--mui-palette-primary-main, #1976d2)',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '13px',
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={applyExemptions}
|
||||
disabled={applying || (!exemptAll && selectedChecks.size === 0)}
|
||||
style={{
|
||||
padding: '6px 16px',
|
||||
backgroundColor:
|
||||
applying || (!exemptAll && selectedChecks.size === 0)
|
||||
? 'var(--mui-palette-action-disabledBackground, #e0e0e0)'
|
||||
: 'var(--mui-palette-primary-main, #1976d2)',
|
||||
color:
|
||||
applying || (!exemptAll && selectedChecks.size === 0)
|
||||
? 'var(--mui-palette-action-disabled, #9e9e9e)'
|
||||
: 'var(--mui-palette-primary-contrastText, #fff)',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor:
|
||||
applying || (!exemptAll && selectedChecks.size === 0) ? 'not-allowed' : 'pointer',
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{applying ? 'Applying...' : 'Apply'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper functions to get API info based on kind
|
||||
function getApiGroup(kind: string): string | null {
|
||||
switch (kind) {
|
||||
case 'Deployment':
|
||||
case 'StatefulSet':
|
||||
case 'DaemonSet':
|
||||
return 'apps';
|
||||
case 'Job':
|
||||
case 'CronJob':
|
||||
return 'batch';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getPlural(kind: string): string {
|
||||
switch (kind) {
|
||||
case 'Deployment':
|
||||
return 'deployments';
|
||||
case 'StatefulSet':
|
||||
return 'statefulsets';
|
||||
case 'DaemonSet':
|
||||
return 'daemonsets';
|
||||
case 'Job':
|
||||
return 'jobs';
|
||||
case 'CronJob':
|
||||
return 'cronjobs';
|
||||
default:
|
||||
return kind.toLowerCase() + 's';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import {
|
||||
NameValueTable,
|
||||
SectionBox,
|
||||
SimpleTable,
|
||||
StatusLabel,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getCheckName, getSeverityStatus } from '../api/checkMapping';
|
||||
import { computeScore, countResultsForItems } from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
import ExemptionManager from './ExemptionManager';
|
||||
|
||||
interface CheckFailure {
|
||||
checkId: string;
|
||||
checkName: string;
|
||||
severity: 'danger' | 'warning';
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface InlineAuditSectionProps {
|
||||
resource: any; // KubeObject from Headlamp
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline Polaris audit section for resource detail views
|
||||
* Shows a compact summary of Polaris findings for Deployments, StatefulSets, etc.
|
||||
*/
|
||||
export default function InlineAuditSection({ resource }: InlineAuditSectionProps) {
|
||||
const { data, loading } = usePolarisDataContext();
|
||||
|
||||
if (loading || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if this is a supported controller kind
|
||||
const supportedKinds = ['Deployment', 'StatefulSet', 'DaemonSet', 'Job', 'CronJob'];
|
||||
const kind = resource.kind;
|
||||
|
||||
if (!supportedKinds.includes(kind)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = resource.metadata?.name;
|
||||
const namespace = resource.metadata?.namespace;
|
||||
|
||||
if (!name || !namespace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find this workload in Polaris audit data
|
||||
const workloadResult = data.Results.find(
|
||||
r => r.Kind === kind && r.Name === name && r.Namespace === namespace
|
||||
);
|
||||
|
||||
if (!workloadResult) {
|
||||
return (
|
||||
<SectionBox title="Polaris Audit">
|
||||
<NameValueTable
|
||||
rows={[
|
||||
{
|
||||
name: 'Status',
|
||||
value: 'Polaris dashboard not detected — install Polaris to see audit results',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate score and counts
|
||||
const counts = countResultsForItems([workloadResult]);
|
||||
const score = computeScore(counts);
|
||||
|
||||
// Extract failing checks
|
||||
const failures: CheckFailure[] = [];
|
||||
|
||||
// Pod-level checks
|
||||
if (workloadResult.PodResult?.Results) {
|
||||
for (const [checkId, checkResult] of Object.entries(workloadResult.PodResult.Results)) {
|
||||
if (!checkResult.Success && checkResult.Severity !== 'ignore') {
|
||||
failures.push({
|
||||
checkId,
|
||||
checkName: getCheckName(checkId),
|
||||
severity: checkResult.Severity as 'danger' | 'warning',
|
||||
message: checkResult.Message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Container checks
|
||||
if (workloadResult.PodResult?.ContainerResults) {
|
||||
for (const container of workloadResult.PodResult.ContainerResults) {
|
||||
for (const [checkId, checkResult] of Object.entries(container.Results)) {
|
||||
if (!checkResult.Success && checkResult.Severity !== 'ignore') {
|
||||
// Avoid duplicates
|
||||
if (!failures.some(f => f.checkId === checkId)) {
|
||||
failures.push({
|
||||
checkId,
|
||||
checkName: getCheckName(checkId),
|
||||
severity: checkResult.Severity as 'danger' | 'warning',
|
||||
message: checkResult.Message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by severity
|
||||
failures.sort((a, b) => {
|
||||
if (a.severity === 'danger' && b.severity !== 'danger') return -1;
|
||||
if (a.severity !== 'danger' && b.severity === 'danger') return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionBox title="Polaris Audit">
|
||||
<NameValueTable
|
||||
rows={[
|
||||
{
|
||||
name: 'Score',
|
||||
value: (
|
||||
<StatusLabel status={score >= 80 ? 'success' : score >= 50 ? 'warning' : 'error'}>
|
||||
{score}%
|
||||
</StatusLabel>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Summary',
|
||||
value: `${counts.pass} passing, ${counts.warning} warnings, ${counts.danger} dangers`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{failures.length > 0 && (
|
||||
<>
|
||||
<div style={{ marginTop: '16px', marginBottom: '8px', fontWeight: 600 }}>
|
||||
Failing Checks:
|
||||
</div>
|
||||
<SimpleTable
|
||||
columns={[
|
||||
{ label: 'Check', getter: (f: CheckFailure) => f.checkName },
|
||||
{
|
||||
label: 'Severity',
|
||||
getter: (f: CheckFailure) => (
|
||||
<StatusLabel status={getSeverityStatus(f.severity)}>{f.severity}</StatusLabel>
|
||||
),
|
||||
},
|
||||
{ label: 'Message', getter: (f: CheckFailure) => f.message },
|
||||
]}
|
||||
data={failures}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: '16px' }}>
|
||||
<Link
|
||||
to={`/polaris/namespaces#${namespace}`}
|
||||
style={{ color: 'var(--link-color, #1976d2)' }}
|
||||
>
|
||||
View Full Report →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '16px' }}>
|
||||
<ExemptionManager
|
||||
workloadResult={workloadResult}
|
||||
namespace={namespace}
|
||||
kind={kind}
|
||||
name={name}
|
||||
/>
|
||||
</div>
|
||||
</SectionBox>
|
||||
);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
computeScore,
|
||||
countResultsForItems,
|
||||
filterResultsByNamespace,
|
||||
POLARIS_DASHBOARD_PROXY,
|
||||
getPolarisProxyUrl,
|
||||
Result,
|
||||
ResultCounts,
|
||||
} from '../api/polaris';
|
||||
@@ -89,7 +89,7 @@ export default function NamespaceDetailView() {
|
||||
{
|
||||
name: 'Polaris Dashboard',
|
||||
value: (
|
||||
<a href={POLARIS_DASHBOARD_PROXY} target="_blank" rel="noopener noreferrer">
|
||||
<a href={getPolarisProxyUrl()} target="_blank" rel="noopener noreferrer">
|
||||
View in Polaris Dashboard
|
||||
</a>
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
@@ -117,7 +118,7 @@ describe('NamespacesListView', () => {
|
||||
expect(screen.getByText('No Polaris audit results found.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders namespace rows with correct scores and links', () => {
|
||||
it('renders namespace rows with correct scores and buttons', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'deploy-a',
|
||||
@@ -157,12 +158,14 @@ describe('NamespacesListView', () => {
|
||||
|
||||
renderWithRouter(<NamespacesListView />);
|
||||
|
||||
// Namespace links
|
||||
const alphaLink = screen.getByText('alpha');
|
||||
expect(alphaLink.closest('a')).toHaveAttribute('href', '/polaris/ns/alpha');
|
||||
// Namespace buttons (now buttons instead of links for drawer)
|
||||
const alphaButton = screen.getByText('alpha');
|
||||
expect(alphaButton).toBeInTheDocument();
|
||||
expect(alphaButton.tagName).toBe('BUTTON');
|
||||
|
||||
const betaLink = screen.getByText('beta');
|
||||
expect(betaLink.closest('a')).toHaveAttribute('href', '/polaris/ns/beta');
|
||||
const betaButton = screen.getByText('beta');
|
||||
expect(betaButton).toBeInTheDocument();
|
||||
expect(betaButton.tagName).toBe('BUTTON');
|
||||
});
|
||||
|
||||
it('uses correct scoreStatus: >=80 success, >=50 warning, <50 error', () => {
|
||||
@@ -216,4 +219,74 @@ describe('NamespacesListView', () => {
|
||||
const errorScore = scoreLabels.find(el => el.textContent === '0%');
|
||||
expect(errorScore).toHaveAttribute('data-status', 'error');
|
||||
});
|
||||
|
||||
it('opens drawer when namespace button is clicked and URL hash is updated', async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'deploy-a',
|
||||
Namespace: 'alpha',
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
renderWithRouter(<NamespacesListView />);
|
||||
|
||||
// Click the namespace button
|
||||
const alphaButton = screen.getByText('alpha');
|
||||
await user.click(alphaButton);
|
||||
|
||||
// Drawer should open (check for the panel title)
|
||||
expect(screen.getByText(/Polaris — alpha/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('initializes drawer from URL hash', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'deploy-a',
|
||||
Namespace: 'test-ns',
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
// Render with initial hash in URL
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/polaris/namespaces#test-ns']}>
|
||||
<NamespacesListView />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// Drawer should be open with the namespace from hash
|
||||
expect(screen.getByText(/Polaris — test-ns/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Router } from '@kinvolk/headlamp-plugin/lib';
|
||||
import {
|
||||
Loader,
|
||||
NameValueTable,
|
||||
@@ -7,13 +6,16 @@ import {
|
||||
SimpleTable,
|
||||
StatusLabel,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
computeScore,
|
||||
countResultsForItems,
|
||||
filterResultsByNamespace,
|
||||
getNamespaces,
|
||||
getPolarisProxyUrl,
|
||||
Result,
|
||||
ResultCounts,
|
||||
} from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
|
||||
@@ -32,9 +34,274 @@ interface NamespaceRow {
|
||||
skipped: number;
|
||||
}
|
||||
|
||||
export default function NamespacesListView() {
|
||||
function resourceCounts(result: Result): ResultCounts {
|
||||
return countResultsForItems([result]);
|
||||
}
|
||||
|
||||
interface NamespaceDetailPanelProps {
|
||||
namespace: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function NamespaceDetailPanel({ namespace, onClose }: NamespaceDetailPanelProps) {
|
||||
const [isMaximized, setIsMaximized] = React.useState(false);
|
||||
const { data, loading, error } = usePolarisDataContext();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<Loader title={`Loading Polaris data for ${namespace}...`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<SectionBox title="Error">
|
||||
<NameValueTable
|
||||
rows={[
|
||||
{
|
||||
name: 'Status',
|
||||
value: <StatusLabel status="error">{error}</StatusLabel>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<SectionBox title="No Data">
|
||||
<NameValueTable rows={[{ name: 'Status', value: 'No Polaris audit results found.' }]} />
|
||||
</SectionBox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const results = filterResultsByNamespace(data, namespace);
|
||||
const counts = countResultsForItems(results);
|
||||
const score = computeScore(counts);
|
||||
const status = scoreStatus(score);
|
||||
|
||||
const countsPerResource = new Map<string, ResultCounts>();
|
||||
for (const r of results) {
|
||||
countsPerResource.set(`${r.Namespace}/${r.Kind}/${r.Name}`, resourceCounts(r));
|
||||
}
|
||||
|
||||
function getResourceCounts(row: Result): ResultCounts {
|
||||
return countsPerResource.get(`${row.Namespace}/${row.Kind}/${row.Name}`) ?? resourceCounts(row);
|
||||
}
|
||||
|
||||
// Generate a unique class name for this drawer to avoid conflicts
|
||||
const drawerClass = `polaris-namespace-drawer-${namespace}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
.${drawerClass} {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: ${isMaximized ? 'calc(100vw - 240px)' : '1000px'};
|
||||
background-color: var(--mui-palette-background-default, #fafafa);
|
||||
color: var(--mui-palette-text-primary);
|
||||
box-shadow: -2px 0 8px rgba(0,0,0,0.15);
|
||||
overflow-y: auto;
|
||||
z-index: 1200;
|
||||
padding: 20px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div className={drawerClass}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: 0, color: 'var(--mui-palette-text-primary)' }}>
|
||||
Polaris — {namespace}
|
||||
</h2>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<button
|
||||
onClick={() => setIsMaximized(!isMaximized)}
|
||||
style={{
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
fontSize: '20px',
|
||||
cursor: 'pointer',
|
||||
padding: '4px 8px',
|
||||
color: 'var(--mui-palette-text-secondary, #666)',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.backgroundColor =
|
||||
'var(--mui-palette-action-hover, rgba(0, 0, 0, 0.04))';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
}}
|
||||
aria-label={isMaximized ? 'Minimize panel' : 'Maximize panel'}
|
||||
title={isMaximized ? 'Minimize' : 'Maximize'}
|
||||
>
|
||||
{isMaximized ? '⊟' : '⊡'}
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
fontSize: '24px',
|
||||
cursor: 'pointer',
|
||||
padding: '4px 8px',
|
||||
color: 'var(--mui-palette-text-secondary, #666)',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.backgroundColor =
|
||||
'var(--mui-palette-action-hover, rgba(0, 0, 0, 0.04))';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
}}
|
||||
aria-label="Close panel"
|
||||
title="Close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SectionBox title="External">
|
||||
<NameValueTable
|
||||
rows={[
|
||||
{
|
||||
name: 'Polaris Dashboard',
|
||||
value: (
|
||||
<a href={getPolarisProxyUrl()} target="_blank" rel="noopener noreferrer">
|
||||
View in Polaris Dashboard
|
||||
</a>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
|
||||
<SectionBox title="Namespace Score">
|
||||
<NameValueTable
|
||||
rows={[
|
||||
{
|
||||
name: 'Score',
|
||||
value: <StatusLabel status={status}>{score}%</StatusLabel>,
|
||||
},
|
||||
{ name: 'Total Checks', value: String(counts.total) },
|
||||
{
|
||||
name: 'Pass',
|
||||
value: <StatusLabel status="success">{counts.pass}</StatusLabel>,
|
||||
},
|
||||
{
|
||||
name: 'Warning',
|
||||
value: <StatusLabel status="warning">{counts.warning}</StatusLabel>,
|
||||
},
|
||||
{
|
||||
name: 'Danger',
|
||||
value: <StatusLabel status="error">{counts.danger}</StatusLabel>,
|
||||
},
|
||||
{
|
||||
name: 'Skipped',
|
||||
value: (
|
||||
<span title="Only counts checks with Severity=ignore. Annotation-based exemptions are not included.">
|
||||
{counts.skipped}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
|
||||
<SectionBox title="Resources">
|
||||
<SimpleTable
|
||||
columns={[
|
||||
{ label: 'Name', getter: (row: Result) => row.Name },
|
||||
{ label: 'Kind', getter: (row: Result) => row.Kind },
|
||||
{
|
||||
label: 'Pass',
|
||||
getter: (row: Result) => (
|
||||
<StatusLabel status="success">{getResourceCounts(row).pass}</StatusLabel>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Warning',
|
||||
getter: (row: Result) => (
|
||||
<StatusLabel status="warning">{getResourceCounts(row).warning}</StatusLabel>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Danger',
|
||||
getter: (row: Result) => (
|
||||
<StatusLabel status="error">{getResourceCounts(row).danger}</StatusLabel>
|
||||
),
|
||||
},
|
||||
]}
|
||||
data={results}
|
||||
emptyMessage={`No resources found in namespace "${namespace}".`}
|
||||
/>
|
||||
</SectionBox>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NamespacesListView() {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const { data, loading, error } = usePolarisDataContext();
|
||||
|
||||
// Initialize from URL hash
|
||||
const [selectedNamespace, setSelectedNamespace] = useState<string | null>(
|
||||
location.hash.slice(1) || null
|
||||
);
|
||||
|
||||
// Sync drawer state when URL hash changes (browser back/forward)
|
||||
useEffect(() => {
|
||||
const hashNs = location.hash.slice(1);
|
||||
setSelectedNamespace(hashNs || null);
|
||||
}, [location.hash]);
|
||||
|
||||
const openNamespace = (ns: string) => {
|
||||
setSelectedNamespace(ns);
|
||||
history.push(`${location.pathname}#${ns}`);
|
||||
};
|
||||
|
||||
const closeNamespace = () => {
|
||||
setSelectedNamespace(null);
|
||||
history.push(location.pathname);
|
||||
};
|
||||
|
||||
// Handle keyboard navigation (Escape key closes drawer)
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && selectedNamespace) {
|
||||
closeNamespace();
|
||||
}
|
||||
};
|
||||
|
||||
if (selectedNamespace) {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedNamespace]);
|
||||
|
||||
if (loading) {
|
||||
return <Loader title="Loading Polaris audit data..." />;
|
||||
}
|
||||
@@ -92,13 +359,20 @@ export default function NamespacesListView() {
|
||||
{
|
||||
label: 'Namespace',
|
||||
getter: (row: NamespaceRow) => (
|
||||
<Link
|
||||
to={Router.createRouteURL('polaris-namespace', {
|
||||
namespace: row.namespace,
|
||||
})}
|
||||
<button
|
||||
onClick={() => openNamespace(row.namespace)}
|
||||
style={{
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
color: 'var(--link-color, #1976d2)',
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'underline',
|
||||
padding: 0,
|
||||
font: 'inherit',
|
||||
}}
|
||||
>
|
||||
{row.namespace}
|
||||
</Link>
|
||||
</button>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -130,6 +404,25 @@ export default function NamespacesListView() {
|
||||
emptyMessage="No namespaces found in Polaris audit data."
|
||||
/>
|
||||
</SectionBox>
|
||||
|
||||
{selectedNamespace && (
|
||||
<>
|
||||
<div
|
||||
onClick={closeNamespace}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
zIndex: 1100,
|
||||
}}
|
||||
aria-label="Close panel backdrop"
|
||||
/>
|
||||
<NamespaceDetailPanel namespace={selectedNamespace} onClose={closeNamespace} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import { NameValueTable, SectionBox } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
|
||||
import {
|
||||
NameValueTable,
|
||||
SectionBox,
|
||||
StatusLabel,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
import { getRefreshInterval, INTERVAL_OPTIONS, setRefreshInterval } from '../api/polaris';
|
||||
import {
|
||||
AuditData,
|
||||
getDashboardUrl,
|
||||
getRefreshInterval,
|
||||
INTERVAL_OPTIONS,
|
||||
setDashboardUrl,
|
||||
setRefreshInterval,
|
||||
} from '../api/polaris';
|
||||
|
||||
interface PluginSettingsProps {
|
||||
data?: { [key: string]: string | number | boolean };
|
||||
@@ -10,13 +22,63 @@ interface PluginSettingsProps {
|
||||
export default function PolarisSettings(props: PluginSettingsProps) {
|
||||
const { data, onDataChange } = props;
|
||||
const currentInterval = (data?.refreshInterval as number) ?? getRefreshInterval();
|
||||
const currentUrl = (data?.dashboardUrl as string) ?? getDashboardUrl();
|
||||
const [testing, setTesting] = React.useState(false);
|
||||
const [testResult, setTestResult] = React.useState<{ success: boolean; message: string } | null>(
|
||||
null
|
||||
);
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
||||
function handleIntervalChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
||||
const seconds = Number(e.target.value);
|
||||
setRefreshInterval(seconds);
|
||||
onDataChange?.({ ...data, refreshInterval: seconds });
|
||||
}
|
||||
|
||||
function handleUrlChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const url = e.target.value;
|
||||
setDashboardUrl(url);
|
||||
onDataChange?.({ ...data, dashboardUrl: url });
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
setTesting(true);
|
||||
setTestResult(null);
|
||||
|
||||
try {
|
||||
const baseUrl = currentUrl;
|
||||
const apiPath = baseUrl.endsWith('/') ? `${baseUrl}results.json` : `${baseUrl}/results.json`;
|
||||
const isFullUrl = apiPath.startsWith('http://') || apiPath.startsWith('https://');
|
||||
|
||||
let result: AuditData;
|
||||
|
||||
if (isFullUrl) {
|
||||
const response = await fetch(apiPath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
result = await response.json();
|
||||
} else {
|
||||
result = await ApiProxy.request(apiPath, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
setTestResult({
|
||||
success: true,
|
||||
message: `Connected successfully! Version: ${
|
||||
result.PolarisOutputVersion
|
||||
}, Last audit: ${new Date(result.AuditTime).toLocaleString()}`,
|
||||
});
|
||||
} catch (err) {
|
||||
setTestResult({
|
||||
success: false,
|
||||
message: `Connection failed: ${String(err)}`,
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionBox title="Polaris Settings">
|
||||
<NameValueTable
|
||||
@@ -24,7 +86,7 @@ export default function PolarisSettings(props: PluginSettingsProps) {
|
||||
{
|
||||
name: 'Refresh Interval',
|
||||
value: (
|
||||
<select value={currentInterval} onChange={handleChange}>
|
||||
<select value={currentInterval} onChange={handleIntervalChange}>
|
||||
{INTERVAL_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
@@ -33,6 +95,74 @@ export default function PolarisSettings(props: PluginSettingsProps) {
|
||||
</select>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Dashboard URL',
|
||||
value: (
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={currentUrl}
|
||||
onChange={handleUrlChange}
|
||||
placeholder="/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '4px 8px',
|
||||
border: '1px solid var(--mui-palette-divider, #e0e0e0)',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px',
|
||||
backgroundColor: 'var(--mui-palette-background-paper, #fff)',
|
||||
color: 'var(--mui-palette-text-primary, #000)',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: 'var(--mui-palette-text-secondary, #666)',
|
||||
marginTop: '4px',
|
||||
}}
|
||||
>
|
||||
Examples:
|
||||
<br />• K8s proxy:{' '}
|
||||
<code>/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/</code>
|
||||
<br />• Full URL: <code>https://my-polaris.example.com</code>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Connection Test',
|
||||
value: (
|
||||
<div>
|
||||
<button
|
||||
onClick={testConnection}
|
||||
disabled={testing}
|
||||
style={{
|
||||
padding: '6px 16px',
|
||||
backgroundColor: testing
|
||||
? 'var(--mui-palette-action-disabledBackground, #e0e0e0)'
|
||||
: 'var(--mui-palette-primary-main, #1976d2)',
|
||||
color: testing
|
||||
? 'var(--mui-palette-action-disabled, #9e9e9e)'
|
||||
: 'var(--mui-palette-primary-contrastText, #fff)',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: testing ? 'not-allowed' : 'pointer',
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{testing ? 'Testing...' : 'Test Connection'}
|
||||
</button>
|
||||
{testResult && (
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<StatusLabel status={testResult.success ? 'success' : 'error'}>
|
||||
{testResult.message}
|
||||
</StatusLabel>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
|
||||
+24
-10
@@ -1,12 +1,15 @@
|
||||
import {
|
||||
registerAppBarAction,
|
||||
registerDetailsViewSection,
|
||||
registerPluginSettings,
|
||||
registerRoute,
|
||||
registerSidebarEntry,
|
||||
} from '@kinvolk/headlamp-plugin/lib';
|
||||
import React from 'react';
|
||||
import { PolarisDataProvider } from './api/PolarisDataContext';
|
||||
import AppBarScoreBadge from './components/AppBarScoreBadge';
|
||||
import DashboardView from './components/DashboardView';
|
||||
import NamespaceDetailView from './components/NamespaceDetailView';
|
||||
import InlineAuditSection from './components/InlineAuditSection';
|
||||
import NamespacesListView from './components/NamespacesListView';
|
||||
import PolarisSettings from './components/PolarisSettings';
|
||||
|
||||
@@ -62,16 +65,27 @@ registerRoute({
|
||||
),
|
||||
});
|
||||
|
||||
registerRoute({
|
||||
path: '/polaris/ns/:namespace',
|
||||
sidebar: 'polaris-namespaces',
|
||||
name: 'polaris-namespace',
|
||||
exact: true,
|
||||
component: () => (
|
||||
// Register plugin settings
|
||||
registerPluginSettings('polaris', PolarisSettings, true);
|
||||
|
||||
// Register details view section for supported controller types
|
||||
registerDetailsViewSection(({ resource }) => {
|
||||
const supportedKinds = ['Deployment', 'StatefulSet', 'DaemonSet', 'Job', 'CronJob'];
|
||||
|
||||
if (!supportedKinds.includes(resource?.kind)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PolarisDataProvider>
|
||||
<NamespaceDetailView />
|
||||
<InlineAuditSection resource={resource} />
|
||||
</PolarisDataProvider>
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
registerPluginSettings('polaris', PolarisSettings, true);
|
||||
// Register app bar score badge
|
||||
registerAppBarAction(() => (
|
||||
<PolarisDataProvider>
|
||||
<AppBarScoreBadge />
|
||||
</PolarisDataProvider>
|
||||
));
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "@kinvolk/headlamp-plugin/config/plugins-tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vite/client", "vite-plugin-svgr/client", "vitest/globals", "lodash", "@testing-library/jest-dom"]
|
||||
"types": ["vite/client", "vite-plugin-svgr/client", "vitest/globals", "@testing-library/jest-dom"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user