diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 78b1e31..b93f889 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,3 +30,29 @@ jobs: uses: golangci/golangci-lint-action@v9 with: version: v2.10 + + mocks: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v6 + with: + cache: false + go-version-file: go.mod + - uses: infracost/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.INFRACOST_CI_APP_ID }} + private-key: ${{ secrets.INFRACOST_CI_APP_PRIVATE_KEY }} + owner: infracost + repositories: config + - run: | + git config --global url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/".insteadOf "https://github.com/" + - run: make mocks + - name: Check for changes + run: | + if [ -n "$(git diff --name-only)" ]; then + echo "Mock files are out of date. Please run 'make mock' and commit the changes." + git diff --name-only + exit 1 + fi diff --git a/.mockery.yml b/.mockery.yml new file mode 100644 index 0000000..d9c4c71 --- /dev/null +++ b/.mockery.yml @@ -0,0 +1,40 @@ +all: false +dir: '{{.InterfaceDir}}/mocks' +filename: mocks.go +force-file-write: true +formatter: goimports +formatter-options: + goimports: + all-errors: false + comments: true + format-only: true + fragment: false + local-prefix: "" + tab-indent: true + tab-width: 8 +generate: true +include-auto-generated: true +log-level: info +structname: '{{.Mock}}{{.InterfaceName}}' +pkgname: mocks +recursive: false +require-template-schema-exists: true +template: testify +template-schema: '{{.Template}}.schema.json' +packages: + github.com/infracost/cli/internal/api/dashboard: + interfaces: + Client: + github.com/infracost/cli/internal/api/events: + interfaces: + Client: + github.com/infracost/proto/gen/go/infracost/parser/api: + config: + dir: pkg/plugins/parser/mocks + interfaces: + ParserServiceClient: + github.com/infracost/proto/gen/go/infracost/provider: + config: + dir: pkg/plugins/providers/mocks + interfaces: + ProviderServiceClient: diff --git a/Makefile b/Makefile index 9f48cac..296b920 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,14 @@ lint_install: ## Install golangci-lint lint: lint_install ## Run linting operations golangci-lint run ./... +.PHONY: mockery_install +mockery_install: ## Install mockery + go install github.com/vektra/mockery/v3@latest + +.PHONY: mocks +mocks: mockery_install ## Generate mocks + mockery + .PHONY: fmt fmt: ## Format the code @gofmt -l . | while read -r f; do \ diff --git a/go.mod b/go.mod index 4965d1f..98a350b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/MicahParks/keyfunc/v3 v3.7.0 github.com/golang-jwt/jwt/v5 v5.3.0 - github.com/google/go-cmp v0.7.0 github.com/google/go-github/v83 v83.0.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-hclog v1.6.3 @@ -52,6 +51,7 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/soongo/path-to-regexp v1.6.4 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/numcpus v0.11.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect diff --git a/go.sum b/go.sum index 531c4d5..50bcf24 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/internal/api/dashboard/client.go b/internal/api/dashboard/client.go index 44557d1..188f798 100644 --- a/internal/api/dashboard/client.go +++ b/internal/api/dashboard/client.go @@ -11,11 +11,6 @@ import ( "github.com/infracost/cli/internal/api/dashboard/graphql" ) -type Client struct { - client *http.Client - config *Config -} - type RunParameters struct { OrganizationID string `json:"organizationId"` RepositoryName string `json:"repositoryName"` @@ -26,7 +21,20 @@ type RunParameters struct { FinopsPolicies []json.RawMessage `json:"finopsPolicies"` } -func (c *Client) RunParameters(ctx context.Context, repoURL, branchName string) (RunParameters, error) { +type Client interface { + RunParameters(ctx context.Context, repoURL, branchName string) (RunParameters, error) +} + +var ( + _ Client = (*client)(nil) +) + +type client struct { + client *http.Client + config *Config +} + +func (c *client) RunParameters(ctx context.Context, repoURL, branchName string) (RunParameters, error) { const query = `query RunParameters($repoUrl: String, $branchName: String) { runParameters(repoUrl: $repoUrl, branchName: $branchName) { organizationId diff --git a/internal/api/dashboard/config.go b/internal/api/dashboard/config.go index 807d71b..9ba5796 100644 --- a/internal/api/dashboard/config.go +++ b/internal/api/dashboard/config.go @@ -26,17 +26,20 @@ var ( type Config struct { Environment string `flagvalue:"environment"` Endpoint string `env:"INFRACOST_CLI_DASHBOARD_ENDPOINT" flag:"dashboard-endpoint;hidden" usage:"The endpoint for the Infracost dashboard"` + + // Can override this in tests. + Client func(httpClient *http.Client) Client } func (c *Config) Process() { if c.Endpoint == "" { c.Endpoint = defaultValues[c.Environment]["endpoint"] } -} -func (c *Config) Client(client *http.Client) *Client { - return &Client{ - client: client, - config: c, + c.Client = func(httpClient *http.Client) Client { + return &client{ + client: httpClient, + config: c, + } } } diff --git a/internal/api/dashboard/mocks/mocks.go b/internal/api/dashboard/mocks/mocks.go new file mode 100644 index 0000000..d5fd3ae --- /dev/null +++ b/internal/api/dashboard/mocks/mocks.go @@ -0,0 +1,111 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + "github.com/infracost/cli/internal/api/dashboard" + mock "github.com/stretchr/testify/mock" +) + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// RunParameters provides a mock function for the type MockClient +func (_mock *MockClient) RunParameters(ctx context.Context, repoURL string, branchName string) (dashboard.RunParameters, error) { + ret := _mock.Called(ctx, repoURL, branchName) + + if len(ret) == 0 { + panic("no return value specified for RunParameters") + } + + var r0 dashboard.RunParameters + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (dashboard.RunParameters, error)); ok { + return returnFunc(ctx, repoURL, branchName) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) dashboard.RunParameters); ok { + r0 = returnFunc(ctx, repoURL, branchName) + } else { + r0 = ret.Get(0).(dashboard.RunParameters) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = returnFunc(ctx, repoURL, branchName) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RunParameters_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunParameters' +type MockClient_RunParameters_Call struct { + *mock.Call +} + +// RunParameters is a helper method to define mock.On call +// - ctx context.Context +// - repoURL string +// - branchName string +func (_e *MockClient_Expecter) RunParameters(ctx interface{}, repoURL interface{}, branchName interface{}) *MockClient_RunParameters_Call { + return &MockClient_RunParameters_Call{Call: _e.mock.On("RunParameters", ctx, repoURL, branchName)} +} + +func (_c *MockClient_RunParameters_Call) Run(run func(ctx context.Context, repoURL string, branchName string)) *MockClient_RunParameters_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockClient_RunParameters_Call) Return(runParameters dashboard.RunParameters, err error) *MockClient_RunParameters_Call { + _c.Call.Return(runParameters, err) + return _c +} + +func (_c *MockClient_RunParameters_Call) RunAndReturn(run func(ctx context.Context, repoURL string, branchName string) (dashboard.RunParameters, error)) *MockClient_RunParameters_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/api/events/client.go b/internal/api/events/client.go index 151a151..9320665 100644 --- a/internal/api/events/client.go +++ b/internal/api/events/client.go @@ -10,12 +10,20 @@ import ( "github.com/infracost/cli/internal/logging" ) -type Client struct { +var ( + _ Client = (*client)(nil) +) + +type Client interface { + Push(ctx context.Context, event string, extra ...interface{}) +} + +type client struct { client *http.Client config *Config } -func (c *Client) Push(ctx context.Context, event string, extra ...interface{}) { +func (c *client) Push(ctx context.Context, event string, extra ...interface{}) { if isTest, ok := metadata["isTest"].(bool); ok && isTest { return } diff --git a/internal/api/events/config.go b/internal/api/events/config.go index 1a606a9..f4e1e14 100644 --- a/internal/api/events/config.go +++ b/internal/api/events/config.go @@ -4,18 +4,26 @@ import ( "net/http" ) +var ( + _ Config +) + type Config struct { Endpoint string `env:"INFRACOST_CLI_EVENTS_ENDPOINT" flag:"events-endpoint;hidden" usage:"The endpoint for the Infracost events service" default:"https://pricing.api.infracost.io"` + + Client func(httpClient *http.Client) Client } -func (c *Config) Client(client *http.Client) *Client { - // The events client may be used before config defaults are applied (e.g. - // to report early errors), so ensure the endpoint is always set. - if c.Endpoint == "" { - c.Endpoint = "https://pricing.api.infracost.io" - } - return &Client{ - client: client, - config: c, +func (c *Config) Process() { + c.Client = func(httpClient *http.Client) Client { + // The events client may be used before config defaults are applied (e.g. + // to report early errors), so ensure the endpoint is always set. + if c.Endpoint == "" { + c.Endpoint = "https://pricing.api.infracost.io" + } + return &client{ + client: httpClient, + config: c, + } } } diff --git a/internal/api/events/mocks/mocks.go b/internal/api/events/mocks/mocks.go new file mode 100644 index 0000000..aef506a --- /dev/null +++ b/internal/api/events/mocks/mocks.go @@ -0,0 +1,98 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + mock "github.com/stretchr/testify/mock" +) + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// Push provides a mock function for the type MockClient +func (_mock *MockClient) Push(ctx context.Context, event string, extra ...interface{}) { + if len(extra) > 0 { + _mock.Called(ctx, event, extra) + } else { + _mock.Called(ctx, event) + } + + return +} + +// MockClient_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push' +type MockClient_Push_Call struct { + *mock.Call +} + +// Push is a helper method to define mock.On call +// - ctx context.Context +// - event string +// - extra ...interface{} +func (_e *MockClient_Expecter) Push(ctx interface{}, event interface{}, extra ...interface{}) *MockClient_Push_Call { + return &MockClient_Push_Call{Call: _e.mock.On("Push", + append([]interface{}{ctx, event}, extra...)...)} +} + +func (_c *MockClient_Push_Call) Run(run func(ctx context.Context, event string, extra ...interface{})) *MockClient_Push_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []interface{} + var variadicArgs []interface{} + if len(args) > 2 { + variadicArgs = args[2].([]interface{}) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockClient_Push_Call) Return() *MockClient_Push_Call { + _c.Call.Return() + return _c +} + +func (_c *MockClient_Push_Call) RunAndReturn(run func(ctx context.Context, event string, extra ...interface{})) *MockClient_Push_Call { + _c.Run(run) + return _c +} diff --git a/internal/format/events.go b/internal/format/events.go index eb290a2..8e74205 100644 --- a/internal/format/events.go +++ b/internal/format/events.go @@ -26,7 +26,7 @@ import ( // the --sync-usage-file flow which doesn't exist in v2. // - remediationAttempts, remediationErrors, remediationOpportunities: not yet // implemented. -func (o *Output) TrackRun(ctx context.Context, client *events.Client, runSeconds float64, outputFormat string) { +func (o *Output) TrackRun(ctx context.Context, client events.Client, runSeconds float64, outputFormat string) { var totalResources int var totalSupported int var totalNoPrice int @@ -108,7 +108,7 @@ func (o *Output) TrackRun(ctx context.Context, client *events.Client, runSeconds // TrackDiff compares this output against a previous output and fires a // "cloud-issue-fixed" event for every policy violation that was present in // other but is no longer present in this output. Projects are matched by name. -func (o *Output) TrackDiff(ctx context.Context, client *events.Client, other *Output) { +func (o *Output) TrackDiff(ctx context.Context, client events.Client, other *Output) { if other == nil { return } @@ -130,7 +130,7 @@ func (o *Output) TrackDiff(ctx context.Context, client *events.Client, other *Ou } } -func trackFinopsDiff(ctx context.Context, client *events.Client, p *ProjectOutput, other []FinopsOutput) { +func trackFinopsDiff(ctx context.Context, client events.Client, p *ProjectOutput, other []FinopsOutput) { // Build a set of currently failing (policySlug, resourceName) pairs. current := make(map[string]map[string]struct{}) for _, r := range p.FinopsResults { @@ -161,7 +161,7 @@ func trackFinopsDiff(ctx context.Context, client *events.Client, p *ProjectOutpu } } -func trackTaggingDiff(ctx context.Context, client *events.Client, p *ProjectOutput, other []TaggingOutput) { +func trackTaggingDiff(ctx context.Context, client events.Client, p *ProjectOutput, other []TaggingOutput) { // Build a set of currently failing (policyId, resourceAddress) pairs. current := make(map[string]map[string]struct{}) for _, r := range p.TaggingResults { diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 3ee039b..99880f5 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -99,8 +99,8 @@ func (s *Scanner) ListPolicies(ctx context.Context, runParameters *dashboard.Run // TODO: eventually we should query which plugins are installed and avoid harcoding the list of default providers plugins := map[provider.Provider]func(hclog.Level) (provider.ProviderServiceClient, func(), error){ provider.Provider_PROVIDER_AWS: s.plugins.Providers.LoadAWS, - provider.Provider_PROVIDER_GOOGLE: s.plugins.Providers.LoadGCP, - provider.Provider_PROVIDER_AZURERM: s.plugins.Providers.LoadAzure, + provider.Provider_PROVIDER_GOOGLE: s.plugins.Providers.LoadGoogle, + provider.Provider_PROVIDER_AZURERM: s.plugins.Providers.LoadAzurerm, } var finOpsPolicies []FinOpsPolicy @@ -427,7 +427,7 @@ func (s *DirectoryScanner) ScanProject(ctx context.Context, project *repoconfig. switch rp { case provider.Provider_PROVIDER_AWS: - rs, ps, err := s.config.plugins.Providers.Process(ctx, rp, input, s.config.plugins.Providers.LoadAWS, s.config.logging.ToHCLogLevel()) + rs, ps, err := s.config.plugins.Providers.ProcessInput(ctx, rp, input, s.config.plugins.Providers.LoadAWS, s.config.logging.ToHCLogLevel()) if err != nil { return nil, fmt.Errorf("failed to execute AWS provider: %w", err) } @@ -435,7 +435,7 @@ func (s *DirectoryScanner) ScanProject(ctx context.Context, project *repoconfig. projectResult.Resources = append(projectResult.Resources, rs...) case provider.Provider_PROVIDER_GOOGLE: - rs, ps, err := s.config.plugins.Providers.Process(ctx, rp, input, s.config.plugins.Providers.LoadGCP, s.config.logging.ToHCLogLevel()) + rs, ps, err := s.config.plugins.Providers.ProcessInput(ctx, rp, input, s.config.plugins.Providers.LoadGoogle, s.config.logging.ToHCLogLevel()) if err != nil { return nil, fmt.Errorf("failed to execute GCP provider: %w", err) } @@ -443,7 +443,7 @@ func (s *DirectoryScanner) ScanProject(ctx context.Context, project *repoconfig. projectResult.Resources = append(projectResult.Resources, rs...) case provider.Provider_PROVIDER_AZURERM: - rs, ps, err := s.config.plugins.Providers.Process(ctx, rp, input, s.config.plugins.Providers.LoadAzure, s.config.logging.ToHCLogLevel()) + rs, ps, err := s.config.plugins.Providers.ProcessInput(ctx, rp, input, s.config.plugins.Providers.LoadAzurerm, s.config.logging.ToHCLogLevel()) if err != nil { return nil, fmt.Errorf("failed to execute Azure provider: %w", err) } diff --git a/pkg/plugins/parser/config.go b/pkg/plugins/parser/config.go index 20d463e..2420bf4 100644 --- a/pkg/plugins/parser/config.go +++ b/pkg/plugins/parser/config.go @@ -2,14 +2,23 @@ package parser import ( "github.com/hashicorp/go-hclog" + "github.com/infracost/cli/internal/config/process" "github.com/infracost/proto/gen/go/infracost/parser/api" ) +var ( + _ process.Processor = (*Config)(nil) +) + type Config struct { Plugin string `env:"INFRACOST_CLI_PARSER_PLUGIN"` Version string `env:"INFRACOST_CLI_PARSER_PLUGIN_VERSION"` + + Load func(level hclog.Level) (api.ParserServiceClient, func(), error) } -func (c *Config) Load(level hclog.Level) (api.ParserServiceClient, func(), error) { - return Connect(c.Plugin, level) +func (c *Config) Process() { + c.Load = func(level hclog.Level) (api.ParserServiceClient, func(), error) { + return Connect(c.Plugin, level) + } } diff --git a/pkg/plugins/parser/mocks/mocks.go b/pkg/plugins/parser/mocks/mocks.go new file mode 100644 index 0000000..11dd3e8 --- /dev/null +++ b/pkg/plugins/parser/mocks/mocks.go @@ -0,0 +1,206 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + "github.com/infracost/proto/gen/go/infracost/parser/api" + mock "github.com/stretchr/testify/mock" + "google.golang.org/grpc" +) + +// NewMockParserServiceClient creates a new instance of MockParserServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockParserServiceClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockParserServiceClient { + mock := &MockParserServiceClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockParserServiceClient is an autogenerated mock type for the ParserServiceClient type +type MockParserServiceClient struct { + mock.Mock +} + +type MockParserServiceClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockParserServiceClient) EXPECT() *MockParserServiceClient_Expecter { + return &MockParserServiceClient_Expecter{mock: &_m.Mock} +} + +// Initialize provides a mock function for the type MockParserServiceClient +func (_mock *MockParserServiceClient) Initialize(ctx context.Context, in *api.InitializeRequest, opts ...grpc.CallOption) (*api.InitializeResponse, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, in, opts) + } else { + tmpRet = _mock.Called(ctx, in) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Initialize") + } + + var r0 *api.InitializeResponse + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *api.InitializeRequest, ...grpc.CallOption) (*api.InitializeResponse, error)); ok { + return returnFunc(ctx, in, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *api.InitializeRequest, ...grpc.CallOption) *api.InitializeResponse); ok { + r0 = returnFunc(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*api.InitializeResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *api.InitializeRequest, ...grpc.CallOption) error); ok { + r1 = returnFunc(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockParserServiceClient_Initialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Initialize' +type MockParserServiceClient_Initialize_Call struct { + *mock.Call +} + +// Initialize is a helper method to define mock.On call +// - ctx context.Context +// - in *api.InitializeRequest +// - opts ...grpc.CallOption +func (_e *MockParserServiceClient_Expecter) Initialize(ctx interface{}, in interface{}, opts ...interface{}) *MockParserServiceClient_Initialize_Call { + return &MockParserServiceClient_Initialize_Call{Call: _e.mock.On("Initialize", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockParserServiceClient_Initialize_Call) Run(run func(ctx context.Context, in *api.InitializeRequest, opts ...grpc.CallOption)) *MockParserServiceClient_Initialize_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *api.InitializeRequest + if args[1] != nil { + arg1 = args[1].(*api.InitializeRequest) + } + var arg2 []grpc.CallOption + var variadicArgs []grpc.CallOption + if len(args) > 2 { + variadicArgs = args[2].([]grpc.CallOption) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockParserServiceClient_Initialize_Call) Return(initializeResponse *api.InitializeResponse, err error) *MockParserServiceClient_Initialize_Call { + _c.Call.Return(initializeResponse, err) + return _c +} + +func (_c *MockParserServiceClient_Initialize_Call) RunAndReturn(run func(ctx context.Context, in *api.InitializeRequest, opts ...grpc.CallOption) (*api.InitializeResponse, error)) *MockParserServiceClient_Initialize_Call { + _c.Call.Return(run) + return _c +} + +// Parse provides a mock function for the type MockParserServiceClient +func (_mock *MockParserServiceClient) Parse(ctx context.Context, in *api.ParseRequest, opts ...grpc.CallOption) (*api.ParseResponse, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, in, opts) + } else { + tmpRet = _mock.Called(ctx, in) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Parse") + } + + var r0 *api.ParseResponse + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *api.ParseRequest, ...grpc.CallOption) (*api.ParseResponse, error)); ok { + return returnFunc(ctx, in, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *api.ParseRequest, ...grpc.CallOption) *api.ParseResponse); ok { + r0 = returnFunc(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*api.ParseResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *api.ParseRequest, ...grpc.CallOption) error); ok { + r1 = returnFunc(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockParserServiceClient_Parse_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Parse' +type MockParserServiceClient_Parse_Call struct { + *mock.Call +} + +// Parse is a helper method to define mock.On call +// - ctx context.Context +// - in *api.ParseRequest +// - opts ...grpc.CallOption +func (_e *MockParserServiceClient_Expecter) Parse(ctx interface{}, in interface{}, opts ...interface{}) *MockParserServiceClient_Parse_Call { + return &MockParserServiceClient_Parse_Call{Call: _e.mock.On("Parse", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockParserServiceClient_Parse_Call) Run(run func(ctx context.Context, in *api.ParseRequest, opts ...grpc.CallOption)) *MockParserServiceClient_Parse_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *api.ParseRequest + if args[1] != nil { + arg1 = args[1].(*api.ParseRequest) + } + var arg2 []grpc.CallOption + var variadicArgs []grpc.CallOption + if len(args) > 2 { + variadicArgs = args[2].([]grpc.CallOption) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockParserServiceClient_Parse_Call) Return(parseResponse *api.ParseResponse, err error) *MockParserServiceClient_Parse_Call { + _c.Call.Return(parseResponse, err) + return _c +} + +func (_c *MockParserServiceClient_Parse_Call) RunAndReturn(run func(ctx context.Context, in *api.ParseRequest, opts ...grpc.CallOption) (*api.ParseResponse, error)) *MockParserServiceClient_Parse_Call { + _c.Call.Return(run) + return _c +} diff --git a/pkg/plugins/providers/config.go b/pkg/plugins/providers/config.go index 8b8930a..7919186 100644 --- a/pkg/plugins/providers/config.go +++ b/pkg/plugins/providers/config.go @@ -2,9 +2,14 @@ package providers import ( "github.com/hashicorp/go-hclog" + "github.com/infracost/cli/internal/config/process" proto "github.com/infracost/proto/gen/go/infracost/provider" ) +var ( + _ process.Processor = (*Config)(nil) +) + type Config struct { AWS string `env:"INFRACOST_CLI_PROVIDER_PLUGIN_AWS"` Google string `env:"INFRACOST_CLI_PROVIDER_PLUGIN_GOOGLE"` @@ -13,16 +18,20 @@ type Config struct { AWSVersion string `env:"INFRACOST_CLI_PROVIDER_PLUGIN_AWS_VERSION"` AzureVersion string `env:"INFRACOST_CLI_PROVIDER_PLUGIN_AZURE_VERSION"` GoogleVersion string `env:"INFRACOST_CLI_PROVIDER_PLUGIN_GOOGLE_VERSION"` -} - -func (c *Config) LoadAWS(level hclog.Level) (proto.ProviderServiceClient, func(), error) { - return Connect(c.AWS, level) -} -func (c *Config) LoadGCP(level hclog.Level) (proto.ProviderServiceClient, func(), error) { - return Connect(c.Google, level) + LoadAWS func(level hclog.Level) (proto.ProviderServiceClient, func(), error) + LoadGoogle func(level hclog.Level) (proto.ProviderServiceClient, func(), error) + LoadAzurerm func(level hclog.Level) (proto.ProviderServiceClient, func(), error) } -func (c *Config) LoadAzure(level hclog.Level) (proto.ProviderServiceClient, func(), error) { - return Connect(c.Azure, level) +func (c *Config) Process() { + c.LoadAWS = func(level hclog.Level) (proto.ProviderServiceClient, func(), error) { + return Connect(c.AWS, level) + } + c.LoadGoogle = func(level hclog.Level) (proto.ProviderServiceClient, func(), error) { + return Connect(c.Google, level) + } + c.LoadAzurerm = func(level hclog.Level) (proto.ProviderServiceClient, func(), error) { + return Connect(c.Azure, level) + } } diff --git a/pkg/plugins/providers/mocks/mocks.go b/pkg/plugins/providers/mocks/mocks.go new file mode 100644 index 0000000..4c38e02 --- /dev/null +++ b/pkg/plugins/providers/mocks/mocks.go @@ -0,0 +1,206 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + "github.com/infracost/proto/gen/go/infracost/provider" + mock "github.com/stretchr/testify/mock" + "google.golang.org/grpc" +) + +// NewMockProviderServiceClient creates a new instance of MockProviderServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockProviderServiceClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockProviderServiceClient { + mock := &MockProviderServiceClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockProviderServiceClient is an autogenerated mock type for the ProviderServiceClient type +type MockProviderServiceClient struct { + mock.Mock +} + +type MockProviderServiceClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockProviderServiceClient) EXPECT() *MockProviderServiceClient_Expecter { + return &MockProviderServiceClient_Expecter{mock: &_m.Mock} +} + +// ListFinopsPolicies provides a mock function for the type MockProviderServiceClient +func (_mock *MockProviderServiceClient) ListFinopsPolicies(ctx context.Context, in *provider.ListFinopsPoliciesRequest, opts ...grpc.CallOption) (*provider.ListFinopsPoliciesResponse, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, in, opts) + } else { + tmpRet = _mock.Called(ctx, in) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for ListFinopsPolicies") + } + + var r0 *provider.ListFinopsPoliciesResponse + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *provider.ListFinopsPoliciesRequest, ...grpc.CallOption) (*provider.ListFinopsPoliciesResponse, error)); ok { + return returnFunc(ctx, in, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *provider.ListFinopsPoliciesRequest, ...grpc.CallOption) *provider.ListFinopsPoliciesResponse); ok { + r0 = returnFunc(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*provider.ListFinopsPoliciesResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *provider.ListFinopsPoliciesRequest, ...grpc.CallOption) error); ok { + r1 = returnFunc(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockProviderServiceClient_ListFinopsPolicies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListFinopsPolicies' +type MockProviderServiceClient_ListFinopsPolicies_Call struct { + *mock.Call +} + +// ListFinopsPolicies is a helper method to define mock.On call +// - ctx context.Context +// - in *provider.ListFinopsPoliciesRequest +// - opts ...grpc.CallOption +func (_e *MockProviderServiceClient_Expecter) ListFinopsPolicies(ctx interface{}, in interface{}, opts ...interface{}) *MockProviderServiceClient_ListFinopsPolicies_Call { + return &MockProviderServiceClient_ListFinopsPolicies_Call{Call: _e.mock.On("ListFinopsPolicies", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockProviderServiceClient_ListFinopsPolicies_Call) Run(run func(ctx context.Context, in *provider.ListFinopsPoliciesRequest, opts ...grpc.CallOption)) *MockProviderServiceClient_ListFinopsPolicies_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *provider.ListFinopsPoliciesRequest + if args[1] != nil { + arg1 = args[1].(*provider.ListFinopsPoliciesRequest) + } + var arg2 []grpc.CallOption + var variadicArgs []grpc.CallOption + if len(args) > 2 { + variadicArgs = args[2].([]grpc.CallOption) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockProviderServiceClient_ListFinopsPolicies_Call) Return(listFinopsPoliciesResponse *provider.ListFinopsPoliciesResponse, err error) *MockProviderServiceClient_ListFinopsPolicies_Call { + _c.Call.Return(listFinopsPoliciesResponse, err) + return _c +} + +func (_c *MockProviderServiceClient_ListFinopsPolicies_Call) RunAndReturn(run func(ctx context.Context, in *provider.ListFinopsPoliciesRequest, opts ...grpc.CallOption) (*provider.ListFinopsPoliciesResponse, error)) *MockProviderServiceClient_ListFinopsPolicies_Call { + _c.Call.Return(run) + return _c +} + +// Process provides a mock function for the type MockProviderServiceClient +func (_mock *MockProviderServiceClient) Process(ctx context.Context, in *provider.ProcessRequest, opts ...grpc.CallOption) (*provider.ProcessResponse, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, in, opts) + } else { + tmpRet = _mock.Called(ctx, in) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Process") + } + + var r0 *provider.ProcessResponse + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *provider.ProcessRequest, ...grpc.CallOption) (*provider.ProcessResponse, error)); ok { + return returnFunc(ctx, in, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *provider.ProcessRequest, ...grpc.CallOption) *provider.ProcessResponse); ok { + r0 = returnFunc(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*provider.ProcessResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *provider.ProcessRequest, ...grpc.CallOption) error); ok { + r1 = returnFunc(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockProviderServiceClient_Process_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Process' +type MockProviderServiceClient_Process_Call struct { + *mock.Call +} + +// Process is a helper method to define mock.On call +// - ctx context.Context +// - in *provider.ProcessRequest +// - opts ...grpc.CallOption +func (_e *MockProviderServiceClient_Expecter) Process(ctx interface{}, in interface{}, opts ...interface{}) *MockProviderServiceClient_Process_Call { + return &MockProviderServiceClient_Process_Call{Call: _e.mock.On("Process", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockProviderServiceClient_Process_Call) Run(run func(ctx context.Context, in *provider.ProcessRequest, opts ...grpc.CallOption)) *MockProviderServiceClient_Process_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *provider.ProcessRequest + if args[1] != nil { + arg1 = args[1].(*provider.ProcessRequest) + } + var arg2 []grpc.CallOption + var variadicArgs []grpc.CallOption + if len(args) > 2 { + variadicArgs = args[2].([]grpc.CallOption) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockProviderServiceClient_Process_Call) Return(processResponse *provider.ProcessResponse, err error) *MockProviderServiceClient_Process_Call { + _c.Call.Return(processResponse, err) + return _c +} + +func (_c *MockProviderServiceClient_Process_Call) RunAndReturn(run func(ctx context.Context, in *provider.ProcessRequest, opts ...grpc.CallOption) (*provider.ProcessResponse, error)) *MockProviderServiceClient_Process_Call { + _c.Call.Return(run) + return _c +} diff --git a/pkg/plugins/providers/process.go b/pkg/plugins/providers/process.go index 073d800..f42e3a1 100644 --- a/pkg/plugins/providers/process.go +++ b/pkg/plugins/providers/process.go @@ -10,7 +10,7 @@ import ( proto "github.com/infracost/proto/gen/go/infracost/provider" ) -func (c *Config) Process(ctx context.Context, provider proto.Provider, input *proto.Input, fn func(hclog.Level) (proto.ProviderServiceClient, func(), error), level hclog.Level) ([]*proto.Resource, []*proto.FinopsPolicyResult, error) { +func (c *Config) ProcessInput(ctx context.Context, provider proto.Provider, input *proto.Input, fn func(hclog.Level) (proto.ProviderServiceClient, func(), error), level hclog.Level) ([]*proto.Resource, []*proto.FinopsPolicyResult, error) { var cache protocache.Cache[*proto.Output]