Microsoft Workloads on AWS
How to build an automated C# code documentation generator using AWS DevOps
In this blog post, we will show you how to create a documentation solution on the Amazon Web Services (AWS) Cloud that automatically generates and publishes a technical documentation website for a .NET project, based on source code comments, API definitions, and Markdown documents included in the project.
Having a technical documentation website improves developer productivity, and generating the documentation automatically ensures that the site is always up-to-date. Without this automation, developers need to continually update the documentation, creating operational overhead and increasing the risk of documentation errors.
This solution also implements additional best practices for technical documentation, such as cost-effective hosting, a consistent structure, and version history. The documentation is stored in a centralized location with access control and gets updated with every code check-in. It contains consistent structure, including a table of contents, class descriptions and links to inheritance (if any), method descriptions along with signature and return type, and much more. This post describes various AWS services that are used to build the solution, including AWS CodeBuild, Amazon Simple Storage Service (Amazon S3), AWS Lambda, and Amazon CloudFront.
Solution overview
This post uses DocFx and AWS to automatically generate code documentation for a C# project. DocFx is an open-source documentation generator for .NET projects that builds a static HTML website from XML code comments, REST API definition (Swagger) files, and Markdown documents in the project. The solution automatically generates updated documentation during every build, triggered by an event on the AWS CodeCommit repository. The documentation website is then hosted on Amazon S3 and distributed through Amazon CloudFront, as shown in Figure 1 below:
Figure1: Architecture diagram for proposed solution
A developer committing code to the AWS CodeCommit repository initiates a build in AWS CodeBuild. During the build, AWS CodeBuild sets up DocFx in the build environment. After the build completes, AWS CodeBuild runs a Lambda function that checks if the project version already has documentation in Amazon S3.
If the documentation doesn’t exist, AWS CodeBuild runs DocFx to generate the documentation website and uploads the HTML pages to Amazon S3. AWS CodeBuild then runs another Lambda function that updates the Amazon S3 website to include a link to the new documentation. Finally, the Amazon CloudFront distribution synchronizes with the Amazon S3 bucket to serve as the public endpoint for the documentation.
Walkthrough
To deploy the solution, follow these high-level steps:
- Use an AWS CloudFormation template to deploy the AWS infrastructure.
- Configure the AWS Codebuild Target.
- Configure the C# project to support DocFx.
Deploy AWS infrastructure
After launching the AWS CloudFormation stack, it will implement this solution in your AWS account, creating all the necessary AWS resources (including AWS Identity and Access Management (IAM) roles, Amazon S3 buckets, an AWS CodePipeline pipeline, an Amazon CloudFront distribution, and so on).
Follow these steps to deploy the solution using AWS CloudFormation:
- Step 1: Copy the below AWS CloudFormation template:
-
- AWS CodeBuild with a Windows image:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
DocumentationBucketName:
Type: String
Description: "S3 Bucket name for bucket where auto generated code documentation will be stored.
Refer https://docs.thinkwithwp.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules"
LoggingBucketName:
Type: String
Description: "S3 Bucket name for bucket where logs will be stored.
Refer https://docs.thinkwithwp.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules"
ArtifactStoreBucketName:
Type: String
Description: "S3 Bucket name for bucket where codepipeline artifacts will be stored.
Refer https://docs.thinkwithwp.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules"
CodeCommitRepositoryName:
Type: String
Description: "Repository name for codecommit project."
CodeBuildProjectName:
Type: String
Description: "Name of codebuild project."
Resources:
# ===== S3 Buckets ======
LoggingBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref LoggingBucketName
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
LoggingBucketAccessPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref LoggingBucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: S3ServerAccessLogsPolicy
Effect: Allow
Principal:
Service: logging.s3.amazonaws.com
Action:
- 's3:PutObject'
Resource: !Sub arn:aws:s3:::${LoggingBucket}/Logs*
S3DocumentationBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref DocumentationBucketName
WebsiteConfiguration:
IndexDocument: index.html
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
LoggingConfiguration:
DestinationBucketName: !Ref LoggingBucket
S3DocumentationBucketAccessPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref S3DocumentationBucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: 1
Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}
Action:
- 's3:GetObject'
Resource: !Sub arn:aws:s3:::${DocumentationBucketName}/*
S3ArtifactBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref ArtifactStoreBucketName
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
LoggingConfiguration:
DestinationBucketName: !Ref LoggingBucket
S3ArtifactBucketAccessPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref S3ArtifactBucket
PolicyDocument:
Version: 2012-10-17
Id: SSEAndSSLPolicy
Statement:
- Sid: DenyUnEncryptedObjectUploads
Effect: Deny
Principal: '*'
Action: 's3:PutObject'
Resource: !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}/*'
Condition:
StringNotEquals:
's3:x-amz-server-side-encryption': 'aws:kms'
- Sid: DenyInsecureConnections
Effect: Deny
Principal: '*'
Action: 's3:*'
Resource: !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}/*'
Condition:
Bool:
'aws:SecureTransport': 'false'
- Sid: ''
Effect: Allow
Principal:
AWS:
- !GetAtt
- CodeBuildRole
- Arn
- !GetAtt
- CodePipelineRole
- Arn
Action:
- 's3:Get*'
- 's3:Put*'
Resource: !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}/*'
- Sid: ''
Effect: Allow
Principal:
AWS:
- !GetAtt
- CodeBuildRole
- Arn
- !GetAtt
- CodePipelineRole
- Arn
Action: 's3:ListBucket'
Resource: !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}'
# ===== Cloudfront ======
CloudFrontDistribution:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
Origins:
- ConnectionAttempts: 3
ConnectionTimeout: 10
DomainName: !Sub '${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com'
Id: !Sub '${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com'
OriginPath: ''
S3OriginConfig:
OriginAccessIdentity: !Sub >-
origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
OriginGroups:
Quantity: 0
# cache optimized
DefaultCacheBehavior:
AllowedMethods:
- HEAD
- GET
CachedMethods:
- HEAD
- GET
Compress: true
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
SmoothStreaming: false
TargetOriginId: !Sub '${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com'
ViewerProtocolPolicy: redirect-to-https
# no cache
CacheBehaviors:
- AllowedMethods:
- HEAD
- GET
Compress: true
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
PathPattern: /index.html
SmoothStreaming: false
TargetOriginId: !Sub '${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com'
ViewerProtocolPolicy: redirect-to-https
CustomErrorResponses:
- ErrorCode: 403
ResponsePagePath: /error.html
ResponseCode: '403'
ErrorCachingMinTTL: 86400
Comment: "CloudFront distribution for automated code documentation."
PriceClass: PriceClass_All
Enabled: true
ViewerCertificate:
CloudFrontDefaultCertificate: true
Restrictions:
GeoRestriction:
RestrictionType: none
HttpVersion: http2
DefaultRootObject: index.html
IPV6Enabled: true
Logging:
Bucket: !GetAtt
- LoggingBucket
- DomainName
DependsOn:
- CloudFrontOriginAccessIdentity
CloudFrontOriginAccessIdentity:
Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Sub >-
access-identity-${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com
# ===== Lambdas ======
CheckUpdateRequiredOrNotLambda:
Type: 'AWS::Lambda::Function'
Properties:
FunctionName: !Sub '${CodeBuildProjectName}CheckUpdateRequiredOrNot'
Timeout: 300
Handler: index.CheckUpdateRequiredOrNot
ReservedConcurrentExecutions: 1
Role: !GetAtt
- CheckUpdateRequiredOrNotLambdaRole
- Arn
Code:
ZipFile: |-
import boto3
def CheckUpdateRequiredOrNot(event, context):
CheckAndAddErrorHtml(event['Bucket'])
print("Checking " + event['Version'] + " folder present or not in root level in S3 Bucket " + event['Bucket'])
s3 = boto3.client('s3')
listObjectOutput = s3.list_objects(Bucket=event['Bucket'], Delimiter='/')
print(listObjectOutput)
content = listObjectOutput.get('CommonPrefixes', [])
for prefix in content:
if (prefix.get('Prefix').strip('/') == event['Version']):
return False
return True
def CheckAndAddErrorHtml(bucket):
s3 = boto3.client('s3')
errorFilePresent = s3.list_objects(Bucket=bucket, Prefix='error.html').get('Contents', [])
if errorFilePresent:
return
print("Adding error.html file.")
text = "Error Page for 403"
file = open('/tmp/error.html','w')
file.write(text)
file.close()
with open('/tmp/error.html', 'rb') as data:
s3.upload_fileobj(data, bucket, 'error.html', {'ContentType': "text/html"})
Runtime: python3.8
DocumentationUpdateIndexLambda:
Type: 'AWS::Lambda::Function'
Properties:
FunctionName: !Sub '${CodeBuildProjectName}DocumentationUpdateIndex'
Timeout: 300
Handler: index.DocumentationUpdateIndex
ReservedConcurrentExecutions: 1
Role: !GetAtt
- DocumentationUpdateIndexLambdaRole
- Arn
Code:
ZipFile:
Fn::Sub:
- |-
import boto3
from pkg_resources import parse_version
def DocumentationUpdateIndex(event, context):
s3 = boto3.client('s3')
print("Deleting current index.html file.")
s3.delete_object(Bucket=event['Bucket'], Key='index.html')
print("Adding index.html file.")
envVersion = event['Version']
domain = '${CloudfrontDomain}'
latestVersionHtml = " <span>Latest Version: <a href='https://{}/{}/api/index.html'>{}</a></span>".format(domain, envVersion, envVersion)
listObjectOutput = s3.list_objects(Bucket=event['Bucket'], Delimiter='/').get('CommonPrefixes', [])
versions = []
for prefix in listObjectOutput:
if (prefix.get('Prefix').startswith("v")):
versions.append(prefix.get('Prefix').strip('/'))
versions = sorted(versions, key=parse_version, reverse=True)
allVersionHtml = ""
for versionNum in versions:
allVersionHtml += "\n <div class='version'>\n \<span>Version: <a href='https://{}/{}/api/index.html'>{}</a></span>\n </div>".format(domain, versionNum, versionNum)
indexHtml = """
<!DOCTYPE html>
<html>
<head>
<style>
div.latestVersion {{margin:1em; margin-left: 2em; font-size: 1.5em;}}
div.versionList {{margin-left: 3em; overflow: scroll; height:80vh;}}
div.version {{margin:1em;}}
</style>
</head>
<body>
<header>
<span style="font-size: 2em; margin-left: 1em;">Tool Name</span>
</header>
<div class="latestVersion">
{}
</div>
<div class="versionList">
{}
</div>
<footer style="position: absolute; bottom: 0; left: 1em; font-size: 2em;">
Powered by <strong>Team Name</strong>
</footer>
</body>
</html>
""".format(latestVersionHtml, allVersionHtml)
print(indexHtml)
file = open('/tmp/index.html','w')
file.write(indexHtml)
file.close()
with open('/tmp/index.html', 'rb') as data:
s3.upload_fileobj(data, event['Bucket'], 'index.html', {'ContentType': "text/html"})
return True
- {
CloudfrontDomain: { Fn::GetAtt: [CloudFrontDistribution, DomainName]}
}
Runtime: python3.8
# ===== Dev Tools ======
CodeCommitRepository:
Type: 'AWS::CodeCommit::Repository'
Properties:
RepositoryName: !Ref CodeCommitRepositoryName
RepositoryDescription: !Sub 'Code commit repository for ${CodeBuildProjectName} project.'
CodeBuildProject:
Type: 'AWS::CodeBuild::Project'
Properties:
Name: !Ref CodeBuildProjectName
Description: !Sub 'Code build for ${CodeBuildProjectName} project.'
Source:
BuildSpec: buildspec.yml
GitCloneDepth: 1
GitSubmodulesConfig:
FetchSubmodules: false
InsecureSsl: false
Location: !Sub >-
https://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/${CodeCommitRepositoryName}
Type: CODECOMMIT
Artifacts:
Type: NO_ARTIFACTS
Cache:
Type: NO_CACHE
Environment:
ComputeType: BUILD_GENERAL1_MEDIUM
Image: 'aws/codebuild/windows-base:2019-1.0'
ImagePullCredentialsType: CODEBUILD
PrivilegedMode: false
Type: WINDOWS_SERVER_2019_CONTAINER
ServiceRole: !GetAtt
- CodeBuildRole
- Arn
TimeoutInMinutes: 60
QueuedTimeoutInMinutes: 480
EncryptionKey: !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3'
BadgeEnabled: false
LogsConfig:
CloudWatchLogs:
Status: ENABLED
S3Logs:
Status: DISABLED
EncryptionDisabled: false
Visibility: PRIVATE
CodePipelinePipeline:
Type: 'AWS::CodePipeline::Pipeline'
Properties:
Name: !Sub '${CodeBuildProjectName}Pipeline'
RoleArn: !GetAtt
- CodePipelineRole
- Arn
ArtifactStore:
Location: !Ref ArtifactStoreBucketName
Type: S3
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: '1'
Configuration:
BranchName: mainline
OutputArtifactFormat: CODE_ZIP
PollForSourceChanges: 'false'
RepositoryName: !Ref CodeCommitRepositoryName
OutputArtifacts:
- Name: SourceArtifact
Region: !Ref 'AWS::Region'
Namespace: SourceVariables
RunOrder: 1
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: '1'
Configuration:
ProjectName: !Ref CodeBuildProjectName
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
Region: !Ref 'AWS::Region'
Namespace: BuildVariables
RunOrder: 1
# ===== IAM Roles ======
CheckUpdateRequiredOrNotLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Sub '${CodeBuildProjectName}CheckUpdateRequiredOrNotLambdaRole'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service: lambda.amazonaws.com
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyName: !Sub '${CodeBuildProjectName}CheckUpdateRequiredOrNotLambdaPolicy'
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 's3:ListBucket'
- 's3:PutObject'
Effect: Allow
Resource:
- !Sub 'arn:aws:s3:::${S3DocumentationBucket}'
- !Sub 'arn:aws:s3:::${S3DocumentationBucket}/*'
DocumentationUpdateIndexLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Sub '${CodeBuildProjectName}DocumentationUpdateIndexLambdaRole'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service: lambda.amazonaws.com
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyName: !Sub '${CodeBuildProjectName}DocumentationUpdateIndexLambdaPolicy'
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 's3:ListBucket'
- 's3:DeleteObject'
- 's3:PutObject'
Effect: Allow
Resource:
- !Sub 'arn:aws:s3:::${S3DocumentationBucket}'
- !Sub 'arn:aws:s3:::${S3DocumentationBucket}/*'
CodePipelineRole:
Type: 'AWS::IAM::Role'
Properties:
Path: /service-role/
RoleName: !Sub 'AWSCodePipelineServiceRole-${AWS::Region}-${CodeBuildProjectName}Pipeline'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
MaxSessionDuration: 3600
ManagedPolicyArns:
- !Ref CodePipelineManagedPolicy
- !Ref CodeBuildManagedPolicy
CodeBuildRole:
Type: 'AWS::IAM::Role'
Properties:
Path: /service-role/
RoleName: !Sub 'AWSCodeBuildServiceRole-${AWS::Region}-${CodeBuildProjectName}Build'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service: codebuild.amazonaws.com
MaxSessionDuration: 3600
ManagedPolicyArns:
- !Ref CodeBuildManagedPolicy
- 'arn:aws:iam::aws:policy/AWSCodeArtifactReadOnlyAccess'
CodePipelineManagedPolicy:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
Path: /service-role/
ManagedPolicyName: !Sub 'AWSCodePipelineManagedPolicy-${AWS::Region}-${CodeBuildProjectName}Pipeline'
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Resource: !GetAtt
- CodeCommitRepository
- Arn
Action:
- 'codecommit:CancelUploadArchive'
- 'codecommit:GetBranch'
- 'codecommit:GetCommit'
- 'codecommit:GetUploadArchiveStatus'
- 'codecommit:UploadArchive'
- Effect: Allow
Resource: !GetAtt
- CodeBuildProject
- Arn
Action:
- 'codebuild:BatchGetBuilds'
- 'codebuild:StartBuild'
CodeBuildManagedPolicy:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
ManagedPolicyName: !Sub 'AWSCodeBuildManagedPolicy-${AWS::Region}-${CodeBuildProjectName}Build'
Path: /service-role/
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Resource:
- !Sub >-
arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${CheckUpdateRequiredOrNotLambda}
- !Sub >-
arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${DocumentationUpdateIndexLambda}
Action:
- 'lambda:InvokeFunction'
- 'lambda:ListFunctions'
- Effect: Allow
Resource: !Sub 'arn:aws:s3:::${DocumentationBucketName}/*'
Action: 's3:PutObject'
- Effect: Allow
Resource:
- !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}'
Action:
- 's3:PutObject'
- 's3:GetObject'
- 's3:GetObjectVersion'
- 's3:GetBucketAcl'
- 's3:GetBucketLocation'
- Effect: Allow
Resource:
- !Sub >-
arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${CodeBuildProjectName}:*
- !Sub >-
arn:aws:logs:${AWS::Region}:${AWS::AccountId}:/aws/codebuild/${CodeBuildProjectName}:*
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
-
- AWS CodeBuild with a Linux image:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
DocumentationBucketName:
Type: String
Description: "S3 Bucket name for bucket where auto generated code documentation will be stored.
Refer https://docs.thinkwithwp.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules"
LoggingBucketName:
Type: String
Description: "S3 Bucket name for bucket where logs will be stored.
Refer https://docs.thinkwithwp.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules"
ArtifactStoreBucketName:
Type: String
Description: "S3 Bucket name for bucket where codepipeline artifacts will be stored.
Refer https://docs.thinkwithwp.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules"
CodeCommitRepositoryName:
Type: String
Description: "Repository name for codecommit project."
CodeBuildProjectName:
Type: String
Description: "Name of codebuild project."
Resources:
# ===== S3 Buckets ======
LoggingBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref LoggingBucketName
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
LoggingBucketAccessPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref LoggingBucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: S3ServerAccessLogsPolicy
Effect: Allow
Principal:
Service: logging.s3.amazonaws.com
Action:
- 's3:PutObject'
Resource: !Sub arn:aws:s3:::${LoggingBucket}/Logs*
S3DocumentationBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref DocumentationBucketName
WebsiteConfiguration:
IndexDocument: index.html
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
LoggingConfiguration:
DestinationBucketName: !Ref LoggingBucket
S3DocumentationBucketAccessPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref S3DocumentationBucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: 1
Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}
Action:
- 's3:GetObject'
Resource: !Sub arn:aws:s3:::${DocumentationBucketName}/*
S3ArtifactBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref ArtifactStoreBucketName
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
LoggingConfiguration:
DestinationBucketName: !Ref LoggingBucket
S3ArtifactBucketAccessPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref S3ArtifactBucket
PolicyDocument:
Version: 2012-10-17
Id: SSEAndSSLPolicy
Statement:
- Sid: DenyUnEncryptedObjectUploads
Effect: Deny
Principal: '*'
Action: 's3:PutObject'
Resource: !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}/*'
Condition:
StringNotEquals:
's3:x-amz-server-side-encryption': 'aws:kms'
- Sid: DenyInsecureConnections
Effect: Deny
Principal: '*'
Action: 's3:*'
Resource: !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}/*'
Condition:
Bool:
'aws:SecureTransport': 'false'
- Sid: ''
Effect: Allow
Principal:
AWS:
- !GetAtt
- CodeBuildRole
- Arn
- !GetAtt
- CodePipelineRole
- Arn
Action:
- 's3:Get*'
- 's3:Put*'
Resource: !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}/*'
- Sid: ''
Effect: Allow
Principal:
AWS:
- !GetAtt
- CodeBuildRole
- Arn
- !GetAtt
- CodePipelineRole
- Arn
Action: 's3:ListBucket'
Resource: !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}'
# ===== Cloudfront ======
CloudFrontDistribution:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
Origins:
- ConnectionAttempts: 3
ConnectionTimeout: 10
DomainName: !Sub '${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com'
Id: !Sub '${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com'
OriginPath: ''
S3OriginConfig:
OriginAccessIdentity: !Sub >-
origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
OriginGroups:
Quantity: 0
# cache optimized
DefaultCacheBehavior:
AllowedMethods:
- HEAD
- GET
CachedMethods:
- HEAD
- GET
Compress: true
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
SmoothStreaming: false
TargetOriginId: !Sub '${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com'
ViewerProtocolPolicy: redirect-to-https
# no cache
CacheBehaviors:
- AllowedMethods:
- HEAD
- GET
Compress: true
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
PathPattern: /index.html
SmoothStreaming: false
TargetOriginId: !Sub '${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com'
ViewerProtocolPolicy: redirect-to-https
CustomErrorResponses:
- ErrorCode: 403
ResponsePagePath: /error.html
ResponseCode: '403'
ErrorCachingMinTTL: 86400
Comment: "CloudFront distribution for automated code documentation."
PriceClass: PriceClass_All
Enabled: true
ViewerCertificate:
CloudFrontDefaultCertificate: true
Restrictions:
GeoRestriction:
RestrictionType: none
HttpVersion: http2
DefaultRootObject: index.html
IPV6Enabled: true
Logging:
Bucket: !GetAtt
- LoggingBucket
- DomainName
DependsOn:
- CloudFrontOriginAccessIdentity
CloudFrontOriginAccessIdentity:
Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Sub >-
access-identity-${DocumentationBucketName}.s3.${AWS::Region}.amazonaws.com
# ===== Lambdas ======
CheckUpdateRequiredOrNotLambda:
Type: 'AWS::Lambda::Function'
Properties:
FunctionName: !Sub '${CodeBuildProjectName}CheckUpdateRequiredOrNot'
Timeout: 300
Handler: index.CheckUpdateRequiredOrNot
ReservedConcurrentExecutions: 1
Role: !GetAtt
- CheckUpdateRequiredOrNotLambdaRole
- Arn
Code:
ZipFile: |-
import boto3
def CheckUpdateRequiredOrNot(event, context):
CheckAndAddErrorHtml(event['Bucket'])
print("Checking " + event['Version'] + " folder present or not in root level in S3 Bucket " + event['Bucket'])
s3 = boto3.client('s3')
listObjectOutput = s3.list_objects(Bucket=event['Bucket'], Delimiter='/')
print(listObjectOutput)
content = listObjectOutput.get('CommonPrefixes', [])
for prefix in content:
if (prefix.get('Prefix').strip('/') == event['Version']):
return False
return True
def CheckAndAddErrorHtml(bucket):
s3 = boto3.client('s3')
errorFilePresent = s3.list_objects(Bucket=bucket, Prefix='error.html').get('Contents', [])
if errorFilePresent:
return
print("Adding error.html file.")
text = "Error Page for 403"
file = open('/tmp/error.html','w')
file.write(text)
file.close()
with open('/tmp/error.html', 'rb') as data:
s3.upload_fileobj(data, bucket, 'error.html', {'ContentType': "text/html"})
Runtime: python3.8
DocumentationUpdateIndexLambda:
Type: 'AWS::Lambda::Function'
Properties:
FunctionName: !Sub '${CodeBuildProjectName}DocumentationUpdateIndex'
Timeout: 300
Handler: index.DocumentationUpdateIndex
ReservedConcurrentExecutions: 1
Role: !GetAtt
- DocumentationUpdateIndexLambdaRole
- Arn
Code:
ZipFile:
Fn::Sub:
- |-
import boto3
from pkg_resources import parse_version
def DocumentationUpdateIndex(event, context):
s3 = boto3.client('s3')
print("Adding index.html file.")
envVersion = event['Version']
domain = '${CloudfrontDomain}'
latestVersionHtml = " <span>Latest Version: <a href='https://{}/{}/index.html'>{}</a></span>".format(domain, envVersion, envVersion)
listObjectOutput = s3.list_objects(Bucket=event['Bucket'], Delimiter='/').get('CommonPrefixes', [])
versions = []
for prefix in listObjectOutput:
if (prefix.get('Prefix').startswith("v")):
versions.append(prefix.get('Prefix').strip('/'))
versions = sorted(versions, key=parse_version, reverse=True)
allVersionHtml = ""
for versionNum in versions:
allVersionHtml += "\n <div class='version'>\n \<span>Version: <a href='https://{}/{}/index.html'>{}</a></span>\n </div>".format(domain, versionNum, versionNum)
indexHtml = """
<!DOCTYPE html>
<html>
<head>
<style>
div.latestVersion {{margin:1em; margin-left: 2em; font-size: 1.5em;}}
div.versionList {{margin-left: 3em; overflow: scroll; height:80vh;}}
div.version {{margin:1em;}}
</style>
</head>
<body>
<header>
<span style="font-size: 2em; margin-left: 1em;">Tool Name</span>
</header>
<div class="latestVersion">
{}
</div>
<div class="versionList">
{}
</div>
<footer style="position: absolute; bottom: 0; left: 1em; font-size: 2em;">
Powered by <strong>Team Name</strong>
</footer>
</body>
</html>
""".format(latestVersionHtml, allVersionHtml)
print(indexHtml)
file = open('/tmp/index.html','w')
file.write(indexHtml)
file.close()
with open('/tmp/index.html', 'rb') as data:
s3.upload_fileobj(data, event['Bucket'], 'index.html', {'ContentType': "text/html"})
return True
- {
CloudfrontDomain: { Fn::GetAtt: [CloudFrontDistribution, DomainName]}
}
Runtime: python3.8
# ===== Dev Tools ======
CodeCommitRepository:
Type: 'AWS::CodeCommit::Repository'
Properties:
RepositoryName: !Ref CodeCommitRepositoryName
RepositoryDescription: !Sub 'Code commit repository for ${CodeBuildProjectName} project.'
CodeBuildProject:
Type: 'AWS::CodeBuild::Project'
Properties:
Name: !Ref CodeBuildProjectName
Description: !Sub 'Code build for ${CodeBuildProjectName} project.'
Source:
BuildSpec: buildspec.yml
GitCloneDepth: 1
GitSubmodulesConfig:
FetchSubmodules: false
InsecureSsl: false
Location: !Sub >-
https://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/${CodeCommitRepositoryName}
Type: CODECOMMIT
Artifacts:
Type: NO_ARTIFACTS
Cache:
Type: NO_CACHE
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:6.0
Type: LINUX_CONTAINER
ServiceRole: !GetAtt
- CodeBuildRole
- Arn
TimeoutInMinutes: 60
QueuedTimeoutInMinutes: 480
EncryptionKey: !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3'
BadgeEnabled: false
LogsConfig:
CloudWatchLogs:
Status: ENABLED
S3Logs:
Status: DISABLED
EncryptionDisabled: false
Visibility: PRIVATE
CodePipelinePipeline:
Type: 'AWS::CodePipeline::Pipeline'
Properties:
Name: !Sub '${CodeBuildProjectName}Pipeline'
RoleArn: !GetAtt
- CodePipelineRole
- Arn
ArtifactStore:
Location: !Ref ArtifactStoreBucketName
Type: S3
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: '1'
Configuration:
BranchName: mainline
OutputArtifactFormat: CODE_ZIP
PollForSourceChanges: 'false'
RepositoryName: !Ref CodeCommitRepositoryName
OutputArtifacts:
- Name: SourceArtifact
Region: !Ref 'AWS::Region'
Namespace: SourceVariables
RunOrder: 1
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: '1'
Configuration:
ProjectName: !Ref CodeBuildProjectName
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
Region: !Ref 'AWS::Region'
Namespace: BuildVariables
RunOrder: 1
# ===== IAM Roles ======
CheckUpdateRequiredOrNotLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Sub '${CodeBuildProjectName}CheckUpdateRequiredOrNotLambdaRole'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service: lambda.amazonaws.com
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyName: !Sub '${CodeBuildProjectName}CheckUpdateRequiredOrNotLambdaPolicy'
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 's3:ListBucket'
- 's3:PutObject'
Effect: Allow
Resource:
- !Sub 'arn:aws:s3:::${S3DocumentationBucket}'
- !Sub 'arn:aws:s3:::${S3DocumentationBucket}/*'
DocumentationUpdateIndexLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Sub '${CodeBuildProjectName}DocumentationUpdateIndexLambdaRole'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service: lambda.amazonaws.com
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyName: !Sub '${CodeBuildProjectName}DocumentationUpdateIndexLambdaPolicy'
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 's3:ListBucket'
- 's3:DeleteObject'
- 's3:PutObject'
Effect: Allow
Resource:
- !Sub 'arn:aws:s3:::${S3DocumentationBucket}'
- !Sub 'arn:aws:s3:::${S3DocumentationBucket}/*'
CodePipelineRole:
Type: 'AWS::IAM::Role'
Properties:
Path: /service-role/
RoleName: !Sub 'AWSCodePipelineServiceRole-${AWS::Region}-${CodeBuildProjectName}Pipeline'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
MaxSessionDuration: 3600
ManagedPolicyArns:
- !Ref CodePipelineManagedPolicy
- !Ref CodeBuildManagedPolicy
CodeBuildRole:
Type: 'AWS::IAM::Role'
Properties:
Path: /service-role/
RoleName: !Sub 'AWSCodeBuildServiceRole-${AWS::Region}-${CodeBuildProjectName}Build'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service: codebuild.amazonaws.com
MaxSessionDuration: 3600
ManagedPolicyArns:
- !Ref CodeBuildManagedPolicy
- 'arn:aws:iam::aws:policy/AWSCodeArtifactReadOnlyAccess'
CodePipelineManagedPolicy:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
Path: /service-role/
ManagedPolicyName: !Sub 'AWSCodePipelineManagedPolicy-${AWS::Region}-${CodeBuildProjectName}Pipeline'
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Resource: !GetAtt
- CodeCommitRepository
- Arn
Action:
- 'codecommit:CancelUploadArchive'
- 'codecommit:GetBranch'
- 'codecommit:GetCommit'
- 'codecommit:GetUploadArchiveStatus'
- 'codecommit:UploadArchive'
- Effect: Allow
Resource: !GetAtt
- CodeBuildProject
- Arn
Action:
- 'codebuild:BatchGetBuilds'
- 'codebuild:StartBuild'
CodeBuildManagedPolicy:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
ManagedPolicyName: !Sub 'AWSCodeBuildManagedPolicy-${AWS::Region}-${CodeBuildProjectName}Build'
Path: /service-role/
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Resource:
- !Sub >-
arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${CheckUpdateRequiredOrNotLambda}
- !Sub >-
arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${DocumentationUpdateIndexLambda}
Action:
- 'lambda:InvokeFunction'
- 'lambda:ListFunctions'
- Effect: Allow
Resource: !Sub 'arn:aws:s3:::${DocumentationBucketName}/*'
Action: 's3:PutObject'
- Effect: Allow
Resource:
- !Sub 'arn:aws:s3:::${ArtifactStoreBucketName}'
Action:
- 's3:PutObject'
- 's3:GetObject'
- 's3:GetObjectVersion'
- 's3:GetBucketAcl'
- 's3:GetBucketLocation'
- Effect: Allow
Resource:
- !Sub >-
arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${CodeBuildProjectName}:*
- !Sub >-
arn:aws:logs:${AWS::Region}:${AWS::AccountId}:/aws/codebuild/${CodeBuildProjectName}:*
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
- Step 2: Sign in to your AWS account and navigate to the CloudFormation console.
- Step 3: Choose Create stack
- Step 4: On the Create stack page, for Prepare template, choose Template is ready.
- Step 5: For Template source, choose the option Upload the template file. Click Choose file option and upload the AWS CloudFormation template file created by copying content from step 1.
- Step 6: Choose Next. (Alternatively, you can navigate to this link and the stack will use an AWS CloudFormation template stored in Amazon S3.)
Figure 2: Create stack on AWS CloudFormation.
- Step 7: For Stack name, enter a name for the stack (such as DocumentGeneratorStack). For ArtifactStoreBucketName, enter a name for the Amazon S3 bucket where the AWS CodePipeline artifacts will be stored. For CodeBuildProjectName, enter a name for the AWS CodeBuild project that the solution will create. For CodeCommitRepositoryName, enter a name for the AWS CodeCommit repository that the solution will create to hold your C# project. For DocumentationBucketName, enter a name for the Amazon S3 bucket where the generated code documentation will be stored. For LoggingBucketName, enter a name for the Amazon S3 bucket where the solution logs will be stored.
Figure 3: Specify stack details on AWS CloudFormation.
- Step 8: Select I acknowledge that AWS CloudFormation might create IAM resources with custom names, then select Submit.
Figure4: Acknowledgment checkbox for IAM resources creation.
Configuring the AWS CodeBuild target
To configure AWS CodeBuild, add a buildspec file to the C# project.
You can use one of these example buildspec files:
For a C# project building on Windows, use below buildspec.yml:
version: 0.2
env:
variables:
# Setting the variables for version, lambdas and bucket where build documentation will be stored.
VERSION : <Version>
DOCUMENTATION_S3_BUCKET : <Documentation S3 Bucket Name>
CHECKUPDATEREQUIREDORNOT_LAMBDA: <CheckUpdateRequiredOrNot lambda>
DOCUMENTATIONUPDATEINDEX_LAMBDA: <DocumentationUpdateIndex lambda>
phases:
install:
commands:
# Download and install dotnet latest version as codebuild is not compatible with dotnet (>5.0)
- Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile 'dotnet-install.ps1'
- ./dotnet-install.ps1 -Channel 'STS' -InstallDir 'C:\Program Files\dotnet'
# Install docfx using chocolatey
- choco install docfx -y
build:
commands:
# Build project
- dotnet build
post_build:
commands:
# Set version and bucket in json for lambda input
- echo $Env:VERSION
- $jObj = @{Version="v"+ $Env:VERSION; Bucket=$Env:DOCUMENTATION_S3_BUCKET}
- $jObj | ConvertTo-Json | Set-Content jsonPayload.json
# Invoke lambda to check if project version document is present in s3
- aws lambda invoke --function-name $Env:CHECKUPDATEREQUIREDORNOT_LAMBDA --cli-binary-format raw-in-base64-out --payload file://jsonPayload.json lambdaresponse.json
- $outputArtifactNeedToUploadToS3 = cat lambdaresponse.json
- echo $outputArtifactNeedToUploadToS3
- $folderName = "v" + $Env:VERSION
# Based on result of check, build documentation using docfx
- if ($outputArtifactNeedToUploadToS3 -eq "true") { docfx -t statictoc }
- if ($outputArtifactNeedToUploadToS3 -eq "true") { ls _site }
# Copy all created artifacts/files for documentation to s3 bucket provided
- if ($outputArtifactNeedToUploadToS3 -eq "true") { aws s3 cp _site\ s3://$Env:DOCUMENTATION_S3_BUCKET/$folderName/ --sse --recursive }
# Invoke lambda to update index.html file with new project version
- if ($outputArtifactNeedToUploadToS3 -eq "true") { aws lambda invoke --function-name $Env:DOCUMENTATIONUPDATEINDEX_LAMBDA --cli-binary-format raw-in-base64-out --payload file://jsonPayload.json updateIndexlambdaresponse.json }
For a C# project building on Linux, use below buildspec.yml:
version: 0.2
env:
variables:
# Setting the variables for version, lambdas and bucket where build documentation will be stored.
VERSION : <Version>
DOCUMENTATION_S3_BUCKET : <Documentation S3 Bucket Name>
CHECKUPDATEREQUIREDORNOT_LAMBDA: <CheckUpdateRequiredOrNot lambda>
DOCUMENTATIONUPDATEINDEX_LAMBDA: <DocumentationUpdateIndex lambda>
phases:
install:
commands:
# Download and install dotnet latest version as codebuild is not compatible with dotnet (>5.0)
- export PATH="$PATH:/root/.dotnet/tools"
- curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel STS
# Install docfx
- dotnet tool install -g docfx
build:
commands:
# Build project
- dotnet build
post_build:
commands:
# Set version and bucket in json for lambda input
- echo $VERSION
- jObj={\"Version\":\"v$VERSION\"\,\"Bucket\":\"$DOCUMENTATION_S3_BUCKET\"}
- echo $jObj > jsonPayload.json
# Invoke lambda to check if project version document is present in s3
- aws lambda invoke --function-name $CHECKUPDATEREQUIREDORNOT_LAMBDA --cli-binary-format raw-in-base64-out --payload file://jsonPayload.json lambdaresponse.json
- outputArtifactNeedToUploadToS3=`cat lambdaresponse.json`
- echo $outputArtifactNeedToUploadToS3
- folderName="v"$VERSION
# Based on result of check, build documentation using docfx
- if [ $outputArtifactNeedToUploadToS3 = "true" ]; then docfx -t statictoc; fi
- if [ $outputArtifactNeedToUploadToS3 = "true" ]; then ls _site; fi
# Copy all created artifacts/files for documentation to s3 bucket provided
- if [ $outputArtifactNeedToUploadToS3 = "true" ]; then aws s3 cp _site s3://$DOCUMENTATION_S3_BUCKET/$folderName/ --sse --recursive; fi
# Invoke lambda to update index.html file with new project version
- if [ $outputArtifactNeedToUploadToS3 = "true" ]; then aws lambda invoke --function-name $DOCUMENTATIONUPDATEINDEX_LAMBDA --cli-binary-format raw-in-base64-out --payload file://jsonPayload.json updateIndexlambdaresponse.json; fi
Configuring the C# project
Update the C# project with the following steps to enable automated documentation generation with DocFx:
- Add the DocFx configuration file.
- Add Markdown index files that will be the section home pages for the documentation website.
- Add table of contents (TOC) files so DocFx will understand the project’s folder structure.
For the DocFX configuration file, use below DocFx.json. (See using DocFx for documentation of each setting.)
{
"metadata": [
{
"src": [
{
"files": [
"**/*.cs"
],
"src": ".."
}
],
"dest": "api",
"disableGitFeatures": false,
"disableDefaultFilter": false
}
],
"build": {
"content": [
{
"files": [
"api/**.yml",
"api/index.md"
]
},
{
"files": [
"articles/**.md",
"articles/**/toc.yml",
"toc.yml",
"*.md"
]
}
],
"resource": [
{
"files": [
"images/**"
]
}
],
"overwrite": [
{
"files": [
"apidoc/**.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
"dest": "_site",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [
"statictoc"
],
"postProcessors": [],
"markdownEngineName": "markdig",
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false
}
}
Add this file to the base folder of the C# project, with the name DocFx.json.
Now, create an api folder in the C# project if it doesn’t exist. You will need to create index and TOC files for the base folder of the project and the api folder.
In the base folder of the project, create a file named index.md. Populate the file with below content:
## Can add version related details like release logs here in markdown format
Next, create a file named toc.yml. Populate the file with below content:
- name: Home
href: index.md
- name: Articles
href: articles/
- name: Documentation
href: api/
homepage: api/index.md
In the api folder, create a file named index.md. Populate the file with below content:
## Can add documentation related details here in markdown format
Next, create a file named toc.yml (in the api folder). Populate the file with below content:
- name: Home
href: index.md
- name: Articles
href: articles/
- name: Documentation
href: api/
homepage: api/index.md
Now, push your C# project to the AWS CodeCommit repository using the main branch (see the AWS CodeCommit user guide for details).
Note: AWS restricts AWS CodeCommit repositories to authorized users. You will need to add a policy allowing read/write access to the created repository for your policy IAM user.
The solution configures the AWS CodePipeline project to monitor an AWS CodeCommit branch named main.
Every code push event in the AWSCodeCommit repository triggers the AWS CodePipeline to build and update the static website. The link to open the documentation website can be found on the Amazon CloudFront page in the AWS Management Console.
Cleaning up
To avoid incurring future charges on your AWS account, you will need to delete the resources created by this solution. You can delete all resources by deleting the Amazon CloudFormation stack. For any resources that are changed after deploying the stack (for example, Amazon S3 buckets), you will need to empty them before deleting the stack.
Conclusion
In this blog post, we have discussed how to create an automated C# code documentation generation system. You can use this system to generate and host real-time, encrypted documentation, with access restricted to a relevant audience.
AWS can help you assess how your company can get the most out of cloud. Join the millions of AWS customers that trust us to migrate and modernize their most important applications in the cloud. To learn more on modernizing Windows Server or SQL Server, visit Windows on AWS. Contact us to start your migration journey today.