Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,25 @@ Multiple computed methods can share the same fetch function to batch-load relate
```
**Why?** The `command` resource ensures proper execution context, authentication, connection handling, and works seamlessly across different connection types (local, SSH, container, etc.). See [lsblk.go](providers/os/resources/lsblk.go) for a complete example.

- **Never `return nil, nil` from a singular resource accessor without setting `StateIsNull` first.** When an accessor returns a single resource pointer (e.g., `(*mqlAwsSomeResource, error)`) and the value is legitimately null, you **must** set the field's state before returning. Otherwise the runtime doesn't know the field was resolved and may panic or re-fetch indefinitely.
```go
// WRONG: causes panic β€” runtime doesn't know the field is null
func (a *mqlAwsRoute53Record) healthCheck() (*mqlAwsRoute53HealthCheck, error) {
if healthCheckId == "" {
return nil, nil
}
}

// CORRECT: mark field as set + null, then return nil
func (a *mqlAwsRoute53Record) healthCheck() (*mqlAwsRoute53HealthCheck, error) {
if healthCheckId == "" {
a.HealthCheck.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
}
```
This applies to all `return nil, nil` paths: empty/missing IDs, access-denied fallbacks, nil API responses, and unset optional fields (e.g., `!a.Field.IsSet()`). Functions returning slices, maps, or basic types are **not** affected β€” only singular resource pointer returns.

### Step 4: Verification (Interactive)
Automated tests are rare for MQL resources (thin wrappers). **Interactive testing is standard.**

Expand Down
6 changes: 6 additions & 0 deletions providers/aws/resources/aws_ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,7 @@ func (a *mqlAwsEcsTaskDefinition) volumes() ([]any, error) {

func (a *mqlAwsEcsTaskDefinition) ephemeralStorage() (*mqlAwsEcsTaskDefinitionEphemeralStorage, error) {
if !a.EphemeralStorage.IsSet() {
a.EphemeralStorage.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
if a.EphemeralStorage.Error != nil {
Expand Down Expand Up @@ -1286,6 +1287,7 @@ func (a *mqlAwsEcsTaskDefinitionContainerDefinition) secrets() ([]any, error) {

func (a *mqlAwsEcsTaskDefinitionContainerDefinition) logConfiguration() (*mqlAwsEcsTaskDefinitionContainerDefinitionLogConfiguration, error) {
if !a.LogConfiguration.IsSet() {
a.LogConfiguration.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
if a.LogConfiguration.Error != nil {
Expand All @@ -1310,6 +1312,7 @@ func (a *mqlAwsEcsTaskDefinitionContainerDefinition) id() (string, error) {

func (a *mqlAwsEcsTaskDefinitionVolume) efsVolumeConfiguration() (*mqlAwsEcsTaskDefinitionVolumeEfsVolumeConfiguration, error) {
if !a.EfsVolumeConfiguration.IsSet() {
a.EfsVolumeConfiguration.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
if a.EfsVolumeConfiguration.Error != nil {
Expand All @@ -1320,6 +1323,7 @@ func (a *mqlAwsEcsTaskDefinitionVolume) efsVolumeConfiguration() (*mqlAwsEcsTask

func (a *mqlAwsEcsTaskDefinitionVolume) host() (*mqlAwsEcsTaskDefinitionVolumeHost, error) {
if !a.Host.IsSet() {
a.Host.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
if a.Host.Error != nil {
Expand All @@ -1330,6 +1334,7 @@ func (a *mqlAwsEcsTaskDefinitionVolume) host() (*mqlAwsEcsTaskDefinitionVolumeHo

func (a *mqlAwsEcsTaskDefinitionVolume) dockerVolumeConfiguration() (*mqlAwsEcsTaskDefinitionVolumeDockerVolumeConfiguration, error) {
if !a.DockerVolumeConfiguration.IsSet() {
a.DockerVolumeConfiguration.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
if a.DockerVolumeConfiguration.Error != nil {
Expand Down Expand Up @@ -1364,6 +1369,7 @@ func (a *mqlAwsEcsTaskDefinitionContainerDefinitionPortMapping) id() (string, er

func (a *mqlAwsEcsTaskDefinitionVolumeEfsVolumeConfiguration) authorizationConfig() (*mqlAwsEcsTaskDefinitionVolumeEfsVolumeConfigurationAuthorizationConfig, error) {
if !a.AuthorizationConfig.IsSet() {
a.AuthorizationConfig.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
if a.AuthorizationConfig.Error != nil {
Expand Down
13 changes: 11 additions & 2 deletions providers/aws/resources/aws_route53.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,15 @@ func (a *mqlAwsRoute53) hostedZones() ([]any, error) {

// Batch-fetch tags (up to 10 per API call)
tagsByID := batchFetchTags(ctx, svc, route53types.TagResourceTypeHostedzone, allZones, func(hz route53types.HostedZone) string {
return convert.ToValue(hz.Id)
id := strings.Split(convert.ToValue(hz.Id), "/")
// get the last part of the hosted zone ID, which is the actual ID used in tagging
return id[len(id)-1]
})

res := []any{}
for _, hz := range allZones {
tags := tagsByID[convert.ToValue(hz.Id)]
id := strings.Split(convert.ToValue(hz.Id), "/")
tags := tagsByID[id[len(id)-1]]

// Filter by tags
if conn.Filters.General.IsFilteredOutByTags(mapStringInterfaceToStringString(tags)) {
Expand Down Expand Up @@ -364,6 +367,7 @@ func (a *mqlAwsRoute53HostedZone) queryLoggingConfig() (*mqlAwsRoute53QueryLoggi
})
if err != nil {
if Is400AccessDeniedError(err) {
a.QueryLoggingConfig.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
return nil, err
Expand All @@ -384,6 +388,7 @@ func (a *mqlAwsRoute53HostedZone) queryLoggingConfig() (*mqlAwsRoute53QueryLoggi
return mqlQlc.(*mqlAwsRoute53QueryLoggingConfig), nil
}

a.QueryLoggingConfig.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}

Expand Down Expand Up @@ -505,6 +510,7 @@ func (a *mqlAwsRoute53Record) cidrRoutingConfig() (any, error) {
func (a *mqlAwsRoute53Record) healthCheck() (*mqlAwsRoute53HealthCheck, error) {
healthCheckId := a.HealthCheckId.Data
if healthCheckId == "" {
a.HealthCheck.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}

Expand All @@ -517,11 +523,13 @@ func (a *mqlAwsRoute53Record) healthCheck() (*mqlAwsRoute53HealthCheck, error) {
})
if err != nil {
if Is400AccessDeniedError(err) {
a.HealthCheck.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}
return nil, err
}
if resp == nil || resp.HealthCheck == nil {
a.HealthCheck.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}

Expand Down Expand Up @@ -640,6 +648,7 @@ func (a *mqlAwsRoute53KeySigningKey) hostedZone() (*mqlAwsRoute53HostedZone, err
func (a *mqlAwsRoute53KeySigningKey) kmsKey() (*mqlAwsKmsKey, error) {
kmsArn := a.KmsArn.Data
if kmsArn == "" {
a.KmsKey.State = plugin.StateIsSet | plugin.StateIsNull
return nil, nil
}

Expand Down
Loading