Skip to content

Commit

Permalink
Merge pull request #244 from tosuke/framework-aws-integration-model
Browse files Browse the repository at this point in the history
Add model for AWS Integration
  • Loading branch information
tosuke authored Oct 29, 2024
2 parents 5955898 + 7d2f59b commit 08eb0ef
Show file tree
Hide file tree
Showing 3 changed files with 732 additions and 2 deletions.
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,6 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW
github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
github.com/hashicorp/terraform-plugin-framework v1.8.0 h1:P07qy8RKLcoBkCrY2RHJer5AEvJnDuXomBgou6fD8kI=
github.com/hashicorp/terraform-plugin-framework v1.8.0/go.mod h1:/CpTukO88PcL/62noU7cuyaSJ4Rsim+A/pa+3rUVufY=
github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE=
github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0 h1:b8vZYB/SkXJT4YPbT3trzE6oJ7dPyMy68+9dEDKsJjE=
Expand Down
321 changes: 321 additions & 0 deletions internal/mackerel/aws_integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
package mackerel

import (
"context"
"fmt"
"slices"
"strings"

"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mackerelio/mackerel-client-go"
)

type AWSIntegrationModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Memo types.String `tfsdk:"memo"`
Key types.String `tfsdk:"key"`
SecretKey types.String `tfsdk:"secret_key"`
RoleARN types.String `tfsdk:"role_arn"`
ExternalID types.String `tfsdk:"external_id"`
Region types.String `tfsdk:"region"`
IncludedTags types.String `tfsdk:"included_tags"`
ExcludedTags types.String `tfsdk:"excluded_tags"`

EC2 AWSIntegrationServiceWithRetireAutomaticallyOpt `tfsdk:"ec2"`
ELB AWSIntegrationServiceOpt `tfsdk:"elb"`
ALB AWSIntegrationServiceOpt `tfsdk:"alb"`
NLB AWSIntegrationServiceOpt `tfsdk:"nlb"`
RDS AWSIntegrationServiceWithRetireAutomaticallyOpt `tfsdk:"rds"`
Redshift AWSIntegrationServiceOpt `tfsdk:"redshift"`
ElastiCache AWSIntegrationServiceWithRetireAutomaticallyOpt `tfsdk:"elasticache"`
SQS AWSIntegrationServiceOpt `tfsdk:"sqs"`
Lambda AWSIntegrationServiceOpt `tfsdk:"lambda"`
DynamoDB AWSIntegrationServiceOpt `tfsdk:"dynamodb"`
CloudFront AWSIntegrationServiceOpt `tfsdk:"cloudfront"`
APIGateway AWSIntegrationServiceOpt `tfsdk:"api_gateway"`
Kinesis AWSIntegrationServiceOpt `tfsdk:"kinesis"`
S3 AWSIntegrationServiceOpt `tfsdk:"s3"`
ES AWSIntegrationServiceOpt `tfsdk:"es"`
ECSCluster AWSIntegrationServiceOpt `tfsdk:"ecs_cluster"`
SES AWSIntegrationServiceOpt `tfsdk:"ses"`
States AWSIntegrationServiceOpt `tfsdk:"states"`
EFS AWSIntegrationServiceOpt `tfsdk:"efs"`
Firehose AWSIntegrationServiceOpt `tfsdk:"firehose"`
Batch AWSIntegrationServiceOpt `tfsdk:"batch"`
WAF AWSIntegrationServiceOpt `tfsdk:"waf"`
Billing AWSIntegrationServiceOpt `tfsdk:"billing"`
Route53 AWSIntegrationServiceOpt `tfsdk:"route53"`
Connect AWSIntegrationServiceOpt `tfsdk:"connect"`
DocDB AWSIntegrationServiceOpt `tfsdk:"docdb"`
CodeBuild AWSIntegrationServiceOpt `tfsdk:"codebuild"`
}

type AWSIntegrationService struct {
Enable types.Bool `tfsdk:"enable"`
Role types.String `tfsdk:"role"`
ExcludedMetrics []string `tfsdk:"excluded_metrics"`
RetireAutomatically types.Bool `tfsdk:"-"`
}

type AWSIntegrationServiceOpt []AWSIntegrationService // length <= 1

type AWSIntegrationServiceWithRetireAutomatically struct {
Enable types.Bool `tfsdk:"enable"`
Role types.String `tfsdk:"role"`
ExcludedMetrics []string `tfsdk:"excluded_metrics"`
RetireAutomatically types.Bool `tfsdk:"retire_automatically"`
}

type AWSIntegrationServiceWithRetireAutomaticallyOpt []AWSIntegrationServiceWithRetireAutomatically // length <= 1

func readAWSIntegration(client *Client, id string) (*AWSIntegrationModel, error) {
mackerelAWSIntegration, err := client.FindAWSIntegration(id)
if err != nil {
return nil, err
}
return newAWSIntegrationModel(*mackerelAWSIntegration)
}

func (m *AWSIntegrationModel) Create(_ context.Context, client *Client) error {
newIntegration, err := client.CreateAWSIntegration(m.createParam())
if err != nil {
return err
}

m.ID = types.StringValue(newIntegration.ID)
return nil
}

func (m *AWSIntegrationModel) Read(_ context.Context, client *Client) error {
integration, err := readAWSIntegration(client, m.ID.ValueString())
if err != nil {
return err
}

m.merge(*integration)
return nil
}

func (m *AWSIntegrationModel) Update(_ context.Context, client *Client) error {
if _, err := client.UpdateAWSIntegration(m.ID.ValueString(), m.updateParam()); err != nil {
return err
}
return nil
}

func (m *AWSIntegrationModel) Delete(_ context.Context, client *Client) error {
if _, err := client.DeleteAWSIntegration(m.ID.ValueString()); err != nil {
return err
}
return nil
}

func newAWSIntegrationModel(aws mackerel.AWSIntegration) (*AWSIntegrationModel, error) {
model := &AWSIntegrationModel{
ID: types.StringValue(aws.ID),
Name: types.StringValue(aws.Name),
Memo: types.StringValue(aws.Memo),
Key: types.StringValue(aws.Key),
RoleARN: types.StringValue(aws.RoleArn),
ExternalID: types.StringValue(aws.ExternalID),
Region: types.StringValue(aws.Region),
IncludedTags: types.StringValue(aws.IncludedTags),
ExcludedTags: types.StringValue(aws.ExcludedTags),
}

svcs := make(map[string]AWSIntegrationService, len(aws.Services))
for name, awsService := range aws.Services {
if /* nil */ !awsService.Enable &&
awsService.Role == nil &&
len(awsService.ExcludedMetrics) == 0 &&
len(awsService.IncludedMetrics) == 0 &&
!awsService.RetireAutomatically {
continue
}
if len(awsService.IncludedMetrics) != 0 {
return nil, fmt.Errorf("%s: IncludedMetrics is not supported.", name)
}

svcs[name] = AWSIntegrationService{
Enable: types.BoolValue(awsService.Enable),
Role: types.StringPointerValue(awsService.Role),
ExcludedMetrics: awsService.ExcludedMetrics,
RetireAutomatically: types.BoolValue(awsService.RetireAutomatically),
}
}
model.each(func(name string, _ *AWSIntegrationService) *AWSIntegrationService {
svc, ok := svcs[name]
if ok {
delete(svcs, name)
return &svc
} else {
return nil
}
})
if len(svcs) != 0 {
unsupportedServiceNames := make([]string, 0, len(svcs))
for name := range svcs {
unsupportedServiceNames = append(unsupportedServiceNames, name)
}
slices.SortStableFunc(unsupportedServiceNames, strings.Compare)
return nil, fmt.Errorf("unsupported AWS integration service(s): %s",
strings.Join(unsupportedServiceNames, ","))
}

return model, nil
}

func (m *AWSIntegrationModel) createParam() *mackerel.CreateAWSIntegrationParam {
mackerelServices := make(map[string]*mackerel.AWSIntegrationService)
m.each(func(name string, service *AWSIntegrationService) *AWSIntegrationService {
var mackerelService mackerel.AWSIntegrationService
if service != nil {
mackerelService = mackerel.AWSIntegrationService{
Enable: service.Enable.ValueBool(),
Role: nil,
ExcludedMetrics: nil,
IncludedMetrics: nil,
RetireAutomatically: service.RetireAutomatically.ValueBool(),
}
if role := service.Role.ValueString(); role != "" {
mackerelService.Role = &role
}
if service.ExcludedMetrics != nil {
mackerelService.ExcludedMetrics = service.ExcludedMetrics
} else {
mackerelService.ExcludedMetrics = []string{}
}
} else {
mackerelService = mackerel.AWSIntegrationService{
Enable: false,
Role: nil,
ExcludedMetrics: []string{},
IncludedMetrics: nil,
RetireAutomatically: false,
}
}
mackerelServices[name] = &mackerelService
return service
})

return &mackerel.CreateAWSIntegrationParam{
Name: m.Name.ValueString(),
Memo: m.Memo.ValueString(),
Key: m.Key.ValueString(),
SecretKey: m.SecretKey.ValueString(),
RoleArn: m.RoleARN.ValueString(),
ExternalID: m.ExternalID.ValueString(),
Region: m.Region.ValueString(),
IncludedTags: m.IncludedTags.ValueString(),
ExcludedTags: m.ExcludedTags.ValueString(),
Services: mackerelServices,
}
}

func (m *AWSIntegrationModel) updateParam() *mackerel.UpdateAWSIntegrationParam {
return (*mackerel.UpdateAWSIntegrationParam)(m.createParam())
}

func (m *AWSIntegrationModel) merge(newModel AWSIntegrationModel) {
oldServices := make(map[string]AWSIntegrationService)
m.each(func(name string, service *AWSIntegrationService) *AWSIntegrationService {
if service != nil {
oldServices[name] = *service
}
return service
})

newModel.SecretKey = m.SecretKey
newModel.each(func(name string, service *AWSIntegrationService) *AWSIntegrationService {
oldService, ok := oldServices[name]
if !ok {
return service
}

// If new == nil && old == zero, use old one.
if service == nil {
if !oldService.Enable.ValueBool() &&
len(oldService.ExcludedMetrics) == 0 &&
oldService.Role.IsNull() &&
!oldService.RetireAutomatically.ValueBool() {
return &oldService
} else {
return nil
}
}

if service.Role.ValueString() == "" && oldService.Role.ValueString() == "" {
service.Role = oldService.Role
}
if len(service.ExcludedMetrics) == 0 && len(oldService.ExcludedMetrics) == 0 {
service.ExcludedMetrics = oldService.ExcludedMetrics
}
return service
})
*m = newModel
}

type awsServiceEachFunc func(name string, service *AWSIntegrationService) *AWSIntegrationService

// Iterates and updates over services by name
func (m *AWSIntegrationModel) each(fn awsServiceEachFunc) {
m.EC2.each("EC2", fn)
m.ELB.each("ELB", fn)
m.ALB.each("ALB", fn)
m.NLB.each("NLB", fn)
m.RDS.each("RDS", fn)
m.Redshift.each("Redshift", fn)
m.ElastiCache.each("ElastiCache", fn)
m.SQS.each("SQS", fn)
m.Lambda.each("Lambda", fn)
m.DynamoDB.each("DynamoDB", fn)
m.CloudFront.each("CloudFront", fn)
m.APIGateway.each("APIGateway", fn)
m.Kinesis.each("Kinesis", fn)
m.S3.each("S3", fn)
m.ES.each("ES", fn)
m.ECSCluster.each("ECSCluster", fn)
m.SES.each("SES", fn)
m.States.each("States", fn)
m.EFS.each("EFS", fn)
m.Firehose.each("Firehose", fn)
m.Batch.each("Batch", fn)
m.WAF.each("WAF", fn)
m.Billing.each("Billing", fn)
m.Route53.each("Route53", fn)
m.Connect.each("Connect", fn)
m.DocDB.each("DocDB", fn)
m.CodeBuild.each("CodeBuild", fn)
}

func (s *AWSIntegrationServiceOpt) each(name string, fn awsServiceEachFunc) {
var svc *AWSIntegrationService
if len(*s) != 0 {
svc = &(*s)[0]
}

newSvc := fn(name, svc)
if newSvc != nil {
*s = []AWSIntegrationService{*newSvc}
} else {
*s = AWSIntegrationServiceOpt{}
}
}

func (s *AWSIntegrationServiceWithRetireAutomaticallyOpt) each(name string, fn awsServiceEachFunc) {
var svc *AWSIntegrationService
if len(*s) != 0 {
baseSvc := AWSIntegrationService((*s)[0])
svc = &baseSvc
}

newSvc := fn(name, svc)
if newSvc != nil {
*s = []AWSIntegrationServiceWithRetireAutomatically{
AWSIntegrationServiceWithRetireAutomatically(*newSvc),
}
} else {
*s = AWSIntegrationServiceWithRetireAutomaticallyOpt{}
}
}
Loading

0 comments on commit 08eb0ef

Please sign in to comment.