// Package awsinv discovers what an AWS account is carrying and reports it // as an internal/infraview.Inventory. It is the ONLY package in this repo // that imports an AWS SDK for inventory purposes, which is the whole point // of the seam: the console page that renders an estate never sees an AWS // type, so a second provider is a new package rather than a rewrite. // // Every call here is read-only. The IAM policy this package needs // (infra/modules/region-host/main.tf) contains no write action, so that // property is enforced by the credential and not by this comment. package awsinv import ( "context" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/acm" "github.com/aws/aws-sdk-go-v2/service/cloudwatch" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" "github.com/aws/aws-sdk-go-v2/service/route53" "github.com/aws/aws-sdk-go-v2/service/s3" ) // The narrow interfaces exist so the mappings can be tested against fakes // with no network and no credential. Each lists exactly the calls this // package makes and nothing else — an interface that named the whole // client would be an invitation to reach for a write. type EC2API interface { DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) DescribeVolumes(context.Context, *ec2.DescribeVolumesInput, ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error) DescribeAddresses(context.Context, *ec2.DescribeAddressesInput, ...func(*ec2.Options)) (*ec2.DescribeAddressesOutput, error) DescribeSecurityGroups(context.Context, *ec2.DescribeSecurityGroupsInput, ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) DescribeVpcs(context.Context, *ec2.DescribeVpcsInput, ...func(*ec2.Options)) (*ec2.DescribeVpcsOutput, error) DescribeSubnets(context.Context, *ec2.DescribeSubnetsInput, ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) DescribeRegions(context.Context, *ec2.DescribeRegionsInput, ...func(*ec2.Options)) (*ec2.DescribeRegionsOutput, error) } type S3API interface { ListBuckets(context.Context, *s3.ListBucketsInput, ...func(*s3.Options)) (*s3.ListBucketsOutput, error) GetBucketLocation(context.Context, *s3.GetBucketLocationInput, ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) GetBucketVersioning(context.Context, *s3.GetBucketVersioningInput, ...func(*s3.Options)) (*s3.GetBucketVersioningOutput, error) GetBucketEncryption(context.Context, *s3.GetBucketEncryptionInput, ...func(*s3.Options)) (*s3.GetBucketEncryptionOutput, error) GetPublicAccessBlock(context.Context, *s3.GetPublicAccessBlockInput, ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) } type Route53API interface { ListHostedZones(context.Context, *route53.ListHostedZonesInput, ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) ListResourceRecordSets(context.Context, *route53.ListResourceRecordSetsInput, ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) } type IAMAPI interface { ListRoles(context.Context, *iam.ListRolesInput, ...func(*iam.Options)) (*iam.ListRolesOutput, error) ListUsers(context.Context, *iam.ListUsersInput, ...func(*iam.Options)) (*iam.ListUsersOutput, error) ListAttachedRolePolicies(context.Context, *iam.ListAttachedRolePoliciesInput, ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) } type CloudWatchAPI interface { DescribeAlarms(context.Context, *cloudwatch.DescribeAlarmsInput, ...func(*cloudwatch.Options)) (*cloudwatch.DescribeAlarmsOutput, error) } type ACMAPI interface { ListCertificates(context.Context, *acm.ListCertificatesInput, ...func(*acm.Options)) (*acm.ListCertificatesOutput, error) } type TaggingAPI interface { GetResources(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) } // ec2Methods satisfies EC2API from method values of one ec2 client. type ec2Methods struct { describeInstances func(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) describeVolumes func(context.Context, *ec2.DescribeVolumesInput, ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error) describeAddresses func(context.Context, *ec2.DescribeAddressesInput, ...func(*ec2.Options)) (*ec2.DescribeAddressesOutput, error) describeSecurityGroups func(context.Context, *ec2.DescribeSecurityGroupsInput, ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) describeVpcs func(context.Context, *ec2.DescribeVpcsInput, ...func(*ec2.Options)) (*ec2.DescribeVpcsOutput, error) describeSubnets func(context.Context, *ec2.DescribeSubnetsInput, ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) describeRegions func(context.Context, *ec2.DescribeRegionsInput, ...func(*ec2.Options)) (*ec2.DescribeRegionsOutput, error) } func (m ec2Methods) DescribeInstances(ctx context.Context, in *ec2.DescribeInstancesInput, opts ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) { return m.describeInstances(ctx, in, opts...) } func (m ec2Methods) DescribeVolumes(ctx context.Context, in *ec2.DescribeVolumesInput, opts ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error) { return m.describeVolumes(ctx, in, opts...) } func (m ec2Methods) DescribeAddresses(ctx context.Context, in *ec2.DescribeAddressesInput, opts ...func(*ec2.Options)) (*ec2.DescribeAddressesOutput, error) { return m.describeAddresses(ctx, in, opts...) } func (m ec2Methods) DescribeSecurityGroups(ctx context.Context, in *ec2.DescribeSecurityGroupsInput, opts ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) { return m.describeSecurityGroups(ctx, in, opts...) } func (m ec2Methods) DescribeVpcs(ctx context.Context, in *ec2.DescribeVpcsInput, opts ...func(*ec2.Options)) (*ec2.DescribeVpcsOutput, error) { return m.describeVpcs(ctx, in, opts...) } func (m ec2Methods) DescribeSubnets(ctx context.Context, in *ec2.DescribeSubnetsInput, opts ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) { return m.describeSubnets(ctx, in, opts...) } func (m ec2Methods) DescribeRegions(ctx context.Context, in *ec2.DescribeRegionsInput, opts ...func(*ec2.Options)) (*ec2.DescribeRegionsOutput, error) { return m.describeRegions(ctx, in, opts...) } func newEc2Methods(c *ec2.Client) ec2Methods { return ec2Methods{ describeInstances: c.DescribeInstances, describeVolumes: c.DescribeVolumes, describeAddresses: c.DescribeAddresses, describeSecurityGroups: c.DescribeSecurityGroups, describeVpcs: c.DescribeVpcs, describeSubnets: c.DescribeSubnets, describeRegions: c.DescribeRegions, } } // s3Methods satisfies S3API from method values of one s3 client. type s3Methods struct { listBuckets func(context.Context, *s3.ListBucketsInput, ...func(*s3.Options)) (*s3.ListBucketsOutput, error) getBucketLocation func(context.Context, *s3.GetBucketLocationInput, ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) getBucketVersioning func(context.Context, *s3.GetBucketVersioningInput, ...func(*s3.Options)) (*s3.GetBucketVersioningOutput, error) getBucketEncryption func(context.Context, *s3.GetBucketEncryptionInput, ...func(*s3.Options)) (*s3.GetBucketEncryptionOutput, error) getPublicAccessBlock func(context.Context, *s3.GetPublicAccessBlockInput, ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) } func (m s3Methods) ListBuckets(ctx context.Context, in *s3.ListBucketsInput, opts ...func(*s3.Options)) (*s3.ListBucketsOutput, error) { return m.listBuckets(ctx, in, opts...) } func (m s3Methods) GetBucketLocation(ctx context.Context, in *s3.GetBucketLocationInput, opts ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) { return m.getBucketLocation(ctx, in, opts...) } func (m s3Methods) GetBucketVersioning(ctx context.Context, in *s3.GetBucketVersioningInput, opts ...func(*s3.Options)) (*s3.GetBucketVersioningOutput, error) { return m.getBucketVersioning(ctx, in, opts...) } func (m s3Methods) GetBucketEncryption(ctx context.Context, in *s3.GetBucketEncryptionInput, opts ...func(*s3.Options)) (*s3.GetBucketEncryptionOutput, error) { return m.getBucketEncryption(ctx, in, opts...) } func (m s3Methods) GetPublicAccessBlock(ctx context.Context, in *s3.GetPublicAccessBlockInput, opts ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) { return m.getPublicAccessBlock(ctx, in, opts...) } func newS3Methods(c *s3.Client) s3Methods { return s3Methods{ listBuckets: c.ListBuckets, getBucketLocation: c.GetBucketLocation, getBucketVersioning: c.GetBucketVersioning, getBucketEncryption: c.GetBucketEncryption, getPublicAccessBlock: c.GetPublicAccessBlock, } } // route53Methods satisfies Route53API from method values of one route53 client. type route53Methods struct { listHostedZones func(context.Context, *route53.ListHostedZonesInput, ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) listResourceRecordSets func(context.Context, *route53.ListResourceRecordSetsInput, ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) } func (m route53Methods) ListHostedZones(ctx context.Context, in *route53.ListHostedZonesInput, opts ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) { return m.listHostedZones(ctx, in, opts...) } func (m route53Methods) ListResourceRecordSets(ctx context.Context, in *route53.ListResourceRecordSetsInput, opts ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) { return m.listResourceRecordSets(ctx, in, opts...) } func newRoute53Methods(c *route53.Client) route53Methods { return route53Methods{ listHostedZones: c.ListHostedZones, listResourceRecordSets: c.ListResourceRecordSets, } } // iamMethods satisfies IAMAPI from method values of one iam client. type iamMethods struct { listRoles func(context.Context, *iam.ListRolesInput, ...func(*iam.Options)) (*iam.ListRolesOutput, error) listUsers func(context.Context, *iam.ListUsersInput, ...func(*iam.Options)) (*iam.ListUsersOutput, error) listAttachedRolePolicies func(context.Context, *iam.ListAttachedRolePoliciesInput, ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) } func (m iamMethods) ListRoles(ctx context.Context, in *iam.ListRolesInput, opts ...func(*iam.Options)) (*iam.ListRolesOutput, error) { return m.listRoles(ctx, in, opts...) } func (m iamMethods) ListUsers(ctx context.Context, in *iam.ListUsersInput, opts ...func(*iam.Options)) (*iam.ListUsersOutput, error) { return m.listUsers(ctx, in, opts...) } func (m iamMethods) ListAttachedRolePolicies(ctx context.Context, in *iam.ListAttachedRolePoliciesInput, opts ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) { return m.listAttachedRolePolicies(ctx, in, opts...) } func newIamMethods(c *iam.Client) iamMethods { return iamMethods{ listRoles: c.ListRoles, listUsers: c.ListUsers, listAttachedRolePolicies: c.ListAttachedRolePolicies, } } // cloudwatchMethods satisfies CloudWatchAPI from method values of one cloudwatch client. type cloudwatchMethods struct { describeAlarms func(context.Context, *cloudwatch.DescribeAlarmsInput, ...func(*cloudwatch.Options)) (*cloudwatch.DescribeAlarmsOutput, error) } func (m cloudwatchMethods) DescribeAlarms(ctx context.Context, in *cloudwatch.DescribeAlarmsInput, opts ...func(*cloudwatch.Options)) (*cloudwatch.DescribeAlarmsOutput, error) { return m.describeAlarms(ctx, in, opts...) } func newCloudwatchMethods(c *cloudwatch.Client) cloudwatchMethods { return cloudwatchMethods{ describeAlarms: c.DescribeAlarms, } } // acmMethods satisfies ACMAPI from method values of one acm client. type acmMethods struct { listCertificates func(context.Context, *acm.ListCertificatesInput, ...func(*acm.Options)) (*acm.ListCertificatesOutput, error) } func (m acmMethods) ListCertificates(ctx context.Context, in *acm.ListCertificatesInput, opts ...func(*acm.Options)) (*acm.ListCertificatesOutput, error) { return m.listCertificates(ctx, in, opts...) } func newAcmMethods(c *acm.Client) acmMethods { return acmMethods{ listCertificates: c.ListCertificates, } } // resourcegroupstaggingapiMethods satisfies TaggingAPI from method values of one resourcegroupstaggingapi client. type resourcegroupstaggingapiMethods struct { getResources func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) } func (m resourcegroupstaggingapiMethods) GetResources(ctx context.Context, in *resourcegroupstaggingapi.GetResourcesInput, opts ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) { return m.getResources(ctx, in, opts...) } func newResourcegroupstaggingapiMethods(c *resourcegroupstaggingapi.Client) resourcegroupstaggingapiMethods { return resourcegroupstaggingapiMethods{ getResources: c.GetResources, } } // Clients is one region's worth of read-only clients. S3/Route53/IAM are // global services; the assembly calls them once, from the home region's // Clients, rather than once per region. type Clients struct { EC2 EC2API S3 S3API Route53 Route53API IAM IAMAPI CloudWatch CloudWatchAPI ACM ACMAPI Tagging TaggingAPI } // Factory builds the clients for one region. Tests inject a fake; the real // one below rebases a single resolved aws.Config onto each region so the // credential chain resolves exactly once. type Factory func(region string) Clients // factoryFor stores METHOD VALUES, not the SDK clients, in the interfaces. // html/template (the edge and console pages) switches the linker into a // mode that keeps every exported method of any concrete type stored in an // interface value. A *ec2.Client in an interface kept all ~2,400 EC2 // methods (14 MB), and the other clients kept theirs too: the linux/arm64 // binary doubled to 77.7 MB (discussion #275). A func value bound to one // method keeps that method and nothing else. Do not "simplify" these back // to ec2.NewFromConfig(c) in the struct; the CI size ceiling will say so. func factoryFor(cfg aws.Config) Factory { return func(region string) Clients { c := cfg.Copy() if region != "" { c.Region = region } return Clients{ EC2: newEc2Methods(ec2.NewFromConfig(c)), S3: newS3Methods(s3.NewFromConfig(c)), Route53: newRoute53Methods(route53.NewFromConfig(c)), IAM: newIamMethods(iam.NewFromConfig(c)), CloudWatch: newCloudwatchMethods(cloudwatch.NewFromConfig(c)), ACM: newAcmMethods(acm.NewFromConfig(c)), Tagging: newResourcegroupstaggingapiMethods(resourcegroupstaggingapi.NewFromConfig(c)), } } }