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
54 changes: 33 additions & 21 deletions pkg/list/list_instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,38 +184,50 @@ func createInstance(stackName, componentName, componentType string, componentCon
return instance
}

// isProDriftDetectionEnabled checks if an instance has Atmos Pro drift detection enabled.
// Returns true if settings.pro.drift_detection.enabled == true and settings.pro.enabled != false.
func isProDriftDetectionEnabled(instance *schema.Instance) bool {
// isProEnabled checks if an instance has Atmos Pro enabled.
// Returns true only if settings.pro.enabled is the boolean true.
// Non-boolean values (e.g., the string "true") and missing values return false.
func isProEnabled(instance *schema.Instance) bool {
proSettings, ok := instance.Settings["pro"].(map[string]any)
if !ok {
return false
}

// Skip if pro is explicitly disabled
if proEnabled, ok := proSettings["enabled"].(bool); ok && !proEnabled {
enabled, ok := proSettings["enabled"].(bool)
return ok && enabled
}

// isDriftEnabled checks if an instance has drift detection enabled.
// Returns true only if settings.pro.drift_detection.enabled is the boolean true.
func isDriftEnabled(instance *schema.Instance) bool {
proSettings, ok := instance.Settings["pro"].(map[string]any)
if !ok {
return false
}

driftDetection, ok := proSettings["drift_detection"].(map[string]any)
drift, ok := proSettings["drift_detection"].(map[string]any)
if !ok {
return false
}

enabled, ok := driftDetection["enabled"].(bool)
enabled, ok := drift["enabled"].(bool)
return ok && enabled
}

// filterProEnabledInstances returns only instances that have Atmos Pro drift detection explicitly enabled
// via settings.pro.drift_detection.enabled == true, but excludes instances where settings.pro.enabled == false.
func filterProEnabledInstances(instances []schema.Instance) []schema.Instance {
filtered := make([]schema.Instance, 0, len(instances))
// countEnabledDisabled returns counts of pro-enabled and non-enabled instances,
// plus the number with drift detection enabled.
// "Disabled" covers both explicit `settings.pro.enabled: false` and instances
// with no `pro` config at all.
func countEnabledDisabled(instances []schema.Instance) (enabled, disabled, drift int) {
for i := range instances {
if isProDriftDetectionEnabled(&instances[i]) {
filtered = append(filtered, instances[i])
if isProEnabled(&instances[i]) {
enabled++
} else {
disabled++
}
if isDriftEnabled(&instances[i]) {
drift++
}
}
return filtered
return enabled, disabled, drift
}

// sortInstances sorts instances by stack and component.
Expand Down Expand Up @@ -338,7 +350,8 @@ func uploadInstancesWithDeps(
return errors.Join(errUtils.ErrFailedToUploadInstances, err)
}

u.PrintfMessageToTUI("Successfully uploaded instances to Atmos Pro API.")
enabled, disabled, drift := countEnabledDisabled(instances)
u.PrintfMessageToTUI("Successfully uploaded %d instances to Atmos Pro API (%d enabled, %d disabled, %d drift enabled).", len(instances), enabled, disabled, drift)
return nil
}

Expand Down Expand Up @@ -505,12 +518,11 @@ func ExecuteListInstancesCmd(opts *InstancesCommandOptions) error {

// Handle upload if requested.
if upload {
proInstances := filterProEnabledInstances(instances)
if len(proInstances) == 0 {
ui.Info("No Atmos Pro-enabled instances found; nothing to upload.")
if len(instances) == 0 {
ui.Info("No instances found; nothing to upload.")
return nil
}
if uploadErr := uploadInstances(proInstances); uploadErr != nil {
if uploadErr := uploadInstances(instances); uploadErr != nil {
Comment thread
osterman marked this conversation as resolved.
return uploadErr
}
}
Expand Down
20 changes: 8 additions & 12 deletions pkg/list/list_instances_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ func BenchmarkSortInstances(b *testing.B) {
}
}

// BenchmarkFilterProEnabledInstances measures performance of filtering Pro-enabled instances.
func BenchmarkFilterProEnabledInstances(b *testing.B) {
// BenchmarkCountEnabledDisabled measures performance of the tally helper.
func BenchmarkCountEnabledDisabled(b *testing.B) {
// Create 100 instances, half with Pro enabled, half without.
instances := make([]schema.Instance, 100)
for i := 0; i < 100; i++ {
Expand All @@ -66,9 +66,7 @@ func BenchmarkFilterProEnabledInstances(b *testing.B) {
// Enable Pro for every other instance.
if i%2 == 0 {
instance.Settings["pro"] = map[string]any{
"drift_detection": map[string]any{
"enabled": true,
},
"enabled": true,
}
}

Expand All @@ -77,7 +75,7 @@ func BenchmarkFilterProEnabledInstances(b *testing.B) {

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = filterProEnabledInstances(instances)
_, _, _ = countEnabledDisabled(instances)
}
}

Expand Down Expand Up @@ -113,22 +111,20 @@ func BenchmarkCreateInstance(b *testing.B) {
}
}

// BenchmarkIsProDriftDetectionEnabled measures performance of Pro drift detection check.
func BenchmarkIsProDriftDetectionEnabled(b *testing.B) {
// BenchmarkIsProEnabled measures performance of Pro-enabled check.
func BenchmarkIsProEnabled(b *testing.B) {
instance := &schema.Instance{
Component: "vpc",
Stack: "dev",
Settings: map[string]interface{}{
"pro": map[string]interface{}{
"drift_detection": map[string]interface{}{
"enabled": true,
},
"enabled": true,
},
},
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = isProDriftDetectionEnabled(instance)
_ = isProEnabled(instance)
}
}
28 changes: 11 additions & 17 deletions pkg/list/list_instances_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/assert"

"github.com/cloudposse/atmos/pkg/pro/dtos"
"github.com/cloudposse/atmos/pkg/schema"
)

// --- Mocks
Expand Down Expand Up @@ -39,7 +38,7 @@ func TestListInstancesCommandLogic(t *testing.T) {
"vpc": map[string]interface{}{
"settings": map[string]interface{}{
"pro": map[string]interface{}{
"drift_detection": map[string]interface{}{"enabled": true},
"enabled": true,
},
},
"metadata": map[string]interface{}{"type": "real"},
Expand All @@ -49,7 +48,7 @@ func TestListInstancesCommandLogic(t *testing.T) {
"app": map[string]interface{}{
"settings": map[string]interface{}{
"pro": map[string]interface{}{
"drift_detection": map[string]interface{}{"enabled": false},
"enabled": false,
},
},
"metadata": map[string]interface{}{"type": "real"},
Expand Down Expand Up @@ -79,7 +78,7 @@ func TestListInstancesCommandLogic(t *testing.T) {
upload: true,
stacks: mockStacks,
expectedListSize: 2,
expectedUploadNum: 1, // only vpc pro-enabled
expectedUploadNum: 2, // all real instances upload; Atmos Pro reconciles enabled/disabled
},
{
name: "describe error",
Expand All @@ -93,7 +92,7 @@ func TestListInstancesCommandLogic(t *testing.T) {
stacks: mockStacks,
uploadErr: errors.New("upload failed"),
expectedListSize: 2,
expectedUploadNum: 1,
expectedUploadNum: 2,
expectError: true,
},
}
Expand Down Expand Up @@ -121,9 +120,8 @@ func TestListInstancesCommandLogic(t *testing.T) {

if tc.upload {
api := &mockAPI{err: tc.uploadErr}
proDeps := filterProEnabledInstances(list)
uploadDeps := make([]dtos.UploadInstance, len(proDeps))
for i, inst := range proDeps {
uploadDeps := make([]dtos.UploadInstance, len(list))
for i, inst := range list {
uploadDeps[i] = dtos.UploadInstance{
Component: inst.Component,
Stack: inst.Stack,
Expand All @@ -138,11 +136,11 @@ func TestListInstancesCommandLogic(t *testing.T) {
return
}
assert.NoError(t, err)
assert.Equal(t, tc.expectedUploadNum, len(proDeps))
assert.Equal(t, tc.expectedUploadNum, len(uploadDeps))

// Verify API received correct payload
assert.NotNil(t, api.captured)
assert.Equal(t, len(proDeps), len(api.captured.Instances))
assert.Equal(t, len(uploadDeps), len(api.captured.Instances))
}
})
}
Expand All @@ -153,9 +151,7 @@ func TestCreateInstanceWithTemplateRendering(t *testing.T) {
componentConfigMap := map[string]any{
"settings": map[string]any{
"pro": map[string]any{
"drift_detection": map[string]any{
"enabled": true,
},
"enabled": true,
"pull_request": map[string]any{
"merged": map[string]any{
"workflows": map[string]any{
Expand Down Expand Up @@ -200,8 +196,6 @@ func TestCreateInstanceWithTemplateRendering(t *testing.T) {
assert.Equal(t, "tenant1-dev", inputs["github_environment"])
assert.Equal(t, "tenant1-ue2-dev", inputs["stack"])

// Verify that the instance would be included in pro-enabled instances
proInstances := filterProEnabledInstances([]schema.Instance{*instance})
assert.Len(t, proInstances, 1)
assert.Equal(t, "vpc", proInstances[0].Component)
// Verify that the instance is reported as pro-enabled.
assert.True(t, isProEnabled(instance))
}
98 changes: 1 addition & 97 deletions pkg/list/list_instances_comprehensive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestProcessComponentConfig(t *testing.T) {

t.Run("valid component config", func(t *testing.T) {
config := map[string]any{
"settings": map[string]any{"pro": map[string]any{"drift_detection": map[string]any{"enabled": true}}},
"settings": map[string]any{"pro": map[string]any{"enabled": true}},
"vars": map[string]any{"key": "value"},
}
result := processComponentConfig("stack1", "comp1", "terraform", config)
Expand Down Expand Up @@ -177,102 +177,6 @@ func TestSortInstances(t *testing.T) {
})
}

// Test filterProEnabledInstances edge cases.
func TestFilterProEnabledInstancesEdgeCases(t *testing.T) {
t.Run("instances with invalid pro settings", func(t *testing.T) {
instances := []schema.Instance{
{
Component: "vpc",
Stack: "stack1",
Settings: map[string]interface{}{
"pro": "invalid", // Not a map.
},
},
{
Component: "app",
Stack: "stack1",
Settings: map[string]interface{}{
"pro": map[string]interface{}{
"drift_detection": "invalid", // Not a map.
},
},
},
{
Component: "db",
Stack: "stack1",
Settings: map[string]interface{}{
"pro": map[string]interface{}{
"drift_detection": map[string]interface{}{
"enabled": "invalid", // Not a bool.
},
},
},
},
}

filtered := filterProEnabledInstances(instances)
assert.Empty(t, filtered)
})

t.Run("instances with missing pro settings", func(t *testing.T) {
instances := []schema.Instance{
{
Component: "vpc",
Stack: "stack1",
Settings: map[string]interface{}{},
},
{
Component: "app",
Stack: "stack1",
Settings: map[string]interface{}{
"other": "value",
},
},
}

filtered := filterProEnabledInstances(instances)
assert.Empty(t, filtered)
})

t.Run("instances with pro settings but missing drift_detection", func(t *testing.T) {
instances := []schema.Instance{
{
Component: "vpc",
Stack: "stack1",
Settings: map[string]interface{}{
"pro": map[string]interface{}{
"other": "value",
},
},
},
}

filtered := filterProEnabledInstances(instances)
assert.Empty(t, filtered)
})

t.Run("instances with pro settings and drift_detection.enabled is true", func(t *testing.T) {
instances := []schema.Instance{
{
Component: "vpc",
Stack: "stack1",
Settings: map[string]interface{}{
"pro": map[string]interface{}{
"drift_detection": map[string]interface{}{
"enabled": true,
},
},
},
},
}

filtered := filterProEnabledInstances(instances)
assert.Len(t, filtered, 1)
assert.Equal(t, "vpc", filtered[0].Component)
assert.Equal(t, "stack1", filtered[0].Stack)
})
}

// Test collectInstances edge cases.
func TestCollectInstances(t *testing.T) {
t.Run("empty stacks map", func(t *testing.T) {
Expand Down
Loading
Loading