Repository: cycloidio/terracost (go)

Issue:
aws: expose a list of supported services (regions?)
## Abstract

In order to be able to use the `aws.Ingester` type correctly, the user needs to know the name of the service they want to ingest. This is not always obvious - for example, `AmazonEC2` is used and not just the expected `EC2`. The users of the library should be able to retrieve a list of all supported services so that it can be displayed or manipulated in any other way.

## Implementation details

A function in the `aws` package that returns a `[]string` containing a list of all supported AWS services that can be ingested.

## Suggestions/ideas

This list can also be used internally to validate the requested service, before issuing a request to AWS.

Pull request:
aws: expose a list of supported services
This PR adds a list of supported AWS services as well as validates it when creating a new Ingester. This causes a breaking change.

Closes #14 

Patch:
diff --git a/aws/ingester.go b/aws/ingester.go
index 482d347..c1d74b9 100644
--- a/aws/ingester.go
+++ b/aws/ingester.go
@@ -46,7 +46,13 @@ type Ingester struct {
 
 // NewIngester returns a new Ingester using the given options. Only the repositories must be provided using the
 // WithRepositories function, other configuration options will use their default values.
-func NewIngester(service, region string, options ...Option) *Ingester {
+// The service must be a valid AWS service name that is supported by Terracost, otherwise this function will
+// return an error.
+func NewIngester(service, region string, options ...Option) (*Ingester, error) {
+	if !IsServiceSupported(service) {
+		return nil, fmt.Errorf("service not supported: %s", service)
+	}
+
 	ing := &Ingester{
 		httpClient:      &http.Client{},
 		pricingURL:      defaultPricingURL,
@@ -60,7 +66,7 @@ func NewIngester(service, region string, options ...Option) *Ingester {
 		opt(ing)
 	}
 
-	return ing
+	return ing, nil
 }
 
 // Ingest starts a goroutine that reads pricing data from AWS and, for the duration of the context, sends
diff --git a/aws/services.go b/aws/services.go
new file mode 100644
index 0000000..1341c39
--- /dev/null
+++ b/aws/services.go
@@ -0,0 +1,23 @@
+package aws
+
+// SupportedServices is a list of all AWS services that are supported by Terracost.
+var supportedServices = map[string]struct{}{
+	"AmazonEC2": {},
+	"AmazonRDS": {},
+	"AWSELB":    {},
+}
+
+// IsServiceSupported returns true if the AWS service is valid and supported by Terracost (e.g. for ingestion.)
+func IsServiceSupported(service string) bool {
+	_, ok := supportedServices[service]
+	return ok
+}
+
+// GetSupportedServices returns all the AWS service names that Terracost supports.
+func GetSupportedServices() []string {
+	svcs := make([]string, 0, len(supportedServices))
+	for k := range supportedServices {
+		svcs = append(svcs, k)
+	}
+	return svcs
+}


Test patch:
diff --git a/aws/ingester_test.go b/aws/ingester_test.go
index f9d1407..4dceddf 100644
--- a/aws/ingester_test.go
+++ b/aws/ingester_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/golang/mock/gomock"
 	"github.com/shopspring/decimal"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/cycloidio/terracost/mock"
 	"github.com/cycloidio/terracost/price"
@@ -17,12 +18,23 @@ import (
 )
 
 func TestIngester_Ingest(t *testing.T) {
+	t.Run("InvalidService", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+
+		client := mock.NewHTTPClient(ctrl)
+		ing, err := NewIngester("InvalidService", "eu-west-3", WithHTTPClient(client))
+		assert.Error(t, err)
+		assert.Nil(t, ing)
+	})
+
 	t.Run("EC2", func(t *testing.T) {
 		ctrl := gomock.NewController(t)
 		defer ctrl.Finish()
 
 		client := mock.NewHTTPClient(ctrl)
-		ing := NewIngester("AmazonEC2", "eu-west-3", WithHTTPClient(client))
+		ing, err := NewIngester("AmazonEC2", "eu-west-3", WithHTTPClient(client))
+		require.NoError(t, err)
 
 		content := makeCSV([][]string{
 			{"SKU", "Product Family", "serviceCode", "TermType", "Location", "Unit", "Currency", "PricePerUnit", "Tenancy", "Instance Type", "Operating System", "Volume API Name"},
diff --git a/e2e/aws_ingestion_test.go b/e2e/aws_ingestion_test.go
index cb56cff..068b0b1 100644
--- a/e2e/aws_ingestion_test.go
+++ b/e2e/aws_ingestion_test.go
@@ -41,7 +41,8 @@ func TestAWSIngestion(t *testing.T) {
 	httpClient.EXPECT().Do(gomock.Any()).Return(&http.Response{Body: f}, nil)
 
 	backend := mysql.NewBackend(db)
-	ingester := aws.NewIngester("AmazonEC2-test", "eu-west-3", aws.WithHTTPClient(httpClient))
+	ingester, err := aws.NewIngester("AmazonEC2", "eu-west-3", aws.WithHTTPClient(httpClient))
+	require.NoError(t, err)
 
 	err = costestimation.IngestPricing(ctx, backend, ingester)
 	require.NoError(t, err)