如何通过环境变量部署对AWS的反应
上下文
我有一个React Web应用程序,可以通过Codepipeline部署到AWS。我的codepipeline与我的React GitHub存储库相连,因此每当我将更改推向GitHub时,我的CodePipeline都会重新构建工件并将其部署到S3桶中。
问题
现在我创建了不同的.env
文件来存储环境变量。我所做的与这些非常相似:
因此带有.env.env.production
文件的网站工件。
由于安全原因,我们不应该将.env
文件添加到github。我应该如何设置环境变量,以便在管道的构建
阶段中可以从某个地方获取.env
文件?
我的CDK代码
import * as CDK from "aws-cdk-lib";
import * as YAML from "yaml";
import * as FS from "fs";
import * as CodeBuild from "aws-cdk-lib/aws-codebuild";
import * as S3 from "aws-cdk-lib/aws-s3";
import * as CloudFront from "aws-cdk-lib/aws-cloudfront";
import * as ACM from "aws-cdk-lib/aws-certificatemanager";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as Route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
import * as IAM from "aws-cdk-lib/aws-iam";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions";
export interface CodePipelineStackProps extends CDK.StackProps {
// Built in Stack props
readonly env: CDK.Environment;
readonly description: string;
readonly websiteDomain: string;
}
export class CodePipelineStack extends CDK.Stack {
constructor(scope: CDK.App, id: string, props: CodePipelineStackProps) {
super(scope, id, props);
// AWS CodeBuild artifacts
const outputSources = new codepipeline.Artifact();
const outputWebsite = new codepipeline.Artifact();
// AWS CodePipeline pipeline
const pipeline = new codepipeline.Pipeline(this, "Pipeline", {
pipelineName: "PandaWebsite",
restartExecutionOnUpdate: true,
});
this.addSourceStage(pipeline, outputSources);
this.addBuildStage(pipeline, outputSources, outputWebsite);
// Amazon S3 bucket to host the store website artifact
const websiteBucket = new S3.Bucket(this, "PandaWebsite", {
bucketName: `${props.websiteDomain}-${props.env.account}-${props.env.region}`,
websiteIndexDocument: "index.html",
websiteErrorDocument: "error.html",
removalPolicy: CDK.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
accessControl: S3.BucketAccessControl.PRIVATE,
encryption: S3.BucketEncryption.S3_MANAGED,
publicReadAccess: false,
blockPublicAccess: S3.BlockPublicAccess.BLOCK_ALL,
});
const hostedZone: Route53.IHostedZone = Route53.HostedZone.fromLookup(
this,
"HostedZoneId",
{
domainName: props.websiteDomain,
}
);
const cloudFrontDistribution: CloudFront.Distribution =
this.createCloudFrontDistribution(
props.websiteDomain,
websiteBucket,
hostedZone
);
new Route53.ARecord(this, "Route53RecordSet", {
recordName: props.websiteDomain,
zone: hostedZone,
target: Route53.RecordTarget.fromAlias(
new Route53Targets.CloudFrontTarget(cloudFrontDistribution)
),
});
// AWS CodePipeline stage to deployt website
pipeline.addStage({
stageName: "Deploy",
actions: [
// AWS CodePipeline action to deploy website to S3 bucket
new codepipeline_actions.S3DeployAction({
actionName: "PandaWebsite",
input: outputWebsite,
bucket: websiteBucket,
}),
],
});
new CDK.CfnOutput(this, "DeployURL", {
value: `https://${props.websiteDomain}`,
description: "Website URL",
});
}
private addSourceStage(
pipeline: codepipeline.Pipeline,
outputSources: codepipeline.Artifact
) {
// AWS CodePipeline stage to clone sources from GitHub repository
pipeline.addStage({
stageName: "Source",
actions: [
new codepipeline_actions.GitHubSourceAction({
actionName: "Checkout",
owner: "yangliu",
repo: "PandaWebsite",
branch: "main",
oauthToken: CDK.SecretValue.secretsManager(
"PandaWebsite-GitHubToken"
),
output: outputSources,
trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
}),
],
});
}
private addBuildStage(
pipeline: codepipeline.Pipeline,
outputSources: codepipeline.Artifact,
outputWebsite: codepipeline.Artifact
) {
const buildspecFile = FS.readFileSync("./config/buildspec.yml", "utf-8");
const buildspecFileYaml = YAML.parse(buildspecFile, {
prettyErrors: true,
});
pipeline.addStage({
stageName: "Build",
actions: [
new codepipeline_actions.CodeBuildAction({
actionName: "BuildeWebsite",
project: new CodeBuild.PipelineProject(this, "BuildWebsite", {
projectName: "BuildeWebsite",
environment: {
buildImage: CodeBuild.LinuxBuildImage.STANDARD_5_0,
},
buildSpec: CodeBuild.BuildSpec.fromObjectToYaml(buildspecFileYaml),
}),
input: outputSources,
outputs: [outputWebsite],
}),
],
});
}
private createCloudFrontDistribution(
websiteDomain: string,
websiteBucket: S3.Bucket,
hostedZone: Route53.IHostedZone
) {
const certificateManagerCertificate = new ACM.Certificate(
this,
"CertificateManagerCertificate",
{
domainName: websiteDomain,
validation: ACM.CertificateValidation.fromDns(hostedZone),
}
);
// Create a special CloudFront user called an origin access identity (OAI)
// and associate it with the CloudFront distribution.
const cloudFrontOAI = new CloudFront.OriginAccessIdentity(
this,
"PandaWebsiteOriginAccessIdentityID",
{
comment: "OriginAccessIdentityID for PandaWebsite"
}
);
const cloudfrontUserAccessPolicy = new IAM.PolicyStatement();
cloudfrontUserAccessPolicy.addActions("s3:GetObject");
cloudfrontUserAccessPolicy.addPrincipals(cloudFrontOAI.grantPrincipal);
cloudfrontUserAccessPolicy.addResources(websiteBucket.arnForObjects("*"));
websiteBucket.addToResourcePolicy(cloudfrontUserAccessPolicy);
return new CloudFront.Distribution(this, "CloudFrontDistribution", {
domainNames: [websiteDomain],
defaultBehavior: {
origin: new CloudFrontOrigins.S3Origin(websiteBucket, {
// CloudFront can use the OAI to access the files in the S3 bucket
// and serve them to users. Users can’t use a direct URL to the
// S3 bucket to access a file there.
originAccessIdentity: cloudFrontOAI,
}),
compress: true,
allowedMethods: CloudFront.AllowedMethods.ALLOW_GET_HEAD,
cachedMethods: CloudFront.CachedMethods.CACHE_GET_HEAD,
viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
cachePolicy: CloudFront.CachePolicy.CACHING_OPTIMIZED,
},
errorResponses: [
{
httpStatus: 403,
responsePagePath: "/index.html",
responseHttpStatus: 200,
ttl: CDK.Duration.minutes(0),
},
{
httpStatus: 404,
responsePagePath: "/index.html",
responseHttpStatus: 200,
ttl: CDK.Duration.minutes(0),
},
],
priceClass: CloudFront.PriceClass.PRICE_CLASS_ALL,
enabled: true,
certificate: certificateManagerCertificate,
minimumProtocolVersion: CloudFront.SecurityPolicyProtocol.TLS_V1_2_2021,
httpVersion: CloudFront.HttpVersion.HTTP2,
defaultRootObject: "index.html",
enableIpv6: true,
});
}
}
Context
I have a react web application which I'm able to deploy to AWS with CodePipeline. My codepipeline is hooked with my React GitHub repository so that whenever I push a change to the GitHub, my codepipeline will re-build the artifact and deploy it to S3 bucket.
Problem
Now I created different .env
files to store environment variables. What I did is quite similar to these:
- Reactjs : set .env file with 3 variables(prod,dev,staging) in the build folder with react build
- How to setup the env variable for the react app?
Thus yarn build:prod
will build the website artifact with .env.production
file.
As we should not add .env
files to GitHub for security reasons. How should I setup environment variables so that in the build
stage of my pipeline it can get the .env
files from somewhere?
My CDK Code
import * as CDK from "aws-cdk-lib";
import * as YAML from "yaml";
import * as FS from "fs";
import * as CodeBuild from "aws-cdk-lib/aws-codebuild";
import * as S3 from "aws-cdk-lib/aws-s3";
import * as CloudFront from "aws-cdk-lib/aws-cloudfront";
import * as ACM from "aws-cdk-lib/aws-certificatemanager";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as Route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
import * as IAM from "aws-cdk-lib/aws-iam";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions";
export interface CodePipelineStackProps extends CDK.StackProps {
// Built in Stack props
readonly env: CDK.Environment;
readonly description: string;
readonly websiteDomain: string;
}
export class CodePipelineStack extends CDK.Stack {
constructor(scope: CDK.App, id: string, props: CodePipelineStackProps) {
super(scope, id, props);
// AWS CodeBuild artifacts
const outputSources = new codepipeline.Artifact();
const outputWebsite = new codepipeline.Artifact();
// AWS CodePipeline pipeline
const pipeline = new codepipeline.Pipeline(this, "Pipeline", {
pipelineName: "PandaWebsite",
restartExecutionOnUpdate: true,
});
this.addSourceStage(pipeline, outputSources);
this.addBuildStage(pipeline, outputSources, outputWebsite);
// Amazon S3 bucket to host the store website artifact
const websiteBucket = new S3.Bucket(this, "PandaWebsite", {
bucketName: `${props.websiteDomain}-${props.env.account}-${props.env.region}`,
websiteIndexDocument: "index.html",
websiteErrorDocument: "error.html",
removalPolicy: CDK.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
accessControl: S3.BucketAccessControl.PRIVATE,
encryption: S3.BucketEncryption.S3_MANAGED,
publicReadAccess: false,
blockPublicAccess: S3.BlockPublicAccess.BLOCK_ALL,
});
const hostedZone: Route53.IHostedZone = Route53.HostedZone.fromLookup(
this,
"HostedZoneId",
{
domainName: props.websiteDomain,
}
);
const cloudFrontDistribution: CloudFront.Distribution =
this.createCloudFrontDistribution(
props.websiteDomain,
websiteBucket,
hostedZone
);
new Route53.ARecord(this, "Route53RecordSet", {
recordName: props.websiteDomain,
zone: hostedZone,
target: Route53.RecordTarget.fromAlias(
new Route53Targets.CloudFrontTarget(cloudFrontDistribution)
),
});
// AWS CodePipeline stage to deployt website
pipeline.addStage({
stageName: "Deploy",
actions: [
// AWS CodePipeline action to deploy website to S3 bucket
new codepipeline_actions.S3DeployAction({
actionName: "PandaWebsite",
input: outputWebsite,
bucket: websiteBucket,
}),
],
});
new CDK.CfnOutput(this, "DeployURL", {
value: `https://${props.websiteDomain}`,
description: "Website URL",
});
}
private addSourceStage(
pipeline: codepipeline.Pipeline,
outputSources: codepipeline.Artifact
) {
// AWS CodePipeline stage to clone sources from GitHub repository
pipeline.addStage({
stageName: "Source",
actions: [
new codepipeline_actions.GitHubSourceAction({
actionName: "Checkout",
owner: "yangliu",
repo: "PandaWebsite",
branch: "main",
oauthToken: CDK.SecretValue.secretsManager(
"PandaWebsite-GitHubToken"
),
output: outputSources,
trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
}),
],
});
}
private addBuildStage(
pipeline: codepipeline.Pipeline,
outputSources: codepipeline.Artifact,
outputWebsite: codepipeline.Artifact
) {
const buildspecFile = FS.readFileSync("./config/buildspec.yml", "utf-8");
const buildspecFileYaml = YAML.parse(buildspecFile, {
prettyErrors: true,
});
pipeline.addStage({
stageName: "Build",
actions: [
new codepipeline_actions.CodeBuildAction({
actionName: "BuildeWebsite",
project: new CodeBuild.PipelineProject(this, "BuildWebsite", {
projectName: "BuildeWebsite",
environment: {
buildImage: CodeBuild.LinuxBuildImage.STANDARD_5_0,
},
buildSpec: CodeBuild.BuildSpec.fromObjectToYaml(buildspecFileYaml),
}),
input: outputSources,
outputs: [outputWebsite],
}),
],
});
}
private createCloudFrontDistribution(
websiteDomain: string,
websiteBucket: S3.Bucket,
hostedZone: Route53.IHostedZone
) {
const certificateManagerCertificate = new ACM.Certificate(
this,
"CertificateManagerCertificate",
{
domainName: websiteDomain,
validation: ACM.CertificateValidation.fromDns(hostedZone),
}
);
// Create a special CloudFront user called an origin access identity (OAI)
// and associate it with the CloudFront distribution.
const cloudFrontOAI = new CloudFront.OriginAccessIdentity(
this,
"PandaWebsiteOriginAccessIdentityID",
{
comment: "OriginAccessIdentityID for PandaWebsite"
}
);
const cloudfrontUserAccessPolicy = new IAM.PolicyStatement();
cloudfrontUserAccessPolicy.addActions("s3:GetObject");
cloudfrontUserAccessPolicy.addPrincipals(cloudFrontOAI.grantPrincipal);
cloudfrontUserAccessPolicy.addResources(websiteBucket.arnForObjects("*"));
websiteBucket.addToResourcePolicy(cloudfrontUserAccessPolicy);
return new CloudFront.Distribution(this, "CloudFrontDistribution", {
domainNames: [websiteDomain],
defaultBehavior: {
origin: new CloudFrontOrigins.S3Origin(websiteBucket, {
// CloudFront can use the OAI to access the files in the S3 bucket
// and serve them to users. Users can’t use a direct URL to the
// S3 bucket to access a file there.
originAccessIdentity: cloudFrontOAI,
}),
compress: true,
allowedMethods: CloudFront.AllowedMethods.ALLOW_GET_HEAD,
cachedMethods: CloudFront.CachedMethods.CACHE_GET_HEAD,
viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
cachePolicy: CloudFront.CachePolicy.CACHING_OPTIMIZED,
},
errorResponses: [
{
httpStatus: 403,
responsePagePath: "/index.html",
responseHttpStatus: 200,
ttl: CDK.Duration.minutes(0),
},
{
httpStatus: 404,
responsePagePath: "/index.html",
responseHttpStatus: 200,
ttl: CDK.Duration.minutes(0),
},
],
priceClass: CloudFront.PriceClass.PRICE_CLASS_ALL,
enabled: true,
certificate: certificateManagerCertificate,
minimumProtocolVersion: CloudFront.SecurityPolicyProtocol.TLS_V1_2_2021,
httpVersion: CloudFront.HttpVersion.HTTP2,
defaultRootObject: "index.html",
enableIpv6: true,
});
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
遵循SándorBakos的评论,我可以解决这个问题。
我更新我的buildspec.yml:
for Pipeline的CDK代码:
Follow Sándor Bakos's comment, I'm able to address the issue.
I update my buildspec.yml with:
CDK Code for pipeline: