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
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/reservations/armreservations v1.1.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20261020053130-ff6225d5ff04
github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261020071945-92722e673741
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20261017134002-3def3f898617
github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20261017134002-3def3f898617
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20261026094505-9cec521d6531
github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261026094505-9cec521d6531
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20261026094505-9cec521d6531
github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20261026094505-9cec521d6531
github.com/aws/aws-lambda-go v1.47.0
github.com/aws/aws-sdk-go-v2/service/kms v1.50.4
github.com/aws/aws-sdk-go-v2/service/lambda v1.89.0
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20261020053130-ff6225d5ff04 h1:D595qtoMszl7dkETbmVFzn+WPECQ7HuiKnsMlFI7bLA=
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20261020053130-ff6225d5ff04/go.mod h1:SCDEgHG6evjPh0RsoXof+J3aMmGXNFMrwbJIle7rMpQ=
github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261020071945-92722e673741 h1:AlfTl8EpfcRIWjU1FIx/XN+4ZgJjktjP/gPlRylPoF8=
github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261020071945-92722e673741/go.mod h1:cx+GF1J6RKy0CTOs2qhy+DM9rwFbKf3g0JIhYwqWz9k=
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20261017134002-3def3f898617 h1:K9wBX9ac1QwhkjW7BvDt0gl2Kt9fGdDytJyndoPRcBU=
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20261017134002-3def3f898617/go.mod h1:iOwl8e9EKGb3VQ4u/YjiqVzglhD6/GX1ABC3tBkqu0A=
github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20261017134002-3def3f898617 h1:VYVbQyoR+tIcPoXhkviclexKuU5H+A21hSXudB+kZag=
github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20261017134002-3def3f898617/go.mod h1:03vjHt5wlHtZEFAFOhosA+paDSqacWnKg9cVa68QnJ0=
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20261026094505-9cec521d6531 h1:x7bdeCDu+GdtVQ5Mcx8j0u09qiCssTPKtjru5nVAzQ0=
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20261026094505-9cec521d6531/go.mod h1:rEJErEsL40Np/ttf0RDoNNC2XQsN1GmWyFf34y0JLD8=
github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261026094505-9cec521d6531 h1:aZhRGSwaGxZ3gnPuR9iqHo069z0o0rJSoA7cgs9xqbg=
github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261026094505-9cec521d6531/go.mod h1:yVHEL5ceNS/CsDatzLH0+Qmwtoi7yY4tIHvqyvhdtig=
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20261026094505-9cec521d6531 h1:cWndlu++2ZWeSxvZthojAsTEHOdGU7BlIufJ60ssfAY=
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20261026094505-9cec521d6531/go.mod h1:zA51HKXtBvHHMYd+owRDmn4p8ialZrLFzI9I052uUmo=
github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20261026094505-9cec521d6531 h1:Y7mztemGOd3kChFN236+zG4XLQ72E/MNiEdh+SU3Sv8=
github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20261026094505-9cec521d6531/go.mod h1:/HxBqXBb5v69+EHLsibHnekkEYRP8TvyCmT6d9QRxt0=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
Expand Down
2 changes: 1 addition & 1 deletion internal/api/combined_plan_purchase_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (p *combinedProvider) GetServiceClient(_ context.Context, service common.Se

type combinedFactory struct{ provider *combinedProvider }

func (f combinedFactory) CreateAndValidateProvider(_ context.Context, name string, _ *provider.ProviderConfig) (provider.Provider, error) {
func (f combinedFactory) CreateAndValidateProvider(_ context.Context, name string, _ *provider.Config) (provider.Provider, error) {
if name != "azure" {
return nil, fmt.Errorf("fixture refuses unexpected provider %q", name)
}
Expand Down
14 changes: 7 additions & 7 deletions internal/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,14 +579,14 @@ func riExchangeArmStateOf(cfg *config.GlobalConfig) riExchangeArmState {
// armed reports whether this state will make the scheduled TaskRIExchangeReshape
// execute exchanges against the provider without a human approval step.
//
// The predicate mirrors the CONSUMER exactly. pkg/exchange/auto.go's
// processRecommendation routes to processManualExchange only on the literal
// "manual" and sends every other value -- including "", "Auto" and any typo --
// to processAutoExchange. Defining "armed" as mode == "auto" would therefore
// leave the gate below open on precisely the route that matters: PUT /api/config
// The predicate is deliberately wider than the consumer. pkg/exchange's
// RunAutoExchange executes without approval only for the literal "auto" and
// refuses any other unknown mode (including "") with an error, so a typo
// fails the scheduled run loudly instead of executing. PUT /api/config
// unmarshals onto GlobalConfig wholesale and GlobalConfig.Validate never
// constrains RIExchangeMode, so any non-"manual" string reaches the scheduler.
// Both axes of the guard must see what the scheduler sees.
// constrains RIExchangeMode, so any non-"manual" string can be stored; treating
// every such value as armed keeps the grant check closed if the consumer's
// mode handling ever changes.
func (s riExchangeArmState) armed() bool {
return s.enabled && s.mode != "manual"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (p *pauseClaimProvider) GetServiceClient(context.Context, common.ServiceTyp

type pauseClaimFactory struct{ provider *pauseClaimProvider }

func (f pauseClaimFactory) CreateAndValidateProvider(context.Context, string, *provider.ProviderConfig) (provider.Provider, error) {
func (f pauseClaimFactory) CreateAndValidateProvider(context.Context, string, *provider.Config) (provider.Provider, error) {
return f.provider, nil
}

Expand Down
2 changes: 1 addition & 1 deletion internal/api/handler_purchases_retry_fanout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (p *fanoutProvider) GetRecommendationsClient(_ context.Context) (provider.R

type fanoutProviderFactory struct{ prov *fanoutProvider }

func (f *fanoutProviderFactory) CreateAndValidateProvider(_ context.Context, _ string, _ *provider.ProviderConfig) (provider.Provider, error) {
func (f *fanoutProviderFactory) CreateAndValidateProvider(_ context.Context, _ string, _ *provider.Config) (provider.Provider, error) {
return f.prov, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (p *runNowProvider) GetServiceClient(context.Context, common.ServiceType, s

type runNowFactory struct{ provider *runNowProvider }

func (f runNowFactory) CreateAndValidateProvider(context.Context, string, *provider.ProviderConfig) (provider.Provider, error) {
func (f runNowFactory) CreateAndValidateProvider(context.Context, string, *provider.Config) (provider.Provider, error) {
return f.provider, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type globalFilterProvider struct {
provider.RecommendationsClient
}

func (p globalFilterProvider) CreateAndValidateProvider(_ context.Context, name string, cfg *provider.ProviderConfig) (provider.Provider, error) {
func (p globalFilterProvider) CreateAndValidateProvider(_ context.Context, name string, cfg *provider.Config) (provider.Provider, error) {
if name != "aws" || cfg != nil {
return nil, fmt.Errorf("unexpected provider request: %s", name)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/api/recommendation_identity_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type identityProviderFactory struct {
recs map[string][]common.Recommendation
}

func (f identityProviderFactory) CreateAndValidateProvider(ctx context.Context, name string, cfg *provider.ProviderConfig) (provider.Provider, error) {
func (f identityProviderFactory) CreateAndValidateProvider(ctx context.Context, name string, cfg *provider.Config) (provider.Provider, error) {
if name != "aws" || cfg == nil || cfg.AWSCredentialsProvider == nil {
return nil, fmt.Errorf("expected registered AWS fixture credentials")
}
Expand Down
34 changes: 29 additions & 5 deletions internal/providerfactory/bypass_guard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var bypassConstructors = map[string]map[string]bool{
}

// No non-test file under cmd/ or internal/ may call the concrete Azure/GCP
// provider constructors or set the deprecated ProviderConfig.Profile field.
// provider constructors or set the deprecated provider.Config.Profile field.
func TestNoRegistryBypassInProductionCode(t *testing.T) {
root := filepath.Join("..", "..")
var violations []string
Expand All @@ -50,15 +50,21 @@ func TestNoRegistryBypassInProductionCode(t *testing.T) {
}
require.Greater(t, scanned, 100, "the walk must actually reach the production sources")
assert.Empty(t, violations,
"build credentialed Azure/GCP providers with providerfactory.NewAzure/NewGCP and use the typed ProviderConfig fields")
"build credentialed Azure/GCP providers with providerfactory.NewAzure/NewGCP and use the typed provider.Config fields")
}

// registryBypasses returns one entry per bypassing call or deprecated
// Profile key in the file at path.
func registryBypasses(t *testing.T, path string) []string {
t.Helper()
return bypassesInSource(t, path, nil)
}

// bypassesInSource is registryBypasses over src (or the file at path when src is nil).
func bypassesInSource(t *testing.T, path string, src any) []string {
t.Helper()
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.SkipObjectResolution)
f, err := parser.ParseFile(fset, path, src, parser.SkipObjectResolution)
require.NoError(t, err, path)

imports := map[string]string{} // local name -> import path
Expand All @@ -80,7 +86,7 @@ func registryBypasses(t *testing.T, path string) []string {
out = append(out, fset.Position(node.Pos()).String()+": "+node.Sel.Name)
}
case *ast.CompositeLit:
if sel, ok := node.Type.(*ast.SelectorExpr); ok && sel.Sel.Name == "ProviderConfig" && selectorPkg(sel, imports) == providerPkg {
if sel, ok := node.Type.(*ast.SelectorExpr); ok && (sel.Sel.Name == "Config" || sel.Sel.Name == "ProviderConfig") && selectorPkg(sel, imports) == providerPkg {
out = append(out, profileKeys(fset, node)...)
}
}
Expand All @@ -105,8 +111,26 @@ func profileKeys(fset *token.FileSet, lit *ast.CompositeLit) []string {
continue
}
if id, ok := kv.Key.(*ast.Ident); ok && id.Name == "Profile" {
out = append(out, fset.Position(kv.Pos()).String()+": ProviderConfig.Profile")
out = append(out, fset.Position(kv.Pos()).String()+": provider.Config.Profile")
}
}
return out
}

func TestBypassGuardMatchesConfigNames(t *testing.T) {
const providerImport = `import "github.com/LeanerCloud/cloud-commitments-go/pkg/provider"`
cases := map[string]struct {
src string
want int
}{
"provider.Config": {providerImport + "\nvar _ = provider.Config{Profile: \"x\"}\n", 1},
"provider.ProviderConfig": {providerImport + "\nvar _ = provider.ProviderConfig{Profile: \"x\"}\n", 1},
"unrelated foo.Config": {"import foo \"example.com/foo\"\nvar _ = foo.Config{Profile: \"x\"}\n", 0},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
got := bypassesInSource(t, "x.go", "package x\n"+tc.src)
assert.Len(t, got, tc.want)
})
}
}
6 changes: 3 additions & 3 deletions internal/providerfactory/providerfactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func NewGCP(projectID string, ts oauth2.TokenSource) (provider.Provider, error)
func newAzure(reg *provider.Registry, subscriptionID string, cred azcore.TokenCredential) (provider.Provider, error) {
// A nil cred converts to a nil `any`, which the Azure factory treats as
// "no credential supplied".
return build(reg, azureName, &provider.ProviderConfig{
return build(reg, azureName, &provider.Config{
Name: azureName,
AzureSubscriptionID: subscriptionID,
AzureTokenCredential: cred,
Expand All @@ -73,14 +73,14 @@ func newGCP(reg *provider.Registry, projectID string, ts oauth2.TokenSource) (pr
if ts == nil {
return nil, ErrMissingGCPTokenSource
}
return build(reg, gcpName, &provider.ProviderConfig{
return build(reg, gcpName, &provider.Config{
Name: gcpName,
GCPProjectID: projectID,
GCPTokenSource: ts,
})
}

func build(reg *provider.Registry, name string, cfg *provider.ProviderConfig) (provider.Provider, error) {
func build(reg *provider.Registry, name string, cfg *provider.Config) (provider.Provider, error) {
prov, err := reg.GetProviderWithConfig(name, cfg)
if err != nil {
return nil, fmt.Errorf("providerfactory: build %s provider: %w", name, err)
Expand Down
8 changes: 4 additions & 4 deletions internal/providerfactory/providerfactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ func (f *fakeTokenSource) Token() (*oauth2.Token, error) {

// recordingRegistry returns a private registry whose name factory records
// the config it receives and returns want.
func recordingRegistry(t *testing.T, name string, want provider.Provider, wantErr error) (*provider.Registry, *[]*provider.ProviderConfig) {
func recordingRegistry(t *testing.T, name string, want provider.Provider, wantErr error) (*provider.Registry, *[]*provider.Config) {
t.Helper()
reg := provider.NewRegistry()
var got []*provider.ProviderConfig
require.NoError(t, reg.Register(name, func(cfg *provider.ProviderConfig) (provider.Provider, error) {
var got []*provider.Config
require.NoError(t, reg.Register(name, func(cfg *provider.Config) (provider.Provider, error) {
got = append(got, cfg)
return want, wantErr
}))
Expand Down Expand Up @@ -147,7 +147,7 @@ func TestBuild_PropagatesFactoryErrorsAndNilProviders(t *testing.T) {
func TestNewAzure_MatchesLegacyConstruction(t *testing.T) {
cred := &fakeAzureCred{id: "legacy"}

legacy, err := azureprovider.NewAzureProvider(&provider.ProviderConfig{Profile: testSubscriptionID}) //nolint:staticcheck // SA1019: this is the legacy baseline being compared against
legacy, err := azureprovider.NewAzureProvider(&provider.Config{Profile: testSubscriptionID}) //nolint:staticcheck // SA1019: this is the legacy baseline being compared against
require.NoError(t, err)
legacy.SetCredential(cred)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func purchaseThroughPinnedAzureClient(t *testing.T, exec *config.PurchaseExecuti
prov.On("GetServiceClient", mock.Anything, common.ServiceSavingsPlansAll, mock.Anything).Return(provider.ServiceClient(client), nil)

m := &Manager{config: store, email: sender, providerFactory: factory}
_, _, errs, err := m.processPurchaseRecommendations(ctx, exec, &config.PurchasePlan{Name: "p"}, "acct", (*provider.ProviderConfig)(nil), "")
_, _, errs, err := m.processPurchaseRecommendations(ctx, exec, &config.PurchasePlan{Name: "p"}, "acct", (*provider.Config)(nil), "")
require.NoError(t, err)
require.Empty(t, errs)
for i, rec := range exec.Recommendations {
Expand Down
Loading
Loading