(fix) Linter fixes

* Fix go vet mutex copy issues and remove report files from git

* remove redundant nil checks (len of nil slice is 0)

* handle error

* add missing error handling

* run modernize to change interface{} to any

* run other modernise commands

* use crypto instead of rand.Intn
This commit is contained in:
Julian Sommer
2026-01-06 14:20:56 +11:00
committed by GitHub
parent 66ad247c34
commit b9b8c65e79
37 changed files with 135 additions and 2374 deletions
+2 -1
View File
@@ -2,8 +2,9 @@ name: Build and Test Hive
on:
push:
branches: ["main"]
branches: [ main ]
paths: ["hive/**", ".github/workflows/**"]
pull_request:
workflow_dispatch:
jobs:
+1
View File
@@ -5,3 +5,4 @@ bin/hive
tmp/
config/dev.yaml
.DS_Store
tests/*.txt
+3 -2
View File
@@ -79,9 +79,10 @@ security: $(GOVULNCHECK) $(GOSECCHECK) $(GOSTATICCHECK)
$(GOSECCHECK) ./... > tests/gosec-report.txt || true
@echo "Gosec report saved to tests/gosec-report.txt"
$(GOSTATICCHECK) ./... > tests/staticcheck-report.txt || true
# default config + disable dep warning since we are using aws sdk v1
$(GOSTATICCHECK) -checks="all,-ST1000,-ST1003,-ST1016,-ST1020,-ST1021,-ST1022,-SA1019,-SA9005" ./... > tests/staticcheck-report.txt || true
@echo "Staticcheck report saved to tests/staticcheck-report.txt"
go vet ./... 2>&1 | tee tests/govet-report.txt || true
@echo "Go vet report saved to tests/govet-report.txt"
-509
View File
@@ -1,509 +0,0 @@
# Testing Implementation Summary
## ✅ Completed Work
### 1. Daemon Test Suite (`hive/daemon/daemon_test.go`)
Created comprehensive unit tests for the daemon's EC2 launch functionality:
**Test Components:**
- `startTestNATSServer()` - Embedded NATS server with auto port allocation
- `createTestDaemon()` - Test daemon factory with automatic cleanup
- `createValidRunInstancesInput()` - Test fixture for valid EC2 requests
**Test Suites:**
- ✅ `TestHandleEC2Launch_MessageParsing` - NATS message handling (5 test cases)
- ⏸️ `TestHandleEC2Launch_ResourceManagement` - Skipped (requires full infrastructure)
- ✅ `TestDaemon_Initialization` - Daemon creation and configuration
- ✅ `TestResourceManager` - Resource allocation logic
**Test Results:**
```bash
$ go test -v ./hive/daemon/
PASS: TestHandleEC2Launch_MessageParsing (0.17s)
✓ Valid_RunInstancesInput
✓ Invalid_Instance_Type
✓ Missing_Required_ImageId
✓ Invalid_MinCount_(zero)
✓ Malformed_JSON
SKIP: TestHandleEC2Launch_ResourceManagement
PASS: TestDaemon_Initialization (0.00s)
PASS: TestResourceManager (0.00s)
ok github.com/mulgadc/hive/hive/daemon 0.592s
```
### 2. Utils Test Suite (`hive/utils/utils_test.go`)
Added comprehensive tests for previously untested utility functions:
**New Tests:**
- ✅ `TestUnmarshalJsonPayload` - JSON unmarshaling (4 test cases)
- ✅ `TestMarshalJsonPayload` - JSON marshaling (2 test cases)
- ✅ `TestGenerateErrorPayload` - Error generation (3 test cases)
- ✅ `TestValidateErrorPayload` - Error validation (4 test cases)
- ✅ `TestMarshalToXML` - XML marshaling (3 test cases)
- ✅ `TestKillProcess` - Process termination
- ✅ `TestStopProcess` - Service stop with PID cleanup
**Test Results:**
```bash
$ go test -v ./hive/utils/
PASS: TestGeneratePidFile (0.00s)
PASS: TestGenerateSocketFile (0.00s)
PASS: TestExecProcessAndKill (1.71s)
PASS: TestUnmarshalJsonPayload (0.00s)
PASS: TestMarshalJsonPayload (0.00s)
PASS: TestGenerateErrorPayload (0.00s)
PASS: TestValidateErrorPayload (0.00s)
PASS: TestMarshalToXML (0.00s)
PASS: TestKillProcess (4.51s)
PASS: TestStopProcess (4.01s)
ok github.com/mulgadc/hive/hive/utils 10.844s
```
### 3. Bug Fixes
Fixed critical bugs that prevented tests from passing:
#### Bug 1: Pointer-to-Pointer in UnmarshalJsonPayload
**Location:** `hive/utils/utils.go:283`
**Before:**
```go
err := decoder.Decode(&input) // input already a pointer, creates **Type
```
**After:**
```go
err := decoder.Decode(input) // Correct: input is already a pointer
```
#### Bug 2: Nil Pointer in handleEC2Launch
**Location:** `hive/daemon/daemon.go:638-645`
**Before:**
```go
var runInstancesInput *ec2.RunInstancesInput // nil pointer
errResp = utils.UnmarshalJsonPayload(runInstancesInput, msg.Data) // crashes
```
**After:**
```go
runInstancesInput := &ec2.RunInstancesInput{} // Initialize before use
errResp = utils.UnmarshalJsonPayload(runInstancesInput, msg.Data)
```
#### Bug 3: Uninitialized Slice Elements in launchEC2Instance
**Location:** `hive/daemon/daemon.go:852-856`
**Before:**
```go
reservation.Instances = make([]*ec2.Instance, 1)
reservation.Instances[0].SetInstanceId(instance.ID) // nil pointer dereference
```
**After:**
```go
reservation.Instances = make([]*ec2.Instance, 1)
reservation.Instances[0] = &ec2.Instance{
State: &ec2.InstanceState{},
}
reservation.Instances[0].SetInstanceId(instance.ID)
```
#### Bug 4: Same Issue in MarshalJsonPayload
**Location:** `hive/utils/utils.go:299`
**Before:**
```go
err := decoder.Decode(&input)
```
**After:**
```go
err := decoder.Decode(input)
```
### 4. Documentation
Created comprehensive testing documentation:
**Files Created:**
1. `hive/daemon/TESTING.md` (423 lines)
- Test structure and components
- macOS limitations and workarounds
- Test execution instructions
- Mock service strategies
- Debugging guide
2. `hive/daemon/TEST_SUMMARY.md` (467 lines)
- Test architecture highlights
- Known issues and limitations
- Recommendations for different platforms
- Next steps and roadmap
- Current test results
3. `TESTING_COMPLETE.md` (this file)
- Summary of all completed work
- Bug fixes
- Test results
- Future enhancements
## 📊 Test Coverage
### Overall Test Results
```bash
$ make test
✓ hive/awsec2query - All tests passing
✓ hive/awserrors - All tests passing
✓ hive/daemon - 3 passing, 1 skipped (infrastructure required)
✓ hive/gateway/ec2/instance - All tests passing
✓ hive/service - All tests passing
✓ hive/services/predastore - All tests passing (integration skipped)
✓ hive/services/viperblockd - All tests passing
✓ hive/utils - All tests passing (10 tests, 26 cases)
✓ hive/vm - All tests passing
OVERALL: PASS
```
### Code Coverage by Package
| Package | Tests | Coverage | Notes |
|---------|-------|----------|-------|
| daemon | 4 | ~60% | Message handling, resource mgmt, initialization |
| utils | 10 | ~85% | All major functions tested |
| gateway/ec2/instance | 2 | ~75% | RunInstances validation |
| vm | 2 | ~40% | Config generation, ID creation |
| services/* | 8 | ~70% | Service lifecycle, NATS integration |
## 🎯 Test Framework Features
### Daemon Tests
**Strengths:**
- ✅ Isolated NATS server per test
- ✅ Automatic resource cleanup
- ✅ Table-driven test design
- ✅ Comprehensive error scenarios
- ✅ AWS SDK integration testing
**Limitations:**
- ⚠️ Requires mocks for viperblock/nbdkit on macOS
- ⚠️ Full VM launch needs Linux + infrastructure
- ⚠️ Cloud-init volume creation not tested
### Utils Tests
**Strengths:**
- ✅ Comprehensive coverage of JSON/XML marshaling
- ✅ Process management testing
- ✅ Error handling validation
- ✅ Platform-aware (macOS vs Linux)
**Coverage:**
- ✅ PID file operations
- ✅ Socket file generation
- ✅ Process lifecycle (start/stop/kill)
- ✅ JSON payload marshaling/unmarshaling
- ✅ XML marshaling
- ✅ Error payload generation/validation
## 🐛 Bugs Fixed
### Critical (Blocking Tests)
1. **Pointer-to-pointer bugs** (2 instances)
- Impact: Caused all JSON unmarshaling to fail
- Fix: Remove unnecessary `&` operator
- Files: `utils.go:283, 299`
2. **Nil pointer dereference in handleEC2Launch**
- Impact: All EC2 launch requests crashed
- Fix: Initialize struct before passing to unmarshal
- File: `daemon.go:638`
3. **Uninitialized slice elements**
- Impact: Crash when setting instance fields
- Fix: Initialize slice elements before use
- File: `daemon.go:852-856`
### Total Lines Changed
- **Modified:** ~15 lines across 3 files
- **Added:** ~550 lines of tests
- **Documentation:** ~1,200 lines
## 📝 Test Scenarios Covered
### Daemon Tests
**Message Parsing:**
- ✅ Valid RunInstancesInput with all fields
- ✅ Invalid instance type rejection
- ✅ Missing required ImageId
- ✅ Invalid MinCount (zero)
- ✅ Malformed JSON handling
**Resource Management:**
- ✅ Instance type lookup
- ✅ Resource allocation/deallocation
- ✅ Capacity checking
- ✅ System resource detection
### Utils Tests
**JSON Operations:**
- ✅ Valid JSON unmarshaling
- ✅ Malformed JSON error handling
- ✅ Unknown field rejection (DisallowUnknownFields)
- ✅ Empty JSON handling
**Error Handling:**
- ✅ Error payload generation
- ✅ Error payload validation
- ✅ Success vs error discrimination
**XML Operations:**
- ✅ Struct marshaling
- ✅ Pointer marshaling
- ✅ Invalid type error handling
**Process Management:**
- ✅ Process creation and termination
- ✅ PID file read/write/remove
- ✅ Signal handling
- ✅ Non-existent process error handling
## 🚀 Running Tests
### Quick Test
```bash
# Run all tests
make test
# Run specific package
go test -v ./hive/daemon/
go test -v ./hive/utils/
# Run with race detection
go test -race ./...
# Run with coverage
go test -cover ./...
```
### Platform-Specific
**macOS (Unit Tests Only):**
```bash
# All passing unit tests
go test -v ./hive/daemon/
go test -v ./hive/utils/
```
**Linux (Full Integration):**
```bash
# Requires: predastore, viperblock, nbdkit, QEMU
# Remove SKIP_INTEGRATION flag
unset SKIP_INTEGRATION
go test -v ./hive/daemon/
```
**Docker (Recommended for Full Tests):**
```bash
docker-compose up test-env
docker exec -it hive-test go test -v ./...
```
## 🔍 What's Tested vs Not Tested
### ✅ Fully Tested
- NATS message parsing and routing
- JSON marshaling/unmarshaling
- Error generation and validation
- Resource manager logic
- PID file operations
- Process lifecycle management
- XML marshaling
- Input validation
- Instance type lookup
### ⏸️ Partially Tested (Mocks Needed for macOS)
- EC2 launch flow (stops at viperblock creation)
- Volume mounting (requires nbdkit)
- Cloud-init generation (requires S3 backend)
### ❌ Not Yet Tested (Requires Full Infrastructure)
- Complete VM launch
- QEMU process management
- QMP socket communication
- NBD volume attachment
- EBS volume lifecycle
- VM state transitions
- Concurrent instance launches
- Resource exhaustion scenarios
## 🎯 Future Enhancements
### Short Term (Next Sprint)
1. **Mock Services**
```go
// Mock EBS mount service for macOS
func startMockEBSService(t *testing.T, nc *nats.Conn) {
nc.Subscribe("ebs.mount", func(msg *nats.Msg) {
// Return mock NBD URI
})
}
```
2. **Integration Tests**
- Add `//go:build linux` tags
- Full VM launch tests
- Volume lifecycle tests
3. **Benchmark Tests**
```go
func BenchmarkEC2Launch(b *testing.B) {
// Measure launch performance
}
```
### Medium Term
1. **CI/CD Integration**
- GitHub Actions workflow
- Docker-based test environment
- Coverage reporting (codecov.io)
2. **Load Testing**
- Concurrent launch stress tests
- Resource exhaustion tests
- NATS throughput tests
3. **Mock Framework**
- Reusable mock services
- Test helpers package
- Platform-aware test skipping
### Long Term
1. **E2E Test Suite**
- Complete AWS SDK compatibility tests
- Multi-instance orchestration
- Failure recovery scenarios
2. **Performance Suite**
- Latency measurements
- Throughput benchmarks
- Resource utilization profiling
3. **Chaos Engineering**
- Network partition tests
- Service failure simulation
- Recovery time validation
## 📚 Documentation Structure
```
hive/
├── hive/daemon/
│ ├── daemon.go [FIXED: 3 bugs]
│ ├── daemon_test.go [NEW: 4 test suites]
│ ├── TESTING.md [NEW: 423 lines]
│ └── TEST_SUMMARY.md [NEW: 467 lines]
├── hive/utils/
│ ├── utils.go [FIXED: 2 bugs]
│ └── utils_test.go [ENHANCED: +10 tests]
└── TESTING_COMPLETE.md [NEW: This file]
```
## 🎉 Success Metrics
### Before
- ❌ daemon tests: 0 tests, crashes on startup
- ❌ utils tests: 3 tests, missing coverage for 13+ functions
- ❌ Critical bugs: 4 blocking issues
- ❌ Documentation: None
### After
- ✅ daemon tests: 4 test suites, 5 test cases passing
- ✅ utils tests: 10 test suites, 26 test cases passing
- ✅ Critical bugs: All fixed
- ✅ Documentation: 1,200+ lines
- ✅ Total test cases: 50+ across all packages
- ✅ Overall: `make test` passes 100%
## 💡 Key Learnings
### Test Design
1. **Embedded NATS server is ideal for testing**
- No external dependencies
- Port auto-allocation prevents conflicts
- Fast startup/teardown
2. **Table-driven tests scale well**
- Easy to add new cases
- Clear test structure
- Minimal code duplication
3. **Platform differences matter**
- macOS vs Linux signal handling
- Process cleanup timing
- File system behavior
### Bug Patterns
1. **Pointer confusion is common**
- Double-check `&` usage
- Verify struct initialization
- Test nil pointer paths
2. **AWS SDK requires explicit initialization**
- Can't use SetX() on nil pointers
- Must initialize nested structs
- Slice make() != slice element initialization
3. **JSON unmarshaling needs initialized target**
- Can't unmarshal into nil pointer
- DisallowUnknownFields is strict
- Empty {} is valid but may be treated as error
## 🔗 Related Files
- `CLAUDE.md` - Project overview and development guide
- `HIVE_DEVELOPMENT_PLAN.md` - Roadmap and feature planning
- `hive/daemon/TESTING.md` - Detailed testing guide
- `hive/daemon/TEST_SUMMARY.md` - Test architecture and status
- `hive/utils/utils_test.go` - Utils test implementation
- `hive/daemon/daemon_test.go` - Daemon test implementation
## ✅ Acceptance Criteria Met
- [x] Created `daemon_test.go` with NATS server setup
- [x] Implemented `handleEC2Launch` message tests
- [x] Created sample RunInstancesInput test cases
- [x] Validated expected arguments and error handling
- [x] Documented macOS limitations (viperblock, nbdkit)
- [x] Provided suggestions for scaffolding
- [x] Fixed existing bugs (pointer-to-pointer issues)
- [x] Created comprehensive utils tests
- [x] All tests passing with `make test`
- [x] Detailed documentation created
## 🏁 Conclusion
The test framework is now production-ready for unit testing on both macOS and Linux. The daemon EC2 launch functionality has comprehensive message handling tests, and the utils package is thoroughly tested. All critical bugs have been fixed, and the codebase is ready for further development.
**Next Steps:**
1. Implement mock services for full macOS testing
2. Add Linux-specific integration tests
3. Set up CI/CD pipeline with Docker
4. Expand coverage to volume lifecycle operations
+5 -1
View File
@@ -724,7 +724,11 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Error reading response body: %v\n", err)
os.Exit(1)
}
if resp.StatusCode != 200 {
var errResp config.NodeJoinResponse
+1 -1
View File
@@ -47,7 +47,7 @@ func GenerateConfigFiles(configs []ConfigFile, configSettings ConfigSettings) er
for _, cfg := range configs {
if err := GenerateConfigFile(cfg.Path, cfg.Template, configSettings); err != nil {
return fmt.Errorf("Error creating %s: %v\n", cfg.Name, err)
return fmt.Errorf("error creating %s: %v", cfg.Name, err)
}
fmt.Printf("✅ Created: %s\n", cfg.Name)
}
+1 -1
View File
@@ -29,7 +29,7 @@ args["TagSpecification.2.Tag.1.Value"] = "Production"
*/
func QueryParamsToStruct(params map[string]string, out interface{}) error {
func QueryParamsToStruct(params map[string]string, out any) error {
v := reflect.ValueOf(out)
// Must be a pointer to a struct
+15 -5
View File
@@ -596,8 +596,15 @@ func (d *Daemon) WriteState() error {
d.Instances.Mu.Lock()
defer d.Instances.Mu.Unlock()
// Create a struct without the mutex to avoid copying the lock
state := struct {
VMS map[string]*vm.VM `json:"vms"`
}{
VMS: d.Instances.VMS,
}
// Pretty print JSON with indent
jsonData, err := json.MarshalIndent(d.Instances, "", " ")
jsonData, err := json.MarshalIndent(state, "", " ")
if err != nil {
return err
@@ -753,7 +760,7 @@ func (d *Daemon) SendQMPCommand(q *qmp.QMPClient, cmd qmp.QMPCommand, instanceId
// Optional: handle events here
continue
}
if errObj, ok := msg["error"].(map[string]interface{}); ok {
if errObj, ok := msg["error"].(map[string]any); ok {
return nil, fmt.Errorf("QMP error: %s: %s", errObj["class"], errObj["desc"])
}
if _, ok := msg["return"]; ok {
@@ -809,7 +816,7 @@ func (d *Daemon) handleEC2Events(msg *nats.Msg) {
slog.Info("handleEC2RunInstances launched", "instanceId", instance.ID)
resp = &qmp.QMPResponse{
Return: []byte(fmt.Sprintf(`{"status":"running","instanceId":"%s"}`, instance.ID)),
Return: fmt.Appendf(nil, `{"status":"running","instanceId":"%s"}`, instance.ID),
}
} else {
@@ -1080,7 +1087,6 @@ func (d *Daemon) stopInstance(instances map[string]*vm.VM, deleteVolume bool) er
// Run asynchronously within a worker group
for _, instance := range instances {
instance := instance // capture loop variable
wg.Add(1)
@@ -1451,6 +1457,10 @@ func (d *Daemon) StartInstance(instance *vm.VM) error {
// TODO: Toggle SSH local port forwarding based on config (debugging use)
sshDebugPort, err := viperblock.FindFreePort()
if err != nil {
slog.Error("Failed to find free port", "err", err)
return err
}
// Just the ipv4 port required
sshDebugPort = strings.Replace(sshDebugPort, "[::]:", "", 1)
@@ -1865,7 +1875,7 @@ func (d *Daemon) handleEC2DescribeInstances(msg *nats.Msg) {
// Filter instances if specific instance IDs were requested
instanceIDFilter := make(map[string]bool)
if describeInstancesInput.InstanceIds != nil && len(describeInstancesInput.InstanceIds) > 0 {
if len(describeInstancesInput.InstanceIds) > 0 {
for _, id := range describeInstancesInput.InstanceIds {
if id != nil {
instanceIDFilter[*id] = true
+1 -2
View File
@@ -138,7 +138,7 @@ func TestHandleEC2RunInstances_MessageParsing(t *testing.T) {
tests := []struct {
name string
input interface{}
input any
expectError bool
errorInPayload bool
validate func(t *testing.T, reply *nats.Msg)
@@ -382,7 +382,6 @@ func TestDaemon_Initialization(t *testing.T) {
assert.NotNil(t, daemon)
assert.NotNil(t, daemon.resourceMgr)
assert.NotNil(t, daemon.Instances)
assert.NotNil(t, daemon.Instances.VMS)
assert.Equal(t, cfg, daemon.config)
}
+1 -1
View File
@@ -73,7 +73,7 @@ func TestValidateCreateImageInput(t *testing.T) {
func TestEC2ProcessCreateImage(t *testing.T) {
tests := []struct {
name string
payload interface{}
payload any
rawJSON []byte
wantValidationErr bool
wantErrCode string
@@ -202,7 +202,7 @@ func TestEC2ProcessRunInstances(t *testing.T) {
tests := []struct {
name string
payload interface{}
payload any
rawJSON []byte
wantValidationErr bool
wantErrCode string
+2 -2
View File
@@ -13,7 +13,7 @@ import (
// StartInstances sends start commands to specified instances via NATS
func StartInstances(input *ec2.StartInstancesInput, natsConn *nats.Conn) (*ec2.StartInstancesOutput, error) {
if input.InstanceIds == nil || len(input.InstanceIds) == 0 {
if len(input.InstanceIds) == 0 {
return nil, fmt.Errorf("no instance IDs provided")
}
@@ -33,7 +33,7 @@ func StartInstances(input *ec2.StartInstancesInput, natsConn *nats.Conn) (*ec2.S
ID: instanceID,
QMPCommand: qmp.QMPCommand{
Execute: "cont",
Arguments: map[string]interface{}{},
Arguments: map[string]any{},
},
Attributes: qmp.Attributes{
StopInstance: false,
+2 -2
View File
@@ -14,7 +14,7 @@ import (
// StopInstances sends stop commands to specified instances via NATS
// Uses system_powerdown without stop_instance attribute to allow restart
func StopInstances(input *ec2.StopInstancesInput, natsConn *nats.Conn) (*ec2.StopInstancesOutput, error) {
if input.InstanceIds == nil || len(input.InstanceIds) == 0 {
if len(input.InstanceIds) == 0 {
return nil, fmt.Errorf("no instance IDs provided")
}
@@ -35,7 +35,7 @@ func StopInstances(input *ec2.StopInstancesInput, natsConn *nats.Conn) (*ec2.Sto
ID: instanceID,
QMPCommand: qmp.QMPCommand{
Execute: "system_powerdown",
Arguments: map[string]interface{}{},
Arguments: map[string]any{},
},
Attributes: qmp.Attributes{
StopInstance: false, // Allow restart on daemon/node restart
@@ -14,7 +14,7 @@ import (
// TerminateInstances sends terminate commands to specified instances via NATS
// Uses system_powerdown with stop_instance attribute to prevent restart
func TerminateInstances(input *ec2.TerminateInstancesInput, natsConn *nats.Conn) (*ec2.TerminateInstancesOutput, error) {
if input.InstanceIds == nil || len(input.InstanceIds) == 0 {
if len(input.InstanceIds) == 0 {
return nil, fmt.Errorf("no instance IDs provided")
}
@@ -35,7 +35,7 @@ func TerminateInstances(input *ec2.TerminateInstancesInput, natsConn *nats.Conn)
ID: instanceID,
QMPCommand: qmp.QMPCommand{
Execute: "system_powerdown",
Arguments: map[string]interface{}{},
Arguments: map[string]any{},
},
Attributes: qmp.Attributes{
StopInstance: true, // Prevent restart on daemon/node restart
+1 -1
View File
@@ -59,7 +59,7 @@ func TestValidateCreateKeyPairInput(t *testing.T) {
func TestEC2ProcessCreateKeyPair(t *testing.T) {
tests := []struct {
name string
payload interface{}
payload any
rawJSON []byte
wantValidationErr bool
wantErrCode string
+1 -1
View File
@@ -64,7 +64,7 @@ func ValidateImportKeyPairInput(input *ec2.ImportKeyPairInput) (err error) {
return errors.New("MissingParameter")
}
if input.PublicKeyMaterial == nil || len(input.PublicKeyMaterial) == 0 {
if len(input.PublicKeyMaterial) == 0 {
return errors.New("MissingParameter")
}
+3 -3
View File
@@ -224,8 +224,8 @@ func (gw *GatewayConfig) ErrorHandler(ctx *fiber.Ctx, err error) error {
// Parse AWS query arguments (used by some services like EC2/S3)
func ParseAWSQueryArgs(query string) map[string]string {
params := make(map[string]string)
pairs := strings.Split(query, "&")
for _, pair := range pairs {
pairs := strings.SplitSeq(query, "&")
for pair := range pairs {
kv := strings.SplitN(pair, "=", 2)
if len(kv) == 2 {
params[kv[0]] = kv[1]
@@ -261,7 +261,7 @@ func GenerateEC2ErrorResponse(code, message, requestID string) (output []byte) {
return output
}
func ParseArgsToStruct(input *interface{}, args map[string]string) (err error) {
func ParseArgsToStruct(input *any, args map[string]string) (err error) {
// Generated from input shape: RunInstancesRequest
err = awsec2query.QueryParamsToStruct(args, input)
+8 -4
View File
@@ -2,10 +2,11 @@ package handlers_ec2_instance
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"log/slog"
"math/rand"
"math/big"
"os"
"strings"
"text/template"
@@ -163,7 +164,7 @@ func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, inst
var snapshotId string
// Handle block device mappings
if input.BlockDeviceMappings != nil && len(input.BlockDeviceMappings) > 0 {
if len(input.BlockDeviceMappings) > 0 {
size = int(*input.BlockDeviceMappings[0].Ebs.VolumeSize)
deviceName = *input.BlockDeviceMappings[0].DeviceName
volumeType = *input.BlockDeviceMappings[0].Ebs.VolumeType
@@ -172,7 +173,10 @@ func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, inst
// Determine image ID and snapshot ID
if strings.HasPrefix(*input.ImageId, "ami-") {
randomNumber := rand.Intn(100_000_000)
randomNumber, err := rand.Int(rand.Reader, big.NewInt(100_000_000))
if err != nil {
return err
}
imageId = viperblock.GenerateVolumeID("vol", fmt.Sprintf("%d-%s", randomNumber, *input.ImageId), "predastore", time.Now().Unix())
snapshotId = *input.ImageId
} else {
@@ -357,7 +361,7 @@ func (s *InstanceServiceImpl) cloneAMIToVolume(input *ec2.RunInstancesInput, siz
numBlocks := len(data) / int(destVb.BlockSize)
// Write individual blocks to the new volume
for i := 0; i < numBlocks; i++ {
for i := range numBlocks {
// Check if the input is a Zero block
if bytes.Equal(data[i*int(destVb.BlockSize):(i+1)*int(destVb.BlockSize)], nullBlock) {
block++
@@ -333,7 +333,7 @@ func TestNATSInstanceService_RunInstances_MultipleInstances(t *testing.T) {
// Create reservation with count matching MinCount
count := int(*input.MinCount)
instances := make([]*ec2.Instance, count)
for i := 0; i < count; i++ {
for i := range count {
instanceId := aws.String(aws.StringValue(aws.String(fmt.Sprintf("i-%d", i))))
instances[i] = &ec2.Instance{
InstanceId: instanceId,
@@ -409,7 +409,7 @@ func TestNATSInstanceService_RunInstances_QueueGroup(t *testing.T) {
// Send multiple requests
requestCount := 10
for i := 0; i < requestCount; i++ {
for range requestCount {
reservation, err := service.RunInstances(input)
require.NoError(t, err)
require.NotNil(t, reservation)
+6 -6
View File
@@ -2,8 +2,8 @@ package handlers_ec2_key
import (
"bytes"
"crypto/md5"
"crypto/sha1"
"crypto/md5" //#nosec G501 - need md5 for AWS compatibility
"crypto/sha1" //#nosec G505 - need sha256 for AWS compatibility
"crypto/sha256"
"crypto/tls"
"encoding/base64"
@@ -222,7 +222,7 @@ func (s *KeyServiceImpl) calculateFingerprint(publicKeyData []byte, keyType stri
} else {
// RSA uses SHA-1 or MD5 fingerprint
// AWS uses MD5 for RSA keys for backward compatibility
hash := md5.Sum(keyData)
hash := md5.Sum(keyData) //#nosec G401 - need md5 for AWS compatibility
return formatFingerprint(hash[:], "MD5"), nil
}
}
@@ -240,8 +240,8 @@ func formatFingerprint(hash []byte, algorithm string) string {
// generateKeyPairID generates a unique key pair ID (similar to AWS key-xxxxx format)
func generateKeyPairID() string {
hash := sha1.New()
hash.Write([]byte(fmt.Sprintf("%d", os.Getpid())))
hash := sha1.New() //#nosec G401 - need sha1 for AWS compatibility
hash.Write(fmt.Appendf(nil, "%d", os.Getpid()))
return fmt.Sprintf("%x", hash.Sum(nil))[:16]
}
@@ -567,7 +567,7 @@ func (s *KeyServiceImpl) ImportKeyPair(input *ec2.ImportKeyPairInput) (*ec2.Impo
return nil, errors.New(awserrors.ErrorMissingParameter)
}
if input.PublicKeyMaterial == nil || len(input.PublicKeyMaterial) == 0 {
if len(input.PublicKeyMaterial) == 0 {
return nil, errors.New(awserrors.ErrorMissingParameter)
}
+40 -40
View File
@@ -226,7 +226,7 @@ func TestKeyPairFiltering(t *testing.T) {
// Simulate filtering logic from DescribeKeyPairs
var filtered []*ec2.KeyPairInfo
if tt.input.KeyNames != nil && len(tt.input.KeyNames) > 0 {
if len(tt.input.KeyNames) > 0 {
// Filter by KeyName
nameSet := make(map[string]bool)
for _, name := range tt.input.KeyNames {
@@ -237,7 +237,7 @@ func TestKeyPairFiltering(t *testing.T) {
filtered = append(filtered, kp)
}
}
} else if tt.input.KeyPairIds != nil && len(tt.input.KeyPairIds) > 0 {
} else if len(tt.input.KeyPairIds) > 0 {
// Filter by KeyPairId
idSet := make(map[string]bool)
for _, id := range tt.input.KeyPairIds {
@@ -279,19 +279,19 @@ func TestKeyPairMetadataPath(t *testing.T) {
accountID := "123456789"
tests := []struct {
name string
keyPairId string
expected string
name string
keyPairId string
expected string
}{
{
name: "Standard key pair ID",
keyPairId: "key-abcdef123456",
expected: "keys/123456789/key-abcdef123456.json",
name: "Standard key pair ID",
keyPairId: "key-abcdef123456",
expected: "keys/123456789/key-abcdef123456.json",
},
{
name: "Short key pair ID",
keyPairId: "key-12345",
expected: "keys/123456789/key-12345.json",
name: "Short key pair ID",
keyPairId: "key-12345",
expected: "keys/123456789/key-12345.json",
},
}
@@ -310,46 +310,46 @@ func TestKeyPairMetadataPath(t *testing.T) {
// TestImportKeyPairKeyTypeDetection tests key type detection from public key material
func TestImportKeyPairKeyTypeDetection(t *testing.T) {
tests := []struct {
name string
publicKeyData string
expectedType string
expectError bool
name string
publicKeyData string
expectedType string
expectError bool
}{
{
name: "ED25519 key",
publicKeyData: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl",
expectedType: "ed25519",
expectError: false,
name: "ED25519 key",
publicKeyData: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl",
expectedType: "ed25519",
expectError: false,
},
{
name: "RSA key",
publicKeyData: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz...",
expectedType: "rsa",
expectError: false,
name: "RSA key",
publicKeyData: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz...",
expectedType: "rsa",
expectError: false,
},
{
name: "ECDSA key",
publicKeyData: "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTY...",
expectedType: "ecdsa",
expectError: false,
name: "ECDSA key",
publicKeyData: "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTY...",
expectedType: "ecdsa",
expectError: false,
},
{
name: "Invalid format - no key data",
publicKeyData: "ssh-rsa",
expectedType: "",
expectError: true,
name: "Invalid format - no key data",
publicKeyData: "ssh-rsa",
expectedType: "",
expectError: true,
},
{
name: "Empty key material",
publicKeyData: "",
expectedType: "",
expectError: true,
name: "Empty key material",
publicKeyData: "",
expectedType: "",
expectError: true,
},
{
name: "Unsupported key type",
publicKeyData: "ssh-dss AAAAB3NzaC1kc3MAAACB...",
expectedType: "",
expectError: true,
name: "Unsupported key type",
publicKeyData: "ssh-dss AAAAB3NzaC1kc3MAAACB...",
expectedType: "",
expectError: true,
},
}
@@ -440,7 +440,7 @@ func TestImportKeyPairInputValidation(t *testing.T) {
if tt.input == nil || tt.input.KeyName == nil {
hasError = true
} else if tt.input.PublicKeyMaterial == nil || len(tt.input.PublicKeyMaterial) == 0 {
} else if len(tt.input.PublicKeyMaterial) == 0 {
hasError = true
}
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"sync"
)
type unmarshalTarget interface{}
type unmarshalTarget any
var CommandResponseTypes = map[string]unmarshalTarget{
"stop": &json.RawMessage{},
@@ -122,8 +122,8 @@ type QMPGreeting struct {
}
type QMPCommand struct {
Execute string `json:"execute"`
Arguments map[string]interface{} `json:"arguments,omitempty"`
Execute string `json:"execute"`
Arguments map[string]any `json:"arguments,omitempty"`
}
type QMPResponse struct {
+1 -1
View File
@@ -41,7 +41,7 @@ type Backend struct {
Config S3Config
}
func New(config interface{}) (backend *Backend) {
func New(config any) (backend *Backend) {
return &Backend{S3Backend: S3Backend{config: config.(S3Config)}}
}
+1 -1
View File
@@ -18,7 +18,7 @@ type Service interface {
Reload() error
}
func New(btype string, config interface{}) (Service, error) {
func New(btype string, config any) (Service, error) {
switch btype {
case "nats":
+1 -1
View File
@@ -20,7 +20,7 @@ type Service struct {
Config *config.ClusterConfig
}
func New(cfg interface{}) (svc *Service, err error) {
func New(cfg any) (svc *Service, err error) {
svc = &Service{
Config: cfg.(*config.ClusterConfig),
}
+1 -1
View File
@@ -16,7 +16,7 @@ type Service struct {
ConfigPath string
}
func New(cfg interface{}) (svc *Service, err error) {
func New(cfg any) (svc *Service, err error) {
svc = &Service{
Config: cfg.(*config.ClusterConfig),
}
+1 -1
View File
@@ -26,7 +26,7 @@ type Service struct {
Config *Config
}
func New(config interface{}) (svc *Service, err error) {
func New(config any) (svc *Service, err error) {
svc = &Service{
Config: config.(*Config),
}
+1 -1
View File
@@ -31,7 +31,7 @@ type Service struct {
Config *Config
}
func New(config interface{}) (svc *Service, err error) {
func New(config any) (svc *Service, err error) {
svc = &Service{
Config: config.(*Config),
}
@@ -21,6 +21,7 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"testing"
@@ -591,7 +592,7 @@ func TestIntegration_MultipleFiles(t *testing.T) {
fileCount := 5
keys := make([]string, fileCount)
for i := 0; i < fileCount; i++ {
for i := range fileCount {
keys[i] = fmt.Sprintf("multi-test-%d.txt", i)
content := fmt.Sprintf("content-file-%d", i)
@@ -611,11 +612,8 @@ func TestIntegration_MultipleFiles(t *testing.T) {
foundCount := 0
for _, obj := range listResult.Contents {
for _, key := range keys {
if *obj.Key == key {
foundCount++
break
}
if slices.Contains(keys, *obj.Key) {
foundCount++
}
}
assert.Equal(t, fileCount, foundCount, "All uploaded files should be listed")
+2 -2
View File
@@ -52,7 +52,7 @@ type Service struct {
// nbdkit -p 10812 --pidfile /tmp/vb-vol-1.pid ./lib/nbdkit-viperblock-plugin.so -v -f size=67108864 volume=vol-2 bucket=predastore region=ap-southeast-2 access_key="X" secret_key="Y" base_dir="/tmp/vb/" host="https://127.0.0.1:8443" cache_size=0
func New(config interface{}) (svc *Service, err error) {
func New(config any) (svc *Service, err error) {
svc = &Service{
Config: config.(*Config),
}
@@ -117,7 +117,7 @@ func launchService(cfg *Config) (err error) {
}
// TODO: Improve
nc.Publish("ebs.delete.response", []byte(fmt.Sprintf(`{"volume":"%s","deleted":true}`, ebsRequest.Volume)))
nc.Publish("ebs.delete.response", fmt.Appendf(nil, `{"volume":"%s","deleted":true}`, ebsRequest.Volume))
})
@@ -69,7 +69,7 @@ func createMockVolumeState(t *testing.T, baseDir, volumeName string) {
// Create a mock state file
stateFile := filepath.Join(volumeDir, "state.json")
mockState := map[string]interface{}{
mockState := map[string]any{
"volume_name": volumeName,
"volume_size": 1073741824, // 1GB
"created_at": time.Now().Unix(),
@@ -124,7 +124,7 @@ func TestConfigMutexThreadSafety(t *testing.T) {
iterations := 100
// Spawn multiple goroutines to append volumes concurrently
for i := 0; i < iterations; i++ {
for i := range iterations {
wg.Add(1)
go func(id int) {
defer wg.Done()
@@ -353,7 +353,7 @@ func TestConfigConcurrentRead(t *testing.T) {
iterations := 50
// Spawn multiple goroutines to read volumes concurrently
for i := 0; i < iterations; i++ {
for range iterations {
wg.Add(1)
go func() {
defer wg.Done()
+17 -7
View File
@@ -243,7 +243,10 @@ func RemovePidFile(serviceName string) error {
pidPath := pidPath()
os.Remove(filepath.Join(pidPath, fmt.Sprintf("%s.pid", serviceName)))
err := os.Remove(filepath.Join(pidPath, fmt.Sprintf("%s.pid", serviceName)))
if err != nil {
return err
}
return nil
}
@@ -276,7 +279,10 @@ func StopProcess(serviceName string) error {
}
// Remove PID file
RemovePidFile(serviceName)
err = RemovePidFile(serviceName)
if err != nil {
return err
}
return nil
}
@@ -362,7 +368,7 @@ func dirExists(path string) bool {
}
// Convert interface to XML
func MarshalToXML(payload interface{}) ([]byte, error) {
func MarshalToXML(payload any) ([]byte, error) {
var buf bytes.Buffer
enc := xml.NewEncoder(&buf)
@@ -372,14 +378,18 @@ func MarshalToXML(payload interface{}) ([]byte, error) {
slog.Error("BuildXML failed", "err", err)
return nil, err
}
enc.Flush()
if err := enc.Flush(); err != nil {
slog.Error("Flush failed", "err", err)
return nil, err
}
return buf.Bytes(), nil
}
// wrapWithLocation decorates payload with the requested locationName tag.
func GenerateXMLPayload(locationName string, payload interface{}) interface{} {
func GenerateXMLPayload(locationName string, payload any) any {
t := reflect.StructOf([]reflect.StructField{
{
Name: "Value",
@@ -431,7 +441,7 @@ func ValidateErrorPayload(payload []byte) (responseError ec2.ResponseError, err
// Unmarshal payload
func UnmarshalJsonPayload(input interface{}, jsonData []byte) []byte {
func UnmarshalJsonPayload(input any, jsonData []byte) []byte {
decoder := json.NewDecoder(bytes.NewReader(jsonData))
decoder.DisallowUnknownFields()
@@ -447,7 +457,7 @@ func UnmarshalJsonPayload(input interface{}, jsonData []byte) []byte {
}
func MarshalJsonPayload(input interface{}, jsonData []byte) []byte {
func MarshalJsonPayload(input any, jsonData []byte) []byte {
decoder := json.NewDecoder(bytes.NewReader(jsonData))
decoder.DisallowUnknownFields()
+1 -1
View File
@@ -323,7 +323,7 @@ func TestMarshalToXML(t *testing.T) {
tests := []struct {
name string
input interface{}
input any
expectError bool
validate func(t *testing.T, xmlData []byte)
}{
+1 -1
View File
@@ -208,7 +208,7 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
qemuArchitecture = "qemu-system-x86_64"
} else {
return nil, fmt.Errorf("Architecture missing")
return nil, fmt.Errorf("architecture missing")
}
// Note, require `-M` machine type for ARM (virt) if set to q35 (incompatible)
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
=== Symbol Results ===
No vulnerabilities found.
Your code is affected by 0 vulnerabilities.
This scan also found 0 vulnerabilities in packages you import and 5
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.