TEST: Init Test Harness Using Localstack
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# The LocalStack target for the same program that deploys production. Nothing
|
||||
# here changes what the code does except thamanyah:localstack — every AWS
|
||||
# endpoint override lives in the aws:* provider config, which the AWS provider
|
||||
# reads on its own.
|
||||
#
|
||||
# localhost.localstack.cloud resolves to 127.0.0.1 in public DNS, and
|
||||
# docker-compose.yml also publishes it as a network alias for the localstack
|
||||
# container — so this one endpoint works whether pulumi runs on the host or as
|
||||
# the compose "infra" service.
|
||||
config:
|
||||
aws:region: us-east-1
|
||||
aws:accessKey: test
|
||||
aws:secretKey: test
|
||||
aws:skipCredentialsValidation: "true"
|
||||
aws:skipRequestingAccountId: "true"
|
||||
aws:skipMetadataApiCheck: "true"
|
||||
# Virtual-host addressing would resolve <bucket>.localhost.localstack.cloud,
|
||||
# which points at 127.0.0.1 — the wrong container from inside compose.
|
||||
aws:s3UsePathStyle: "true"
|
||||
aws:endpoints:
|
||||
- iam: http://localhost.localstack.cloud:4566
|
||||
rds: http://localhost.localstack.cloud:4566
|
||||
s3: http://localhost.localstack.cloud:4566
|
||||
sts: http://localhost.localstack.cloud:4566
|
||||
thamanyah:localstack: "true"
|
||||
@@ -77,6 +77,7 @@ require (
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cobra v1.10.2 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/texttheater/golang-levenshtein v1.0.1 // indirect
|
||||
|
||||
@@ -187,6 +187,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9Am
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -195,6 +197,7 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
|
||||
+418
-309
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudfront"
|
||||
@@ -20,11 +21,18 @@ import (
|
||||
"github.com/pulumi/pulumi-postgresql/sdk/v3/go/postgresql"
|
||||
"github.com/pulumi/pulumi-random/sdk/v4/go/random"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
|
||||
)
|
||||
|
||||
// Matches the region configured in Pulumi.main.yaml (aws:region).
|
||||
const awsRegion = "us-east-1"
|
||||
|
||||
// Port the Postgres instance listens on under LocalStack. LocalStack backs an
|
||||
// RDS instance with a real Postgres process inside its own container, and must
|
||||
// stay inside the 4510-4559 range docker-compose.yml publishes so the instance
|
||||
// is reachable both from the cms container and from the host.
|
||||
const localstackDBPort = 4510
|
||||
|
||||
// currentPublicIP returns the caller's public IP, used to scope the RDS
|
||||
// security group to the machine that will actually run `pulumi up`.
|
||||
func currentPublicIP() (string, error) {
|
||||
@@ -46,27 +54,34 @@ const originId = "encoded-bucket-origin"
|
||||
const cachingOptimizedPolicyId = "658327ea-f89d-4fab-a63d-7e88639e58f6"
|
||||
|
||||
// newServiceDatabase creates a login role and a same-named database owned by
|
||||
// that role, so the service only ever touches its own database.
|
||||
func newServiceDatabase(ctx *pulumi.Context, provider *postgresql.Provider, service string) (*random.RandomPassword, error) {
|
||||
password, err := random.NewRandomPassword(ctx, service+"-db-password", &random.RandomPasswordArgs{
|
||||
Length: pulumi.Int(24),
|
||||
Special: pulumi.Bool(true),
|
||||
MinUpper: pulumi.Int(1),
|
||||
MinLower: pulumi.Int(1),
|
||||
MinNumeric: pulumi.Int(1),
|
||||
MinSpecial: pulumi.Int(1),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// that role, so the service only ever touches its own database. The password is
|
||||
// generated and kept in stack state, except on the LocalStack stack, where
|
||||
// fixedPassword carries the value docker-compose already hands the container as
|
||||
// DB_PASSWORD — compose reads a static .env and cannot consume a stack output.
|
||||
func newServiceDatabase(ctx *pulumi.Context, provider *postgresql.Provider, service, fixedPassword string) (pulumi.StringOutput, error) {
|
||||
password := pulumi.String(fixedPassword).ToStringOutput()
|
||||
if fixedPassword == "" {
|
||||
generated, err := random.NewRandomPassword(ctx, service+"-db-password", &random.RandomPasswordArgs{
|
||||
Length: pulumi.Int(24),
|
||||
Special: pulumi.Bool(true),
|
||||
MinUpper: pulumi.Int(1),
|
||||
MinLower: pulumi.Int(1),
|
||||
MinNumeric: pulumi.Int(1),
|
||||
MinSpecial: pulumi.Int(1),
|
||||
})
|
||||
if err != nil {
|
||||
return pulumi.StringOutput{}, err
|
||||
}
|
||||
password = generated.Result
|
||||
}
|
||||
|
||||
role, err := postgresql.NewRole(ctx, service+"-db-role", &postgresql.RoleArgs{
|
||||
Name: pulumi.String(service),
|
||||
Login: pulumi.Bool(true),
|
||||
Password: password.Result,
|
||||
Password: password,
|
||||
}, pulumi.Provider(provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return pulumi.StringOutput{}, err
|
||||
}
|
||||
|
||||
_, err = postgresql.NewDatabase(ctx, service+"-database", &postgresql.DatabaseArgs{
|
||||
@@ -74,7 +89,7 @@ func newServiceDatabase(ctx *pulumi.Context, provider *postgresql.Provider, serv
|
||||
Owner: role.Name,
|
||||
}, pulumi.Provider(provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return pulumi.StringOutput{}, err
|
||||
}
|
||||
|
||||
return password, nil
|
||||
@@ -114,7 +129,7 @@ func deployFargateService(
|
||||
serviceSecurityGroup *ec2.SecurityGroup,
|
||||
dbAddress pulumi.StringOutput,
|
||||
dbPort pulumi.IntOutput,
|
||||
dbPassword *random.RandomPassword,
|
||||
dbPassword pulumi.StringOutput,
|
||||
runMigrations bool,
|
||||
) (*ecr.Repository, *ecs.Service, *lb.LoadBalancer, error) {
|
||||
alb, err := lb.NewLoadBalancer(ctx, name+"-alb", &lb.LoadBalancerArgs{
|
||||
@@ -154,7 +169,7 @@ func deployFargateService(
|
||||
|
||||
_, err = secretsmanager.NewSecretVersion(ctx, name+"-db-secret-version", &secretsmanager.SecretVersionArgs{
|
||||
SecretId: secret.ID(),
|
||||
SecretString: dbPassword.Result,
|
||||
SecretString: dbPassword,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
@@ -335,8 +350,51 @@ func deployFargateService(
|
||||
|
||||
func main() {
|
||||
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||
// The LocalStack stack (Pulumi.local.yaml) runs this same program
|
||||
// against docker-compose. Everything AWS-endpoint-related is handled by
|
||||
// that file's aws:* config, so the only thing the code has to know is
|
||||
// which resources are meaningful locally: LocalStack stands in for S3,
|
||||
// RDS and IAM, while docker-compose — not ECS — runs the containers, so
|
||||
// the VPC, the load balancers, the ECS services, the CDN and the CI
|
||||
// user are all skipped.
|
||||
cfg := config.New(ctx, "")
|
||||
localstack := cfg.GetBool("localstack")
|
||||
|
||||
// docker-compose passes this to the cms container as DB_PASSWORD; the
|
||||
// service login roles below are created with the same value.
|
||||
servicePassword := ""
|
||||
if localstack {
|
||||
servicePassword = os.Getenv("DB_PASSWORD")
|
||||
if servicePassword == "" {
|
||||
return fmt.Errorf("missing required env var: DB_PASSWORD (needed by the %q stack)", ctx.Stack())
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
distribution *cloudfront.Distribution
|
||||
vpcID string
|
||||
subnetIDs []string
|
||||
dbSubnetGroup *rds.SubnetGroup
|
||||
dbSecurityGroup *ec2.SecurityGroup
|
||||
cluster *ecs.Cluster
|
||||
albSecurityGroup, serviceSecurityGroup *ec2.SecurityGroup
|
||||
execRole *iam.Role
|
||||
cmsRepo, discoveryRepo *ecr.Repository
|
||||
cmsService, discoveryService *ecs.Service
|
||||
cmsAlb, discoveryAlb *lb.LoadBalancer
|
||||
)
|
||||
|
||||
// Bucket names are left to Pulumi's auto-naming in AWS, but pinned under
|
||||
// LocalStack because docker-compose.yml refers to them literally.
|
||||
encodedBucketArgs := &s3.BucketArgs{}
|
||||
rawUploadsBucketArgs := &s3.BucketArgs{}
|
||||
if localstack {
|
||||
encodedBucketArgs.Bucket = pulumi.String("encoded-bucket")
|
||||
rawUploadsBucketArgs.Bucket = pulumi.String("raw-uploads-bucket")
|
||||
}
|
||||
|
||||
// Create an AWS resource (S3 Bucket) that holds encoded assets
|
||||
bucket, err := s3.NewBucket(ctx, "encoded-bucket", nil)
|
||||
bucket, err := s3.NewBucket(ctx, "encoded-bucket", encodedBucketArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -353,140 +411,152 @@ func main() {
|
||||
return err
|
||||
}
|
||||
|
||||
oac, err := cloudfront.NewOriginAccessControl(ctx, "encoded-bucket-oac", &cloudfront.OriginAccessControlArgs{
|
||||
Description: pulumi.String("OAC for encoded-bucket assets"),
|
||||
OriginAccessControlOriginType: pulumi.String("s3"),
|
||||
SigningBehavior: pulumi.String("always"),
|
||||
SigningProtocol: pulumi.String("sigv4"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// CloudFront: public delivery of the finished renditions. Skipped under
|
||||
// LocalStack, which reads the encoded bucket directly.
|
||||
if !localstack {
|
||||
oac, err := cloudfront.NewOriginAccessControl(ctx, "encoded-bucket-oac", &cloudfront.OriginAccessControlArgs{
|
||||
Description: pulumi.String("OAC for encoded-bucket assets"),
|
||||
OriginAccessControlOriginType: pulumi.String("s3"),
|
||||
SigningBehavior: pulumi.String("always"),
|
||||
SigningProtocol: pulumi.String("sigv4"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
distribution, err := cloudfront.NewDistribution(ctx, "encoded-bucket-cdn", &cloudfront.DistributionArgs{
|
||||
Enabled: pulumi.Bool(true),
|
||||
Comment: pulumi.String("Edge caching for encoded-bucket assets"),
|
||||
Origins: cloudfront.DistributionOriginArray{
|
||||
&cloudfront.DistributionOriginArgs{
|
||||
DomainName: bucket.BucketRegionalDomainName,
|
||||
OriginId: pulumi.String(originId),
|
||||
OriginAccessControlId: oac.ID(),
|
||||
S3OriginConfig: &cloudfront.DistributionOriginS3OriginConfigArgs{
|
||||
OriginAccessIdentity: pulumi.String(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
|
||||
TargetOriginId: pulumi.String(originId),
|
||||
ViewerProtocolPolicy: pulumi.String("redirect-to-https"),
|
||||
AllowedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
||||
CachedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
||||
Compress: pulumi.Bool(true),
|
||||
CachePolicyId: pulumi.String(cachingOptimizedPolicyId),
|
||||
},
|
||||
Restrictions: &cloudfront.DistributionRestrictionsArgs{
|
||||
GeoRestriction: &cloudfront.DistributionRestrictionsGeoRestrictionArgs{
|
||||
RestrictionType: pulumi.String("none"),
|
||||
},
|
||||
},
|
||||
ViewerCertificate: &cloudfront.DistributionViewerCertificateArgs{
|
||||
CloudfrontDefaultCertificate: pulumi.Bool(true),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Allow only this distribution to read objects from the bucket.
|
||||
policy := pulumi.All(bucket.Arn, distribution.Arn).ApplyT(func(args []interface{}) (string, error) {
|
||||
bucketArn := args[0].(string)
|
||||
distributionArn := args[1].(string)
|
||||
|
||||
doc := map[string]interface{}{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": []map[string]interface{}{
|
||||
{
|
||||
"Sid": "AllowCloudFrontServicePrincipal",
|
||||
"Effect": "Allow",
|
||||
"Principal": map[string]string{"Service": "cloudfront.amazonaws.com"},
|
||||
"Action": "s3:GetObject",
|
||||
"Resource": bucketArn + "/*",
|
||||
"Condition": map[string]interface{}{
|
||||
"StringEquals": map[string]string{"AWS:SourceArn": distributionArn},
|
||||
distribution, err = cloudfront.NewDistribution(ctx, "encoded-bucket-cdn", &cloudfront.DistributionArgs{
|
||||
Enabled: pulumi.Bool(true),
|
||||
Comment: pulumi.String("Edge caching for encoded-bucket assets"),
|
||||
Origins: cloudfront.DistributionOriginArray{
|
||||
&cloudfront.DistributionOriginArgs{
|
||||
DomainName: bucket.BucketRegionalDomainName,
|
||||
OriginId: pulumi.String(originId),
|
||||
OriginAccessControlId: oac.ID(),
|
||||
S3OriginConfig: &cloudfront.DistributionOriginS3OriginConfigArgs{
|
||||
OriginAccessIdentity: pulumi.String(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
|
||||
TargetOriginId: pulumi.String(originId),
|
||||
ViewerProtocolPolicy: pulumi.String("redirect-to-https"),
|
||||
AllowedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
||||
CachedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
||||
Compress: pulumi.Bool(true),
|
||||
CachePolicyId: pulumi.String(cachingOptimizedPolicyId),
|
||||
},
|
||||
Restrictions: &cloudfront.DistributionRestrictionsArgs{
|
||||
GeoRestriction: &cloudfront.DistributionRestrictionsGeoRestrictionArgs{
|
||||
RestrictionType: pulumi.String("none"),
|
||||
},
|
||||
},
|
||||
ViewerCertificate: &cloudfront.DistributionViewerCertificateArgs{
|
||||
CloudfrontDefaultCertificate: pulumi.Bool(true),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(doc)
|
||||
return string(b), err
|
||||
}).(pulumi.StringOutput)
|
||||
|
||||
_, err = s3.NewBucketPolicy(ctx, "encoded-bucket-policy", &s3.BucketPolicyArgs{
|
||||
Bucket: bucket.ID(),
|
||||
Policy: policy,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
// Allow only this distribution to read objects from the bucket.
|
||||
policy := pulumi.All(bucket.Arn, distribution.Arn).ApplyT(func(args []interface{}) (string, error) {
|
||||
bucketArn := args[0].(string)
|
||||
distributionArn := args[1].(string)
|
||||
|
||||
doc := map[string]interface{}{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": []map[string]interface{}{
|
||||
{
|
||||
"Sid": "AllowCloudFrontServicePrincipal",
|
||||
"Effect": "Allow",
|
||||
"Principal": map[string]string{"Service": "cloudfront.amazonaws.com"},
|
||||
"Action": "s3:GetObject",
|
||||
"Resource": bucketArn + "/*",
|
||||
"Condition": map[string]interface{}{
|
||||
"StringEquals": map[string]string{"AWS:SourceArn": distributionArn},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
b, err := json.Marshal(doc)
|
||||
return string(b), err
|
||||
}).(pulumi.StringOutput)
|
||||
|
||||
_, err = s3.NewBucketPolicy(ctx, "encoded-bucket-policy", &s3.BucketPolicyArgs{
|
||||
Bucket: bucket.ID(),
|
||||
Policy: policy,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Small, single-AZ Postgres instance for the assignment: default VPC,
|
||||
// no public access, no Multi-AZ, no backups, destroyable without a snapshot.
|
||||
vpc, err := ec2.LookupVpc(ctx, &ec2.LookupVpcArgs{Default: pulumi.BoolRef(true)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Networking for the RDS instance and the ECS services. Skipped under
|
||||
// LocalStack: its RDS emulation is a Postgres process inside the
|
||||
// LocalStack container, reached over the docker-compose network rather
|
||||
// than through a VPC, and nothing else here runs in one.
|
||||
if !localstack {
|
||||
// Small, single-AZ Postgres instance for the assignment: default VPC,
|
||||
// no public access, no Multi-AZ, no backups, destroyable without a snapshot.
|
||||
vpc, err := ec2.LookupVpc(ctx, &ec2.LookupVpcArgs{Default: pulumi.BoolRef(true)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vpcID = vpc.Id
|
||||
|
||||
subnets, err := ec2.GetSubnets(ctx, &ec2.GetSubnetsArgs{
|
||||
Filters: []ec2.GetSubnetsFilter{
|
||||
{Name: "vpc-id", Values: []string{vpc.Id}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbSubnetGroup, err := rds.NewSubnetGroup(ctx, "postgres-subnet-group", &rds.SubnetGroupArgs{
|
||||
SubnetIds: pulumi.ToStringArray(subnets.Ids),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
myIP, err := currentPublicIP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
myIPCidr := fmt.Sprintf("%s/32", myIP)
|
||||
|
||||
dbSecurityGroup, err := ec2.NewSecurityGroup(ctx, "postgres-sg", &ec2.SecurityGroupArgs{
|
||||
Description: pulumi.String("Allow Postgres access from within the VPC and the deployers IP"),
|
||||
VpcId: pulumi.String(vpc.Id),
|
||||
Ingress: ec2.SecurityGroupIngressArray{
|
||||
&ec2.SecurityGroupIngressArgs{
|
||||
Protocol: pulumi.String("tcp"),
|
||||
FromPort: pulumi.Int(5432),
|
||||
ToPort: pulumi.Int(5432),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{vpc.CidrBlock}),
|
||||
subnets, err := ec2.GetSubnets(ctx, &ec2.GetSubnetsArgs{
|
||||
Filters: []ec2.GetSubnetsFilter{
|
||||
{Name: "vpc-id", Values: []string{vpc.Id}},
|
||||
},
|
||||
&ec2.SecurityGroupIngressArgs{
|
||||
Protocol: pulumi.String("tcp"),
|
||||
FromPort: pulumi.Int(5432),
|
||||
ToPort: pulumi.Int(5432),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{myIPCidr}),
|
||||
Description: pulumi.String("Deployer IP, for the postgresql provider to create databases"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subnetIDs = subnets.Ids
|
||||
|
||||
dbSubnetGroup, err = rds.NewSubnetGroup(ctx, "postgres-subnet-group", &rds.SubnetGroupArgs{
|
||||
SubnetIds: pulumi.ToStringArray(subnets.Ids),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
myIP, err := currentPublicIP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
myIPCidr := fmt.Sprintf("%s/32", myIP)
|
||||
|
||||
dbSecurityGroup, err = ec2.NewSecurityGroup(ctx, "postgres-sg", &ec2.SecurityGroupArgs{
|
||||
Description: pulumi.String("Allow Postgres access from within the VPC and the deployers IP"),
|
||||
VpcId: pulumi.String(vpc.Id),
|
||||
Ingress: ec2.SecurityGroupIngressArray{
|
||||
&ec2.SecurityGroupIngressArgs{
|
||||
Protocol: pulumi.String("tcp"),
|
||||
FromPort: pulumi.Int(5432),
|
||||
ToPort: pulumi.Int(5432),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{vpc.CidrBlock}),
|
||||
},
|
||||
&ec2.SecurityGroupIngressArgs{
|
||||
Protocol: pulumi.String("tcp"),
|
||||
FromPort: pulumi.Int(5432),
|
||||
ToPort: pulumi.Int(5432),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{myIPCidr}),
|
||||
Description: pulumi.String("Deployer IP, for the postgresql provider to create databases"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Egress: ec2.SecurityGroupEgressArray{
|
||||
&ec2.SecurityGroupEgressArgs{
|
||||
Protocol: pulumi.String("-1"),
|
||||
FromPort: pulumi.Int(0),
|
||||
ToPort: pulumi.Int(0),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{"0.0.0.0/0"}),
|
||||
Egress: ec2.SecurityGroupEgressArray{
|
||||
&ec2.SecurityGroupEgressArgs{
|
||||
Protocol: pulumi.String("-1"),
|
||||
FromPort: pulumi.Int(0),
|
||||
ToPort: pulumi.Int(0),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{"0.0.0.0/0"}),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Generated once and kept in the stack state, so it stays stable across deploys
|
||||
@@ -504,7 +574,7 @@ func main() {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := rds.NewInstance(ctx, "postgres-db", &rds.InstanceArgs{
|
||||
dbArgs := &rds.InstanceArgs{
|
||||
Engine: pulumi.String("postgres"),
|
||||
EngineVersion: pulumi.String("16"),
|
||||
InstanceClass: pulumi.String("db.t3.micro"),
|
||||
@@ -514,109 +584,54 @@ func main() {
|
||||
DbName: pulumi.String("appdb"),
|
||||
Username: pulumi.String("postgres"),
|
||||
Password: dbPassword.Result,
|
||||
DbSubnetGroupName: dbSubnetGroup.Name,
|
||||
VpcSecurityGroupIds: pulumi.StringArray{dbSecurityGroup.ID()},
|
||||
PubliclyAccessible: pulumi.Bool(true),
|
||||
MultiAz: pulumi.Bool(false),
|
||||
BackupRetentionPeriod: pulumi.Int(0),
|
||||
DeletionProtection: pulumi.Bool(false),
|
||||
SkipFinalSnapshot: pulumi.Bool(true),
|
||||
ApplyImmediately: pulumi.Bool(true),
|
||||
})
|
||||
}
|
||||
if localstack {
|
||||
dbArgs.Port = pulumi.Int(localstackDBPort)
|
||||
} else {
|
||||
dbArgs.DbSubnetGroupName = dbSubnetGroup.Name
|
||||
dbArgs.VpcSecurityGroupIds = pulumi.StringArray{dbSecurityGroup.ID()}
|
||||
}
|
||||
|
||||
db, err := rds.NewInstance(ctx, "postgres-db", dbArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// RDS terminates TLS; the Postgres process LocalStack runs does not.
|
||||
sslMode := "require"
|
||||
if localstack {
|
||||
sslMode = "disable"
|
||||
}
|
||||
|
||||
pgProvider, err := postgresql.NewProvider(ctx, "postgres-provider", &postgresql.ProviderArgs{
|
||||
Host: db.Address,
|
||||
Port: db.Port,
|
||||
Username: pulumi.String("postgres"),
|
||||
Password: dbPassword.Result,
|
||||
Sslmode: pulumi.String("require"),
|
||||
Sslmode: pulumi.String(sslMode),
|
||||
Superuser: pulumi.Bool(false),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmsPassword, err := newServiceDatabase(ctx, pgProvider, "cms")
|
||||
cmsPassword, err := newServiceDatabase(ctx, pgProvider, "cms", servicePassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
discoveryPassword, err := newServiceDatabase(ctx, pgProvider, "discovery")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ECS Fargate: one cluster, one internet-facing ALB, one service per app.
|
||||
cluster, err := ecs.NewCluster(ctx, "app-cluster", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ecs.NewClusterCapacityProviders(ctx, "app-cluster-capacity-providers", &ecs.ClusterCapacityProvidersArgs{
|
||||
ClusterName: cluster.Name,
|
||||
CapacityProviders: pulumi.ToStringArray([]string{"FARGATE", "FARGATE_SPOT"}),
|
||||
DefaultCapacityProviderStrategies: ecs.ClusterCapacityProvidersDefaultCapacityProviderStrategyArray{
|
||||
&ecs.ClusterCapacityProvidersDefaultCapacityProviderStrategyArgs{
|
||||
CapacityProvider: pulumi.String("FARGATE"),
|
||||
Weight: pulumi.Int(1),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
albSecurityGroup, err := ec2.NewSecurityGroup(ctx, "alb-sg", &ec2.SecurityGroupArgs{
|
||||
Description: pulumi.String("Allow inbound HTTP to the app load balancers"),
|
||||
VpcId: pulumi.String(vpc.Id),
|
||||
Ingress: ec2.SecurityGroupIngressArray{
|
||||
&ec2.SecurityGroupIngressArgs{
|
||||
Protocol: pulumi.String("tcp"),
|
||||
FromPort: pulumi.Int(80),
|
||||
ToPort: pulumi.Int(80),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{"0.0.0.0/0"}),
|
||||
},
|
||||
},
|
||||
Egress: ec2.SecurityGroupEgressArray{
|
||||
&ec2.SecurityGroupEgressArgs{
|
||||
Protocol: pulumi.String("-1"),
|
||||
FromPort: pulumi.Int(0),
|
||||
ToPort: pulumi.Int(0),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{"0.0.0.0/0"}),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serviceSecurityGroup, err := ec2.NewSecurityGroup(ctx, "ecs-service-sg", &ec2.SecurityGroupArgs{
|
||||
Description: pulumi.String("Allow the ALB to reach cms and discovery tasks"),
|
||||
VpcId: pulumi.String(vpc.Id),
|
||||
Ingress: ec2.SecurityGroupIngressArray{
|
||||
&ec2.SecurityGroupIngressArgs{
|
||||
Protocol: pulumi.String("tcp"),
|
||||
FromPort: pulumi.Int(0),
|
||||
ToPort: pulumi.Int(65535),
|
||||
SecurityGroups: pulumi.StringArray{albSecurityGroup.ID()},
|
||||
},
|
||||
},
|
||||
Egress: ec2.SecurityGroupEgressArray{
|
||||
&ec2.SecurityGroupEgressArgs{
|
||||
Protocol: pulumi.String("-1"),
|
||||
FromPort: pulumi.Int(0),
|
||||
ToPort: pulumi.Int(0),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{"0.0.0.0/0"}),
|
||||
},
|
||||
},
|
||||
})
|
||||
discoveryPassword, err := newServiceDatabase(ctx, pgProvider, "discovery", servicePassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Trust policy shared by every ECS task role.
|
||||
execRoleAssumePolicy, err := json.Marshal(map[string]any{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": []map[string]any{
|
||||
@@ -631,21 +646,94 @@ func main() {
|
||||
return err
|
||||
}
|
||||
|
||||
execRole, err := iam.NewRole(ctx, "ecs-task-execution-role", &iam.RoleArgs{
|
||||
AssumeRolePolicy: pulumi.String(execRoleAssumePolicy),
|
||||
ManagedPolicyArns: pulumi.ToStringArray([]string{
|
||||
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
// ECS Fargate: one cluster, one internet-facing ALB, one service per app.
|
||||
// docker-compose runs the containers under LocalStack, so none of this
|
||||
// — cluster, load balancer security groups, or the ECS agent's
|
||||
// execution role — has a local counterpart.
|
||||
if !localstack {
|
||||
cluster, err = ecs.NewCluster(ctx, "app-cluster", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ecs.NewClusterCapacityProviders(ctx, "app-cluster-capacity-providers", &ecs.ClusterCapacityProvidersArgs{
|
||||
ClusterName: cluster.Name,
|
||||
CapacityProviders: pulumi.ToStringArray([]string{"FARGATE", "FARGATE_SPOT"}),
|
||||
DefaultCapacityProviderStrategies: ecs.ClusterCapacityProvidersDefaultCapacityProviderStrategyArray{
|
||||
&ecs.ClusterCapacityProvidersDefaultCapacityProviderStrategyArgs{
|
||||
CapacityProvider: pulumi.String("FARGATE"),
|
||||
Weight: pulumi.Int(1),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
albSecurityGroup, err = ec2.NewSecurityGroup(ctx, "alb-sg", &ec2.SecurityGroupArgs{
|
||||
Description: pulumi.String("Allow inbound HTTP to the app load balancers"),
|
||||
VpcId: pulumi.String(vpcID),
|
||||
Ingress: ec2.SecurityGroupIngressArray{
|
||||
&ec2.SecurityGroupIngressArgs{
|
||||
Protocol: pulumi.String("tcp"),
|
||||
FromPort: pulumi.Int(80),
|
||||
ToPort: pulumi.Int(80),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{"0.0.0.0/0"}),
|
||||
},
|
||||
},
|
||||
Egress: ec2.SecurityGroupEgressArray{
|
||||
&ec2.SecurityGroupEgressArgs{
|
||||
Protocol: pulumi.String("-1"),
|
||||
FromPort: pulumi.Int(0),
|
||||
ToPort: pulumi.Int(0),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{"0.0.0.0/0"}),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serviceSecurityGroup, err = ec2.NewSecurityGroup(ctx, "ecs-service-sg", &ec2.SecurityGroupArgs{
|
||||
Description: pulumi.String("Allow the ALB to reach cms and discovery tasks"),
|
||||
VpcId: pulumi.String(vpcID),
|
||||
Ingress: ec2.SecurityGroupIngressArray{
|
||||
&ec2.SecurityGroupIngressArgs{
|
||||
Protocol: pulumi.String("tcp"),
|
||||
FromPort: pulumi.Int(0),
|
||||
ToPort: pulumi.Int(65535),
|
||||
SecurityGroups: pulumi.StringArray{albSecurityGroup.ID()},
|
||||
},
|
||||
},
|
||||
Egress: ec2.SecurityGroupEgressArray{
|
||||
&ec2.SecurityGroupEgressArgs{
|
||||
Protocol: pulumi.String("-1"),
|
||||
FromPort: pulumi.Int(0),
|
||||
ToPort: pulumi.Int(0),
|
||||
CidrBlocks: pulumi.ToStringArray([]string{"0.0.0.0/0"}),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
execRole, err = iam.NewRole(ctx, "ecs-task-execution-role", &iam.RoleArgs{
|
||||
AssumeRolePolicy: pulumi.String(execRoleAssumePolicy),
|
||||
ManagedPolicyArns: pulumi.ToStringArray([]string{
|
||||
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Private bucket for raw video uploads (pre-transcode). Kept separate
|
||||
// from encoded-bucket, which is fronted by CloudFront/OAC for public
|
||||
// delivery of finished renditions — raw source video must not be
|
||||
// reachable through that CDN.
|
||||
rawUploadsBucket, err := s3.NewBucket(ctx, "raw-uploads-bucket", nil)
|
||||
rawUploadsBucket, err := s3.NewBucket(ctx, "raw-uploads-bucket", rawUploadsBucketArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -786,31 +874,43 @@ func main() {
|
||||
return err
|
||||
}
|
||||
|
||||
cmsExtraEnv := []envVar{
|
||||
{Name: "AWS_REGION", Value: pulumi.String(awsRegion).ToStringOutput()},
|
||||
{Name: "S3_BUCKET", Value: rawUploadsBucket.ID().ToStringOutput()},
|
||||
{Name: "MEDIACONVERT_INPUT_BUCKET", Value: rawUploadsBucket.ID().ToStringOutput()},
|
||||
{Name: "MEDIACONVERT_OUTPUT_BUCKET", Value: bucket.ID().ToStringOutput()},
|
||||
{Name: "MEDIACONVERT_ROLE_ARN", Value: mediaConvertRole.Arn},
|
||||
}
|
||||
// The two ECS services and the CI user that deploys them. Under
|
||||
// LocalStack the equivalent of all this is the cms service in
|
||||
// docker-compose.yml, which reads the same env vars from .env.
|
||||
if !localstack {
|
||||
cmsExtraEnv := []envVar{
|
||||
{Name: "AWS_REGION", Value: pulumi.String(awsRegion).ToStringOutput()},
|
||||
{Name: "S3_BUCKET", Value: rawUploadsBucket.ID().ToStringOutput()},
|
||||
{Name: "MEDIACONVERT_INPUT_BUCKET", Value: rawUploadsBucket.ID().ToStringOutput()},
|
||||
{Name: "MEDIACONVERT_OUTPUT_BUCKET", Value: bucket.ID().ToStringOutput()},
|
||||
{Name: "MEDIACONVERT_ROLE_ARN", Value: mediaConvertRole.Arn},
|
||||
}
|
||||
|
||||
cmsRepo, cmsService, cmsAlb, err := deployFargateService(ctx, "cms", 8081,
|
||||
cluster, execRole, cmsTaskRole, cmsExtraEnv, vpc.Id, subnets.Ids, albSecurityGroup, serviceSecurityGroup,
|
||||
db.Address, db.Port, cmsPassword, true)
|
||||
if err != nil {
|
||||
return err
|
||||
cmsRepo, cmsService, cmsAlb, err = deployFargateService(ctx, "cms", 8081,
|
||||
cluster, execRole, cmsTaskRole, cmsExtraEnv, vpcID, subnetIDs, albSecurityGroup, serviceSecurityGroup,
|
||||
db.Address, db.Port, cmsPassword, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// The browser PUTs directly to raw-uploads-bucket using the presigned
|
||||
// URL from PresignVideoUpload, so the bucket (not the cms app) is
|
||||
// what needs to answer the CORS preflight — scoped to the cms
|
||||
// origin the upload page is actually served from.
|
||||
// origin the upload page is actually served from. Under LocalStack
|
||||
// there is no ALB to name, and the caller is whatever the developer
|
||||
// happens to be running.
|
||||
var corsOrigins pulumi.StringArrayInput = pulumi.ToStringArray([]string{"*"})
|
||||
if !localstack {
|
||||
corsOrigins = pulumi.StringArray{pulumi.Sprintf("http://%s", cmsAlb.DnsName)}
|
||||
}
|
||||
|
||||
_, err = s3.NewBucketCorsConfigurationV2(ctx, "raw-uploads-bucket-cors", &s3.BucketCorsConfigurationV2Args{
|
||||
Bucket: rawUploadsBucket.ID(),
|
||||
CorsRules: s3.BucketCorsConfigurationV2CorsRuleArray{
|
||||
&s3.BucketCorsConfigurationV2CorsRuleArgs{
|
||||
AllowedMethods: pulumi.ToStringArray([]string{"PUT"}),
|
||||
AllowedOrigins: pulumi.StringArray{pulumi.Sprintf("http://%s", cmsAlb.DnsName)},
|
||||
AllowedOrigins: corsOrigins,
|
||||
AllowedHeaders: pulumi.ToStringArray([]string{"*"}),
|
||||
ExposeHeaders: pulumi.ToStringArray([]string{"ETag"}),
|
||||
MaxAgeSeconds: pulumi.Int(3000),
|
||||
@@ -821,90 +921,99 @@ func main() {
|
||||
return err
|
||||
}
|
||||
|
||||
discoveryRepo, discoveryService, discoveryAlb, err := deployFargateService(ctx, "discovery", 8080,
|
||||
cluster, execRole, nil, nil, vpc.Id, subnets.Ids, albSecurityGroup, serviceSecurityGroup,
|
||||
db.Address, db.Port, discoveryPassword, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !localstack {
|
||||
discoveryRepo, discoveryService, discoveryAlb, err = deployFargateService(ctx, "discovery", 8080,
|
||||
cluster, execRole, nil, nil, vpcID, subnetIDs, albSecurityGroup, serviceSecurityGroup,
|
||||
db.Address, db.Port, discoveryPassword, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Gitea Actions CI: one IAM user, scoped to just what the build/deploy
|
||||
// pipeline needs — push images to the two ECR repos and force a new
|
||||
// deployment on the two ECS services. Credentials are exported as
|
||||
// stack secrets for the pipeline to consume (e.g. as Gitea Actions
|
||||
// secrets); nothing broader like ecr:*, ecs:*, or task-def changes.
|
||||
giteaCIPolicyDocument := pulumi.All(cmsRepo.Arn, discoveryRepo.Arn, cmsService.Arn, discoveryService.Arn).ApplyT(
|
||||
func(args []any) (string, error) {
|
||||
cmsRepoArn := args[0].(string)
|
||||
discoveryRepoArn := args[1].(string)
|
||||
cmsServiceArn := args[2].(string)
|
||||
discoveryServiceArn := args[3].(string)
|
||||
// Gitea Actions CI: one IAM user, scoped to just what the build/deploy
|
||||
// pipeline needs — push images to the two ECR repos and force a new
|
||||
// deployment on the two ECS services. Credentials are exported as
|
||||
// stack secrets for the pipeline to consume (e.g. as Gitea Actions
|
||||
// secrets); nothing broader like ecr:*, ecs:*, or task-def changes.
|
||||
giteaCIPolicyDocument := pulumi.All(cmsRepo.Arn, discoveryRepo.Arn, cmsService.Arn, discoveryService.Arn).ApplyT(
|
||||
func(args []any) (string, error) {
|
||||
cmsRepoArn := args[0].(string)
|
||||
discoveryRepoArn := args[1].(string)
|
||||
cmsServiceArn := args[2].(string)
|
||||
discoveryServiceArn := args[3].(string)
|
||||
|
||||
doc := map[string]any{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": []map[string]any{
|
||||
{
|
||||
"Sid": "ECRAuth",
|
||||
"Effect": "Allow",
|
||||
"Action": "ecr:GetAuthorizationToken",
|
||||
"Resource": "*",
|
||||
},
|
||||
{
|
||||
"Sid": "ECRPush",
|
||||
"Effect": "Allow",
|
||||
"Action": []string{
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:PutImage",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:CompleteLayerUpload",
|
||||
doc := map[string]any{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": []map[string]any{
|
||||
{
|
||||
"Sid": "ECRAuth",
|
||||
"Effect": "Allow",
|
||||
"Action": "ecr:GetAuthorizationToken",
|
||||
"Resource": "*",
|
||||
},
|
||||
"Resource": []string{cmsRepoArn, discoveryRepoArn},
|
||||
},
|
||||
{
|
||||
"Sid": "ECSDeploy",
|
||||
"Effect": "Allow",
|
||||
"Action": []string{
|
||||
"ecs:UpdateService",
|
||||
"ecs:DescribeServices",
|
||||
{
|
||||
"Sid": "ECRPush",
|
||||
"Effect": "Allow",
|
||||
"Action": []string{
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:PutImage",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:CompleteLayerUpload",
|
||||
},
|
||||
"Resource": []string{cmsRepoArn, discoveryRepoArn},
|
||||
},
|
||||
{
|
||||
"Sid": "ECSDeploy",
|
||||
"Effect": "Allow",
|
||||
"Action": []string{
|
||||
"ecs:UpdateService",
|
||||
"ecs:DescribeServices",
|
||||
},
|
||||
"Resource": []string{cmsServiceArn, discoveryServiceArn},
|
||||
},
|
||||
"Resource": []string{cmsServiceArn, discoveryServiceArn},
|
||||
},
|
||||
},
|
||||
}
|
||||
b, err := json.Marshal(doc)
|
||||
return string(b), err
|
||||
},
|
||||
).(pulumi.StringOutput)
|
||||
}
|
||||
b, err := json.Marshal(doc)
|
||||
return string(b), err
|
||||
},
|
||||
).(pulumi.StringOutput)
|
||||
|
||||
giteaCIUser, err := iam.NewUser(ctx, "gitea-ci-user", &iam.UserArgs{
|
||||
Name: pulumi.String("gitea-ci"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
giteaCIUser, err := iam.NewUser(ctx, "gitea-ci-user", &iam.UserArgs{
|
||||
Name: pulumi.String("gitea-ci"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = iam.NewUserPolicy(ctx, "gitea-ci-policy", &iam.UserPolicyArgs{
|
||||
User: giteaCIUser.Name,
|
||||
Policy: giteaCIPolicyDocument,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
_, err = iam.NewUserPolicy(ctx, "gitea-ci-policy", &iam.UserPolicyArgs{
|
||||
User: giteaCIUser.Name,
|
||||
Policy: giteaCIPolicyDocument,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Export the name of the bucket
|
||||
ctx.Export("bucketName", bucket.ID())
|
||||
ctx.Export("cdnDomainName", distribution.DomainName)
|
||||
ctx.Export("rawUploadsBucketName", rawUploadsBucket.ID())
|
||||
ctx.Export("dbEndpoint", db.Endpoint)
|
||||
ctx.Export("dbPassword", dbPassword.Result)
|
||||
ctx.Export("cmsUrl", pulumi.Sprintf("http://%s", cmsAlb.DnsName))
|
||||
ctx.Export("discoveryUrl", pulumi.Sprintf("http://%s", discoveryAlb.DnsName))
|
||||
ctx.Export("cmsRepoUrl", cmsRepo.RepositoryUrl)
|
||||
ctx.Export("discoveryRepoUrl", discoveryRepo.RepositoryUrl)
|
||||
ctx.Export("ecsClusterArn", cluster.Arn)
|
||||
ctx.Export("cmsServiceArn", cmsService.Arn)
|
||||
ctx.Export("discoveryServiceArn", discoveryService.Arn)
|
||||
ctx.Export("mediaConvertRoleArn", mediaConvertRole.Arn)
|
||||
|
||||
// Everything below exists only on the AWS stacks. The CI workflows read
|
||||
// ecsClusterArn and cmsServiceArn from here.
|
||||
if !localstack {
|
||||
ctx.Export("cdnDomainName", distribution.DomainName)
|
||||
ctx.Export("cmsUrl", pulumi.Sprintf("http://%s", cmsAlb.DnsName))
|
||||
ctx.Export("discoveryUrl", pulumi.Sprintf("http://%s", discoveryAlb.DnsName))
|
||||
ctx.Export("cmsRepoUrl", cmsRepo.RepositoryUrl)
|
||||
ctx.Export("discoveryRepoUrl", discoveryRepo.RepositoryUrl)
|
||||
ctx.Export("ecsClusterArn", cluster.Arn)
|
||||
ctx.Export("cmsServiceArn", cmsService.Arn)
|
||||
ctx.Export("discoveryServiceArn", discoveryService.Arn)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user