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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
- `archera-comparison` command: a read-only, default-off comparison of an
Archera commitment plan (table or JSON). See `docs/cli/archera-comparison.md`
(#2156).
- `--ri-account-scope` flag (`linked` default, `payer`) for Reserved Instance
recommendations, mirroring `--sp-account-scope` (#2181).

### Fixed

Expand Down
2 changes: 2 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Config struct {
PaymentOption string
CSVOutput string
SPAccountScope string
RIAccountScope string
// RecLookbackPeriod controls the LookbackPeriodInDays passed to
// GetReservationPurchaseRecommendation. Valid values: "7d", "30d", "60d"
// (recommendations.DefaultRecLookbackPeriod is the shared default).
Expand Down Expand Up @@ -143,6 +144,7 @@ func init() {
rootCmd.Flags().StringSliceVar(&toolCfg.IncludeSPTypes, "include-sp-types", []string{}, "Only include these Savings Plan types (comma-separated: Compute, EC2Instance, SageMaker, Database)")
rootCmd.Flags().StringSliceVar(&toolCfg.ExcludeSPTypes, "exclude-sp-types", []string{}, "Exclude these Savings Plan types (comma-separated: Compute, EC2Instance, SageMaker, Database)")

rootCmd.Flags().StringVar(&toolCfg.RIAccountScope, "ri-account-scope", "linked", "Cost Explorer account scope for Reserved Instance recommendations: linked or payer (payer applies to a management account)")
rootCmd.Flags().StringVar(&toolCfg.SPAccountScope, "sp-account-scope", "linked", "Cost Explorer account scope for Savings Plan recommendations: linked or payer (payer applies to a management account)")

// Purchase pipeline flags
Expand Down
1 change: 1 addition & 0 deletions cmd/multi_service_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ func fetchRecommendationsForRegion(
IncludeSPTypes: cfg.IncludeSPTypes,
ExcludeSPTypes: cfg.ExcludeSPTypes,
SPAccountScope: cfg.SPAccountScope,
RIAccountScope: cfg.RIAccountScope,
}
// For Savings Plans, region is only the endpoint the account-level call is
// signed in (spAPIRegion), not a filter. The provider folds params.Region
Expand Down
48 changes: 48 additions & 0 deletions cmd/ri_account_scope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"context"
"testing"

"github.com/LeanerCloud/cloud-commitments-go/pkg/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestValidateRIAccountScope(t *testing.T) {
orig := toolCfg.RIAccountScope
t.Cleanup(func() { toolCfg.RIAccountScope = orig })
for scope, wantErr := range map[string]bool{"": false, "linked": false, "payer": false, "PAYER": false, "org": true} {
toolCfg.RIAccountScope = scope
err := validateRIAccountScope()
if wantErr {
require.Error(t, err, scope)
assert.Contains(t, err.Error(), "invalid ri-account-scope")
} else {
require.NoError(t, err, scope)
}
}
}

func TestRIAccountScopeFlagDefault(t *testing.T) {
f := rootCmd.Flags().Lookup("ri-account-scope")
require.NotNil(t, f)
assert.Equal(t, "linked", f.DefValue)
}

func TestFetchRecommendationsForRegion_PassesRIAccountScope(t *testing.T) {
captureAppLog(t)
client := &MockRecommendationsClient{}
var got *common.RecommendationParams
client.On("GetRecommendations", mock.Anything, mock.Anything).
Run(func(args mock.Arguments) { got = args.Get(1).(*common.RecommendationParams) }).
Return([]common.Recommendation{}, nil)

_, err := fetchRecommendationsForRegion(context.Background(), client, common.ServiceRDS, "us-east-1",
Config{TermYears: 1, RIAccountScope: "payer"})
require.NoError(t, err)

require.NotNil(t, got)
assert.Equal(t, "payer", got.RIAccountScope)
}
14 changes: 13 additions & 1 deletion cmd/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ func validateFlags(cmd *cobra.Command, args []string) error {
return err
}

return validateSPAccountScope()
if err := validateSPAccountScope(); err != nil {
return err
}
return validateRIAccountScope()
}

// validateSPAccountScope validates --sp-account-scope. Empty means linked.
Expand All @@ -55,6 +58,15 @@ func validateSPAccountScope() error {
return fmt.Errorf("invalid sp-account-scope %q: must be linked or payer", toolCfg.SPAccountScope)
}

// validateRIAccountScope validates --ri-account-scope. Empty means linked.
func validateRIAccountScope() error {
switch strings.ToLower(toolCfg.RIAccountScope) {
case "", "linked", "payer":
return nil
}
return fmt.Errorf("invalid ri-account-scope %q: must be linked or payer", toolCfg.RIAccountScope)
}

func validateServices(cmd *cobra.Command) error {
if cmd != nil && cmd.Flags().Changed("services") && len(toolCfg.Services) == 0 {
return fmt.Errorf("--services must contain at least one service")
Expand Down
1 change: 1 addition & 0 deletions docs/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ All flags belong to the root command unless noted otherwise.
| `--exclude-accounts` | (none) | Exclude recommendations for these accounts, matched exactly (case-insensitive) on account name or ID. A value that only partly matches account names stops the run. |
| `--include-sp-types` | (all) | Only include these Savings Plan types: `Compute`, `EC2Instance`, `SageMaker`, `Database`. |
| `--exclude-sp-types` | (none) | Exclude these Savings Plan types. |
| `--ri-account-scope` | `linked` | Cost Explorer account scope for Reserved Instance recommendations: `linked` or `payer`. A management account may only get recommendations at `payer` scope. |
| `--sp-account-scope` | `linked` | Cost Explorer account scope for Savings Plan recommendations: `linked` or `payer`. A management account may only get recommendations at `payer` scope. |
| `--include-extended-support` | `false` | By default, instances running engine versions in AWS Extended Support are excluded because Extended Support surcharges can erase RI savings. Pass this flag to include them. |

Expand Down
Loading