feat: describe-account-attributes (#26)

This commit is contained in:
Julian Sommer
2026-02-09 10:19:33 +11:00
committed by GitHub
parent 0efa570cc5
commit e31e523380
3 changed files with 126 additions and 0 deletions
+10
View File
@@ -1,5 +1,9 @@
# Hive Development Roadmap
## Known Bugs
- **QueryParamsToStruct does not match all AWS query param key formats**: The parser tries the Go field name (e.g. `AttributeNames.1`) and the SDK `locationName` tag (e.g. `attributeName.1`), but the AWS CLI sends PascalCase singular form (e.g. `AttributeName.1`). This causes list filters like `--attribute-names` on `describe-account-attributes` to be silently ignored. Affects any SDK field where the Go name, locationName, and wire format all differ. Fix in `hive/awsec2query/parser.go`.
## Update Jan 2026
### Big rocks:
@@ -137,6 +141,12 @@ Instance `dmesg`:
| `describe-regions` | *(returns configured region, endpoint, opt-in status from config — input params ignored)* | `--region-names`, `--filters`, `--all-regions`, `--dry-run` | None | Return configured region from hive init config with Endpoint and OptInStatus → local-only response, no NATS | 1. List all regions<br>2. Filter by region name<br>3. Verify endpoint URL returned<br>4. Verify OptInStatus returned | **DONE** |
| `describe-availability-zones` | *(returns configured AZ, region, zone ID, state, opt-in status from config — input params ignored)* | `--zone-names`, `--filters`, `--all-availability-zones` | None | Return configured AZ from hive init config with zone ID, state, group name, network border group → local-only response, no NATS | 1. List all AZs<br>2. Filter by zone name<br>3. Verify zone state is available<br>4. Verify region name matches config | **DONE** |
### EC2 - Account Attributes
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---------|-------------------|---------------|---------------|-------------|------------|--------|
| `describe-account-attributes` | *(returns all 6 hardcoded attributes — input filter parsed but not applied due to QueryParamsToStruct bug, see Known Bugs)* | `--attribute-names` (filter not working) | None | Gateway parses input → returns static account attributes: supported-platforms=VPC, default-vpc=none, max-instances=100, vpc-max-security-groups-per-interface=5, max-elastic-ips=5, vpc-max-elastic-ips=20 → local-only response, no NATS | 1. List all account attributes<br>2. Filter by attribute name (blocked by parser bug)<br>3. Verify all 6 attributes returned with correct values | **DONE** |
### EC2 - VPC Networking (Core)
Requires Open vSwitch (`apt install openvswitch-switch openvswitch-common`). Single AZ for Hive v1.
+23
View File
@@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/gofiber/fiber/v2"
"github.com/mulgadc/hive/hive/awsec2query"
gateway_ec2_account "github.com/mulgadc/hive/hive/gateway/ec2/account"
gateway_ec2_image "github.com/mulgadc/hive/hive/gateway/ec2/image"
gateway_ec2_instance "github.com/mulgadc/hive/hive/gateway/ec2/instance"
gateway_ec2_key "github.com/mulgadc/hive/hive/gateway/ec2/key"
@@ -479,6 +480,28 @@ func (gw *GatewayConfig) EC2_Request(ctx *fiber.Ctx) error {
return errors.New("failed to marshal response to XML")
}
case "DescribeAccountAttributes":
var input = &ec2.DescribeAccountAttributesInput{}
err = awsec2query.QueryParamsToStruct(queryArgs, input)
if err != nil {
return err
}
output, err := gateway_ec2_account.DescribeAccountAttributes(input)
if err != nil {
return err
}
// Convert to XML
payload := utils.GenerateXMLPayload("DescribeAccountAttributesResponse", output)
xmlOutput, err = utils.MarshalToXML(payload)
if err != nil {
return errors.New("failed to marshal response to XML")
}
default:
err = errors.New("InvalidAction")
}
@@ -0,0 +1,93 @@
package gateway_ec2_account
import (
"log/slog"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)
// DescribeAccountAttributes returns static account attributes for the Hive platform
func DescribeAccountAttributes(input *ec2.DescribeAccountAttributesInput) (*ec2.DescribeAccountAttributesOutput, error) {
slog.Debug("DescribeAccountAttributes called")
// Build set of requested attribute names for filtering
requestedAttrs := make(map[string]bool)
for _, name := range input.AttributeNames {
if name != nil {
requestedAttrs[*name] = true
}
}
// If no specific attributes requested, return all
returnAll := len(requestedAttrs) == 0
var accountAttributes []*ec2.AccountAttribute
// supported-platforms - VPC only (no EC2-Classic)
if returnAll || requestedAttrs["supported-platforms"] {
accountAttributes = append(accountAttributes, &ec2.AccountAttribute{
AttributeName: aws.String("supported-platforms"),
AttributeValues: []*ec2.AccountAttributeValue{
{AttributeValue: aws.String("VPC")},
},
})
}
// default-vpc
if returnAll || requestedAttrs["default-vpc"] {
accountAttributes = append(accountAttributes, &ec2.AccountAttribute{
AttributeName: aws.String("default-vpc"),
AttributeValues: []*ec2.AccountAttributeValue{
{AttributeValue: aws.String("none")},
},
})
}
// max-instances
if returnAll || requestedAttrs["max-instances"] {
accountAttributes = append(accountAttributes, &ec2.AccountAttribute{
AttributeName: aws.String("max-instances"),
AttributeValues: []*ec2.AccountAttributeValue{
{AttributeValue: aws.String("100")},
},
})
}
// vpc-max-security-groups-per-interface
if returnAll || requestedAttrs["vpc-max-security-groups-per-interface"] {
accountAttributes = append(accountAttributes, &ec2.AccountAttribute{
AttributeName: aws.String("vpc-max-security-groups-per-interface"),
AttributeValues: []*ec2.AccountAttributeValue{
{AttributeValue: aws.String("5")},
},
})
}
// max-elastic-ips
if returnAll || requestedAttrs["max-elastic-ips"] {
accountAttributes = append(accountAttributes, &ec2.AccountAttribute{
AttributeName: aws.String("max-elastic-ips"),
AttributeValues: []*ec2.AccountAttributeValue{
{AttributeValue: aws.String("5")},
},
})
}
// vpc-max-elastic-ips
if returnAll || requestedAttrs["vpc-max-elastic-ips"] {
accountAttributes = append(accountAttributes, &ec2.AccountAttribute{
AttributeName: aws.String("vpc-max-elastic-ips"),
AttributeValues: []*ec2.AccountAttributeValue{
{AttributeValue: aws.String("20")},
},
})
}
output := &ec2.DescribeAccountAttributesOutput{
AccountAttributes: accountAttributes,
}
slog.Info("DescribeAccountAttributes completed", "attributeCount", len(accountAttributes))
return output, nil
}