-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat daily cost notification template
- Loading branch information
Showing
25 changed files
with
812 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package service | ||
|
||
import ( | ||
"context" | ||
"time" | ||
) | ||
|
||
// CostGetterInput is the interface that wraps the basic GetCost method. | ||
type CostGetterInput struct { | ||
// Start is the start date of the period. | ||
Start time.Time | ||
// End is the end date of the period. | ||
End time.Time | ||
} | ||
|
||
// CostGetterOutput is the output of the GetCost method. | ||
type CostGetterOutput struct { | ||
// Cost is the cost of the period. The unit is USD. | ||
Cost string | ||
} | ||
|
||
// CostGetter is the interface that wraps the basic GetCost method. | ||
type CostGetter interface { | ||
GetCost(ctx context.Context, input *CostGetterInput) (*CostGetterOutput, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package service | ||
|
||
import "context" | ||
|
||
// SNSPublisherInput is the input of the Publish method. | ||
type SNSPublisherInput struct { | ||
// TopicArn is the ARN of the topic. | ||
TopicArn string | ||
// Message is the message that you want to publish. | ||
Message string | ||
} | ||
|
||
// SNSPublisherOutput is the output of the Publish method. | ||
type SNSPublisherOutput struct{} | ||
|
||
// SNSPublisher is the interface that wraps the basic Publish method. | ||
type SNSPublisher interface { | ||
PublishSNS(ctx context.Context, input *SNSPublisherInput) (*SNSPublisherOutput, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package external | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/service/costexplorer" | ||
"github.com/aws/aws-sdk-go-v2/service/costexplorer/types" | ||
"github.com/google/wire" | ||
"github.com/nao1215/rainbow/app/domain/model" | ||
"github.com/nao1215/rainbow/app/domain/service" | ||
) | ||
|
||
// NewCostExplorerClient returns a new CostExplorer client. | ||
func NewCostExplorerClient(cfg *model.AWSConfig) *costexplorer.Client { | ||
return costexplorer.NewFromConfig(*cfg.Config) | ||
} | ||
|
||
// CostGetter is an interface for getting cost. | ||
type CostGetter struct { | ||
*costexplorer.Client | ||
} | ||
|
||
// CostGetterSet is a provider set for CostGetter. | ||
// | ||
//nolint:gochecknoglobals | ||
var CostGetterSet = wire.NewSet( | ||
NewCostGetter, | ||
wire.Bind(new(service.CostGetter), new(*CostGetter)), | ||
) | ||
|
||
var _ service.CostGetter = (*CostGetter)(nil) | ||
|
||
// NewCostGetter creates a new CostGetter. | ||
func NewCostGetter(c *costexplorer.Client) *CostGetter { | ||
return &CostGetter{Client: c} | ||
} | ||
|
||
// GetCost gets the cost. | ||
func (c *CostGetter) GetCost(ctx context.Context, input *service.CostGetterInput) (*service.CostGetterOutput, error) { | ||
params := &costexplorer.GetCostAndUsageInput{ | ||
TimePeriod: &types.DateInterval{ | ||
Start: aws.String(input.Start.Format("2006-01-02")), | ||
End: aws.String(input.End.Format("2006-01-02")), | ||
}, | ||
Granularity: types.GranularityDaily, | ||
Metrics: []string{"UnblendedCost"}, | ||
} | ||
|
||
resp, err := c.GetCostAndUsage(ctx, params) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(resp.ResultsByTime) == 0 || len(resp.ResultsByTime[0].Total) == 0 { | ||
return nil, fmt.Errorf("no cost data available for the specified time period") | ||
} | ||
|
||
return &service.CostGetterOutput{Cost: *resp.ResultsByTime[0].Total["UnblendedCost"].Amount}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package mock | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/nao1215/rainbow/app/domain/service" | ||
) | ||
|
||
// CostGetter is a mock of the CostGetter interface. | ||
type CostGetter func(ctx context.Context, input *service.CostGetterInput) (*service.CostGetterOutput, error) | ||
|
||
// GetCost calls the GetCostFunc. | ||
func (m CostGetter) GetCost(ctx context.Context, input *service.CostGetterInput) (*service.CostGetterOutput, error) { | ||
return m(ctx, input) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package mock | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/nao1215/rainbow/app/domain/service" | ||
) | ||
|
||
// SNSPublisher is a mock of the SNSPublisher interface. | ||
type SNSPublisher func(ctx context.Context, input *service.SNSPublisherInput) (*service.SNSPublisherOutput, error) | ||
|
||
// PublishSNS is a mock of the PublishSNS method. | ||
func (m SNSPublisher) PublishSNS(ctx context.Context, input *service.SNSPublisherInput) (*service.SNSPublisherOutput, error) { | ||
return m(ctx, input) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package external | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/service/sns" | ||
"github.com/google/wire" | ||
"github.com/nao1215/rainbow/app/domain/model" | ||
"github.com/nao1215/rainbow/app/domain/service" | ||
) | ||
|
||
// NewSNSClient returns a new SNSClient. | ||
func NewSNSClient(cfg *model.AWSConfig) *sns.Client { | ||
return sns.NewFromConfig(*cfg.Config) | ||
} | ||
|
||
// SNSPublisher is an implementation for SNSPublisher. | ||
type SNSPublisher struct { | ||
*sns.Client | ||
} | ||
|
||
// SNSPublisherSet is a provider set for SNSPublisher. | ||
// | ||
//nolint:gochecknoglobals | ||
var SNSPublisherSet = wire.NewSet( | ||
NewSNSPublisher, | ||
wire.Bind(new(service.SNSPublisher), new(*SNSPublisher)), | ||
) | ||
|
||
// NewSNSPublisher creates a new SNSPublisher. | ||
func NewSNSPublisher(c *sns.Client) *SNSPublisher { | ||
return &SNSPublisher{ | ||
Client: c, | ||
} | ||
} | ||
|
||
var _ service.SNSPublisher = (*SNSPublisher)(nil) | ||
|
||
// PublishSNS publishes a message to SNS. | ||
func (p *SNSPublisher) PublishSNS(ctx context.Context, input *service.SNSPublisherInput) (*service.SNSPublisherOutput, error) { | ||
if _, err := p.Publish(ctx, &sns.PublishInput{ | ||
Message: aws.String(input.Message), | ||
TopicArn: aws.String(input.TopicArn), | ||
}); err != nil { | ||
return nil, err | ||
} | ||
|
||
return &service.SNSPublisherOutput{}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package interactor | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
"github.com/google/wire" | ||
"github.com/nao1215/rainbow/app/domain/service" | ||
"github.com/nao1215/rainbow/app/usecase" | ||
) | ||
|
||
// CostGetterSet is a set of CostGetter. | ||
// | ||
//nolint:gochecknoglobals | ||
var CostGetterSet = wire.NewSet( | ||
NewCostGetter, | ||
wire.Bind(new(usecase.CostGetter), new(*CostGetter)), | ||
) | ||
|
||
var _ usecase.CostGetter = (*CostGetter)(nil) | ||
|
||
// CostGetter is an implementation for CostGetter. | ||
type CostGetter struct { | ||
service.CostGetter | ||
} | ||
|
||
// NewCostGetter returns a new CostGetter struct. | ||
func NewCostGetter(c service.CostGetter) *CostGetter { | ||
return &CostGetter{CostGetter: c} | ||
} | ||
|
||
// GetCost gets the cost. | ||
func (c *CostGetter) GetCost(ctx context.Context, input *usecase.CostGetterInput) (*usecase.CostGetterOutput, error) { | ||
if input.End.Before(input.Start) { | ||
return nil, errors.New("End date is before the start date") | ||
} | ||
output, err := c.CostGetter.GetCost(ctx, &service.CostGetterInput{ | ||
Start: input.Start, | ||
End: input.End, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &usecase.CostGetterOutput{Cost: output.Cost}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package interactor | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"testing" | ||
"time" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/nao1215/rainbow/app/domain/service" | ||
"github.com/nao1215/rainbow/app/external/mock" | ||
"github.com/nao1215/rainbow/app/usecase" | ||
) | ||
|
||
func TestCostGetter_GetCost(t *testing.T) { | ||
t.Run("Success to get cost", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
costGetter := mock.CostGetter(func(_ context.Context, input *service.CostGetterInput) (*service.CostGetterOutput, error) { | ||
want := &service.CostGetterInput{ | ||
Start: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), | ||
End: time.Date(2021, 1, 31, 23, 59, 59, 0, time.UTC), | ||
} | ||
|
||
if diff := cmp.Diff(want, input); diff != "" { | ||
t.Errorf("differs: (-want +got)\n%s", diff) | ||
} | ||
return &service.CostGetterOutput{Cost: "1000"}, nil | ||
}) | ||
|
||
getter := NewCostGetter(costGetter) | ||
got, err := getter.GetCost(context.Background(), &usecase.CostGetterInput{ | ||
Start: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), | ||
End: time.Date(2021, 1, 31, 23, 59, 59, 0, time.UTC), | ||
}) | ||
if err != nil { | ||
t.Errorf("unexpected error: %v", err) | ||
} | ||
|
||
want := &usecase.CostGetterOutput{Cost: "1000"} | ||
if diff := cmp.Diff(want, got); diff != "" { | ||
t.Errorf("differs: (-want +got)\n%s", diff) | ||
} | ||
}) | ||
|
||
t.Run("Fail to get cost", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
costGetter := mock.CostGetter(func(_ context.Context, _ *service.CostGetterInput) (*service.CostGetterOutput, error) { | ||
want := &service.CostGetterInput{ | ||
Start: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), | ||
End: time.Date(2021, 1, 31, 23, 59, 59, 0, time.UTC), | ||
} | ||
if diff := cmp.Diff(want, want); diff != "" { | ||
t.Errorf("differs: (-want +got)\n%s", diff) | ||
} | ||
return nil, errors.New("failed to get cost") | ||
}) | ||
|
||
getter := NewCostGetter(costGetter) | ||
_, err := getter.GetCost(context.Background(), &usecase.CostGetterInput{ | ||
Start: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), | ||
End: time.Date(2021, 1, 31, 23, 59, 59, 0, time.UTC), | ||
}) | ||
if err == nil { | ||
t.Error("expected error, but not occurred") | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package interactor | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/google/wire" | ||
"github.com/nao1215/rainbow/app/domain/service" | ||
"github.com/nao1215/rainbow/app/usecase" | ||
) | ||
|
||
// SNSPublisherSet is a set of SNSPublisher. | ||
// | ||
//nolint:gochecknoglobals | ||
var SNSPublisherSet = wire.NewSet( | ||
NewSNSPublisher, | ||
wire.Bind(new(usecase.SNSPublisher), new(*SNSPublisher)), | ||
) | ||
|
||
var _ usecase.SNSPublisher = (*SNSPublisher)(nil) | ||
|
||
// SNSPublisher is an implementation for SNSPublisher. | ||
type SNSPublisher struct { | ||
service.SNSPublisher | ||
} | ||
|
||
// NewSNSPublisher returns a new SNSPublisher struct. | ||
func NewSNSPublisher(s service.SNSPublisher) *SNSPublisher { | ||
return &SNSPublisher{SNSPublisher: s} | ||
} | ||
|
||
// PublishSNS publishes a message to SNS. | ||
func (s *SNSPublisher) PublishSNS(ctx context.Context, input *usecase.SNSPublisherInput) (*usecase.SNSPublisherOutput, error) { | ||
if _, err := s.SNSPublisher.PublishSNS(ctx, &service.SNSPublisherInput{ | ||
Message: input.Message, | ||
TopicArn: input.TopicArn, | ||
}); err != nil { | ||
return nil, err | ||
} | ||
return &usecase.SNSPublisherOutput{}, nil | ||
} |
Oops, something went wrong.