Lab complete!
Now that you have completed this lab, make sure to update your Well-Architected review if you have implemented these changes in your workload.
Click here to access the Well-Architected Tool
The lab uses AWS CLI to perform all of the tasks, but you can also use the AWS SDK for Python (Boto3) to perform the same steps. As a reference, you can download the code LabExample.py which will perform all of steps from the lab in a single python file. This file assumes you have already setup your AWS credential file, and uses the default profile for all interactions.
The code has been broken up into functions which accept various parameters, so you can pull those out and place them into integration points in your environment. There is error checking for most of the various API calls, but the code should not be considered production ready. Please review before implementing in your environment.
#!/usr/bin/env python3
# This is a simple python app for use with the Well-Architected labs
# This will simulate all of the steps in the 200-level lab on using the
# Well-Architected API calls
#
# This code is only for use in Well-Architected labs
# *** NOT FOR PRODUCTION USE ***
#
#
# Licensed under the Apache 2.0 and MITnoAttr License.
#
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at
# https://aws.amazon.com/apache2.0/
import botocore
import boto3
import json
import datetime
import logging
import jmespath
import base64
from pkg_resources import packaging
__author__ = "Eric Pullen"
__email__ = "eppullen@amazon.com"
__copyright__ = "Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved."
__credits__ = ["Eric Pullen"]
# Default region listed here
REGION_NAME = "us-east-1"
blankjson = {}
response = ""
# Setup Logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
)
logger = logging.getLogger()
logging.getLogger('boto3').setLevel(logging.CRITICAL)
logging.getLogger('botocore').setLevel(logging.CRITICAL)
logging.getLogger('s3transfer').setLevel(logging.CRITICAL)
logging.getLogger('urllib3').setLevel(logging.CRITICAL)
# Helper class to convert a datetime item to JSON.
class DateTimeEncoder(json.JSONEncoder):
def default(self, z):
if isinstance(z, datetime.datetime):
return (str(z))
else:
return super().default(z)
def CreateNewWorkload(
waclient,
workloadName,
description,
reviewOwner,
environment,
awsRegions,
lenses
):
# Create your workload
try:
waclient.create_workload(
WorkloadName=workloadName,
Description=description,
ReviewOwner=reviewOwner,
Environment=environment,
AwsRegions=awsRegions,
Lenses=lenses
)
except waclient.exceptions.ConflictException as e:
workloadId = FindWorkload(waclient,workloadName)
logger.error("ERROR - The workload name %s already exists as workloadId %s" % (workloadName, workloadId))
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
def FindWorkload(
waclient,
workloadName
):
# Finding your WorkloadId
try:
response=waclient.list_workloads(
WorkloadNamePrefix=workloadName
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print("Full JSON:",json.dumps(response['WorkloadSummaries'], cls=DateTimeEncoder))
workloadId = response['WorkloadSummaries'][0]['WorkloadId']
# print("WorkloadId",workloadId)
return workloadId
def DeleteWorkload(
waclient,
workloadId
):
# Delete the WorkloadId
try:
response=waclient.delete_workload(
WorkloadId=workloadId
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
def GetWorkload(
waclient,
workloadId
):
# Get the WorkloadId
try:
response=waclient.get_workload(
WorkloadId=workloadId
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print("Full JSON:",json.dumps(response['Workload'], cls=DateTimeEncoder))
workload = response['Workload']
# print("WorkloadId",workloadId)
return workload
def disassociateLens(
waclient,
workloadId,
lens
):
# Disassociate the lens from the WorkloadId
try:
response=waclient.disassociate_lenses(
WorkloadId=workloadId,
LensAliases=lens
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
def associateLens(
waclient,
workloadId,
lens
):
# Associate the lens from the WorkloadId
try:
response=waclient.associate_lenses(
WorkloadId=workloadId,
LensAliases=lens
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
def listLens(
waclient
):
# List all lenses currently available
try:
response=waclient.list_lenses()
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print(json.dumps(response))
lenses = jmespath.search("LensSummaries[*].LensAlias", response)
return lenses
def findQuestionId(
waclient,
workloadId,
lensAlias,
pillarId,
questionTitle
):
# Find a questionID using the questionTitle
try:
response=waclient.list_answers(
WorkloadId=workloadId,
LensAlias=lensAlias,
PillarId=pillarId
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
answers = response['AnswerSummaries']
while "NextToken" in response:
response = waclient.list_answers(WorkloadId=workloadId,LensAlias=lensAlias,PillarId=pillarId,NextToken=response["NextToken"])
answers.extend(response["AnswerSummaries"])
jmesquery = "[?starts_with(QuestionTitle, `"+questionTitle+"`) == `true`].QuestionId"
questionId = jmespath.search(jmesquery, answers)
return questionId[0]
def findChoiceId(
waclient,
workloadId,
lensAlias,
questionId,
choiceTitle,
):
# Find a choiceId using the choiceTitle
try:
response=waclient.get_answer(
WorkloadId=workloadId,
LensAlias=lensAlias,
QuestionId=questionId
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
jmesquery = "Answer.Choices[?starts_with(Title, `"+choiceTitle+"`) == `true`].ChoiceId"
choiceId = jmespath.search(jmesquery, response)
return choiceId[0]
def getAnswersForQuestion(
waclient,
workloadId,
lensAlias,
questionId
):
# Find a answer for a questionId
try:
response=waclient.get_answer(
WorkloadId=workloadId,
LensAlias=lensAlias,
QuestionId=questionId
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print(json.dumps(response))
jmesquery = "Answer.SelectedChoices"
answers = jmespath.search(jmesquery, response)
# print(answers)
return answers
def updateAnswersForQuestion(
waclient,
workloadId,
lensAlias,
questionId,
selectedChoices,
notes
):
# Update a answer to a question
try:
response=waclient.update_answer(
WorkloadId=workloadId,
LensAlias=lensAlias,
QuestionId=questionId,
SelectedChoices=selectedChoices,
Notes=notes
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print(json.dumps(response))
jmesquery = "Answer.SelectedChoices"
answers = jmespath.search(jmesquery, response)
return answers
def listMilestones(
waclient,
workloadId
):
# Find a milestone for a workloadId
try:
response=waclient.list_milestones(
WorkloadId=workloadId,
MaxResults=50 # Need to check why I am having to pass this parameter
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print("Full JSON:",json.dumps(response['MilestoneSummaries'], cls=DateTimeEncoder))
milestoneNumber = response['MilestoneSummaries']
return milestoneNumber
def createMilestone(
waclient,
workloadId,
milestoneName
):
# Create a new milestone with milestoneName
try:
response=waclient.create_milestone(
WorkloadId=workloadId,
MilestoneName=milestoneName
)
except waclient.exceptions.ConflictException as e:
milestones = listMilestones(waclient,workloadId)
jmesquery = "[?starts_with(MilestoneName,`"+milestoneName+"`) == `true`].MilestoneNumber"
milestoneNumber = jmespath.search(jmesquery,milestones)
logger.error("ERROR - The milestone name %s already exists as milestone %s" % (milestoneName, milestoneNumber))
return milestoneNumber[0]
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print("Full JSON:",json.dumps(response['MilestoneSummaries'], cls=DateTimeEncoder))
milestoneNumber = response['MilestoneNumber']
return milestoneNumber
def getMilestone(
waclient,
workloadId,
milestoneNumber
):
# Use get_milestone to return the milestone structure
try:
response=waclient.get_milestone(
WorkloadId=workloadId,
MilestoneNumber=milestoneNumber
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print("Full JSON:",json.dumps(response['Milestone'], cls=DateTimeEncoder))
milestoneResponse = response['Milestone']
return milestoneResponse
def getMilestoneRiskCounts(
waclient,
workloadId,
milestoneNumber
):
# Return just the RiskCount for a particular milestoneNumber
milestone = getMilestone(waclient,workloadId,milestoneNumber)
# print("Full JSON:",json.dumps(milestone['Workload']['RiskCounts'], cls=DateTimeEncoder))
milestoneRiskCounts = milestone['Workload']['RiskCounts']
return milestoneRiskCounts
def listAllAnswers(
waclient,
workloadId,
lensAlias,
milestoneNumber=""
):
# Get a list of all answers
try:
if milestoneNumber:
response=waclient.list_answers(
WorkloadId=workloadId,
LensAlias=lensAlias,
MilestoneNumber=milestoneNumber
)
else:
response=waclient.list_answers(
WorkloadId=workloadId,
LensAlias=lensAlias
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
answers = response['AnswerSummaries']
while "NextToken" in response:
if milestoneNumber:
response = waclient.list_answers(WorkloadId=workloadId,LensAlias=lensAlias,MilestoneNumber=milestoneNumber,NextToken=response["NextToken"])
else:
response = waclient.list_answers(WorkloadId=workloadId,LensAlias=lensAlias,NextToken=response["NextToken"])
answers.extend(response["AnswerSummaries"])
# print("Full JSON:",json.dumps(answers, cls=DateTimeEncoder))
return answers
def getLensReview(
waclient,
workloadId,
lensAlias,
milestoneNumber=""
):
# Use get_lens_review to return the lens review structure
try:
if milestoneNumber:
response=waclient.get_lens_review(
WorkloadId=workloadId,
LensAlias=lensAlias,
MilestoneNumber=milestoneNumber
)
else:
response=waclient.get_lens_review(
WorkloadId=workloadId,
LensAlias=lensAlias
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print("Full JSON:",json.dumps(response['LensReview'], cls=DateTimeEncoder))
lensReview = response['LensReview']
return lensReview
def getLensReviewPDFReport(
waclient,
workloadId,
lensAlias,
milestoneNumber=""
):
# Use get_lens_review_report to return the lens review PDF in base64 structure
try:
if milestoneNumber:
response=waclient.get_lens_review_report(
WorkloadId=workloadId,
LensAlias=lensAlias,
MilestoneNumber=milestoneNumber
)
else:
response=waclient.get_lens_review_report(
WorkloadId=workloadId,
LensAlias=lensAlias
)
except botocore.exceptions.ParamValidationError as e:
logger.error("ERROR - Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
logger.error("ERROR - Unexpected error: %s" % e)
# print("Full JSON:",json.dumps(response['LensReviewReport']['Base64String'], cls=DateTimeEncoder))
lensReviewPDF = response['LensReviewReport']['Base64String']
return lensReviewPDF
def main():
boto3_min_version = "1.16.38"
# Verify if the version of Boto3 we are running has the wellarchitected APIs included
if (packaging.version.parse(boto3.__version__) < packaging.version.parse(boto3_min_version)):
logger.error("Your Boto3 version (%s) is less than %s. You must ugprade to run this script (pip3 upgrade boto3)" % (boto3.__version__, boto3_min_version))
exit()
# STEP 1 - Configure environment
logger.info("1 - Starting Boto %s Session" % boto3.__version__)
# Create a new boto3 session
SESSION = boto3.session.Session()
# Initiate the well-architected session using the region defined above
WACLIENT = SESSION.client(
service_name='wellarchitected',
region_name=REGION_NAME,
)
WORKLOADNAME = 'WA Lab Test Workload'
DESCRIPTION = 'Test Workload for WA Lab'
REVIEWOWNER = 'WA Python Script'
ENVIRONMENT= 'PRODUCTION'
AWSREGIONS = [REGION_NAME]
LENSES = ['wellarchitected', 'serverless']
# STEP 2 - Creating a workload
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/2_create_workload/
logger.info("2 - Creating a new workload")
CreateNewWorkload(WACLIENT,WORKLOADNAME,DESCRIPTION,REVIEWOWNER,ENVIRONMENT,AWSREGIONS,LENSES)
logger.info("2 - Finding your WorkloadId")
workloadId = FindWorkload(WACLIENT,WORKLOADNAME)
logger.info("New workload created with id %s" % workloadId)
logger.info("2 - Using WorkloadId to remove and add lenses")
listOfLenses = listLens(WACLIENT)
logger.info("Lenses currently available: %s" % listOfLenses)
workloadJson = GetWorkload(WACLIENT,workloadId)
logger.info("Workload ID '%s' has lenses '%s'" % (workloadId, workloadJson['Lenses']))
logger.info("Removing the serverless lens")
disassociateLens(WACLIENT,workloadId,['serverless'])
workloadJson = GetWorkload(WACLIENT,workloadId)
logger.info("Now workload ID '%s' has lenses '%s'" % (workloadId, workloadJson['Lenses']))
logger.info("Adding serverless lens back into the workload")
associateLens(WACLIENT,workloadId,['serverless'])
workloadJson = GetWorkload(WACLIENT,workloadId)
logger.info("Now workload ID '%s' has lenses '%s'" % (workloadId, workloadJson['Lenses']))
# STEP 3 - Performing a review
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/3_perform_review/
logger.info("3 - Performing a review")
logger.info("3 - STEP1 - Find the QuestionId and ChoiceID for a particular pillar question and best practice")
questionSearch = "How do you reduce defects, ease remediation, and improve flow into production"
questionId = findQuestionId(WACLIENT,workloadId,'wellarchitected','operationalExcellence',questionSearch)
logger.info("Found QuestionID of '%s' for the question text of '%s'" % (questionId, questionSearch))
choiceSet = []
choiceSet.append(findChoiceId(WACLIENT,workloadId,'wellarchitected',questionId,"Use version control"))
logger.info("Found choiceId of '%s' for the choice text of 'Use version control'" % choiceSet)
choiceSet.append(findChoiceId(WACLIENT,workloadId,'wellarchitected',questionId,"Use configuration management systems"))
choiceSet.append(findChoiceId(WACLIENT,workloadId,'wellarchitected',questionId,"Use build and deployment management systems"))
choiceSet.append(findChoiceId(WACLIENT,workloadId,'wellarchitected',questionId,"Perform patch management"))
choiceSet.append(findChoiceId(WACLIENT,workloadId,'wellarchitected',questionId,"Use multiple environments"))
logger.info("All choices we will select for questionId of %s is %s" % (questionId, choiceSet))
logger.info("3 - STEP2 - Use the QuestionID and ChoiceID to update the answer in well-architected review")
answers = getAnswersForQuestion(WACLIENT,workloadId,'wellarchitected',questionId)
logger.info("Current answer for questionId '%s' is '%s'" % (questionId, answers))
logger.info("Adding answers found in choices above (%s)" % choiceSet)
updateAnswersForQuestion(WACLIENT,workloadId,'wellarchitected',questionId,choiceSet,'Added by Python')
answers = getAnswersForQuestion(WACLIENT,workloadId,'wellarchitected',questionId)
logger.info("Now the answer for questionId '%s' is '%s'" % (questionId, answers))
# STEP 4 - Saving a milestone
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/4_save_milestone/
logger.info("4 - Saving a Milestone")
logger.info("4 - STEP1 - Create a Milestone")
milestones = listMilestones(WACLIENT,workloadId)
milestoneCount = jmespath.search("length([*].MilestoneNumber)",milestones)
logger.info("Workload %s has %s milestones" % (workloadId, milestoneCount))
milestoneNumber = createMilestone(WACLIENT,workloadId,'Rev1')
logger.info("Created Milestone #%s called Rev1" % milestoneNumber)
logger.info("4 - STEP2 - List all Milestones")
milestones = listMilestones(WACLIENT,workloadId)
milestoneCount = jmespath.search("length([*].MilestoneNumber)",milestones)
logger.info("Now workload %s has %s milestones" % (workloadId, milestoneCount))
logger.info("4 - STEP3 - Retrieve the results from a milestone")
riskCounts = getMilestoneRiskCounts(WACLIENT,workloadId,milestoneNumber)
logger.info("Risk counts for all lenses for milestone %s are: %s " % (milestoneNumber,riskCounts))
logger.info("4 - STEP4 - List all question and answers based from a milestone")
answers = listAllAnswers(WACLIENT,workloadId,'wellarchitected',milestoneNumber)
# STEP 5 - Viewing and downloading the report
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/5_view_report/
logger.info("5 - Viewing and downloading the report")
logger.info("5 - STEP1 - Gather pillar and risk data for a workload")
lensReview = getLensReview(WACLIENT,workloadId,'wellarchitected')
logger.info("The Well-Architected base framework has the following RiskCounts %s " % lensReview['RiskCounts'])
logger.info("5 - STEP2 - Generate and download workload PDF")
lensReviewBase64PDF = getLensReviewPDFReport(WACLIENT,workloadId,'wellarchitected')
# lensReviewPDF = base64.b64decode(lensReviewBase64PDF)
# We will write the PDF to a file in the same directory
with open("WAReviewOutput.pdf", "wb") as fh:
fh.write(base64.b64decode(lensReviewBase64PDF))
# STEP 6 - Teardown
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/6_cleanup/
logger.info("6 - Teardown")
# Allow user to keep the workload
input("\n*** Press Enter to delete the workload or use ctrl-c to abort the script and keep the workload")
logger.info("6 - STEP1 - Delete Workload")
DeleteWorkload(WACLIENT, workloadId)
if __name__ == "__main__":
main()
The lab uses AWS CLI to perform all of the tasks, but you can also use the AWS Tools for PowerShell to perform the same steps. As a reference, you can download the code LabExample.ps1 which will perform all of steps from the lab in a single PowerShell script. This file assumes you have already installed the AWS.Tools PowerShell module as well as the AWS.Tools.WellArchitected module using Install-AWSToolsModule command. It also assumes you have setup your AWS credential file and it will uses the default profile for all interactions.
There is no error checking for most of the various API calls, so the code should not be considered production ready. Please review before implementing in your environment.
#!/usr/bin/env pwsh
# This is a simple Powershell app for use with the Well-Architected labs
# This will simulate all of the steps in the 200-level lab on using the
# Well-Architected API calls
#
# This code is only for use in Well-Architected labs
# *** NOT FOR PRODUCTION USE ***
#
#
# Licensed under the Apache 2.0 and MITnoAttr License.
#
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at
# https://aws.amazon.com/apache2.0/
# Requires -Modules @{ModuleName='AWSPowerShell.NetCore';ModuleVersion='3.3.618.0'}
$__author__ = "Eric Pullen"
$__email__ = "eppullen@amazon.com"
$__copyright__ = "Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved."
$__credits__ = @({"Eric Pullen"})
# Default region listed here
$REGION_NAME = "us-east-1"
# Setup Log Message Routine to print current date/time to console
function Log-Message
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string]$LogMessage
)
Write-Output ("{0} - {1}" -f (Get-Date), $LogMessage)
}
function Find-Workload
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[string]$workloadName
)
$response = Get-WATWorkloadList -WorkloadNamePrefix $workloadName
if (!$response.WorkloadId)
{
Write-Warning ("Did not find a workload called "+$workloadName) -InformationAction Continue
}
return $response.WorkloadId
}
function Create-New-Workload
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[string]$workloadName,
[Parameter(Mandatory=$true)]
[string]$description,
[Parameter(Mandatory=$true)]
[string]$reviewOwner,
[Parameter(Mandatory=$true)]
[string]$environment,
[Parameter(Mandatory=$true)]
[array]$awsRegions,
[Parameter(Mandatory=$true)]
[array]$lenses
)
try
{
$response = New-WATWorkload -WorkloadName $workloadName -Description $description -ReviewOwner $reviewOwner -Environment $environment -AwsRegion $awsRegions -Lense $lenses
$returnValue = $response.WorkloadId
}
catch [Amazon.WellArchitected.Model.ConflictException]
{
Write-Warning ("Conflict - Found a workload that already exists with the name "+$workloadName+". Finding the workloadId to return") -InformationAction Continue
$returnValue = Find-Workload $workloadName
}
return $returnValue
}
function Create-New-Milestone
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[string]$workloadId,
[Parameter(Mandatory=$true)]
[string]$milestoneName
)
try
{
$response = New-WATMilestone -WorkloadId $workloadId -MilestoneName $milestoneName
$returnValue = $response
}
catch [Amazon.WellArchitected.Model.ConflictException]
{
Write-Warning ("Conflict - Found a milestone that already exists with the name "+$milestoneName+". Finding the milestone to return") -InformationAction Continue
$milestoneReturn = Get-WATMilestoneList -WorkloadId $workloadId
$foundMilestone = $milestoneReturn.MilestoneSummaries | where {$_.MilestoneName -like $milestoneName+"*" }
$returnValue = $foundMilestone
}
return $returnValue
}
$WORKLOADNAME = 'WA Lab Test Workload'
$DESCRIPTION = 'Test Workload for WA Lab'
$REVIEWOWNER = 'WA Python Script'
$ENVIRONMENT= 'PRODUCTION'
$AWSREGIONS = @($REGION_NAME)
$LENSES = @('wellarchitected', 'serverless')
Log-Message ("1 - Starting LabExample.ps1")
# STEP 2 - Creating a workload
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/2_create_workload/
Log-Message ("2 - Creating new workload")
$createId = Create-New-Workload $WORKLOADNAME $DESCRIPTION $REVIEWOWNER $ENVIRONMENT $AWSREGIONS $LENSES
Log-Message ("Created with new workloadId: "+$createId)
Log-Message ("2 - Finding your WorkloadId for name "+$WORKLOADNAME)
$workloadId = Find-Workload $WORKLOADNAME
Log-Message ("New workload created with id " + $workloadId)
Log-Message ("2 - Using WorkloadId to remove and add lenses")
$listOfLenses = Get-WATLenseList
Log-Message ("Lenses currently available: "+$listOfLenses.LensAlias)
$workload = Get-WATWorkload -WorkloadId $workloadId
Log-Message ("WorkloadId "+$workloadId+" has lenses '"+$workload.Lenses+"'")
Log-Message ("Removing the serverless lens")
Remove-WATLense -WorkloadId $workloadId -LensAlias "serverless" -Force
$workload = Get-WATWorkload -WorkloadId $workloadId
Log-Message ("WorkloadId "+$workloadId+" has lenses '"+$workload.Lenses+"'")
Log-Message ("Adding serverless lens back into the workload")
Add-WATLense -WorkloadId $workloadId -LensAlias "serverless" -Force
$workload = Get-WATWorkload -WorkloadId $workloadId
Log-Message ("WorkloadId "+$workloadId+" has lenses '"+$workload.Lenses+"'")
# STEP 3 - Performing a review
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/3_perform_review/
Log-Message ("3 - Performing a review")
Log-Message ("3 - STEP1 - Find the QuestionId and ChoiceID for a particular pillar question and best practice")
$questionSearch = "How do you reduce defects, ease remediation, and improve flow into production"
$questionReturn = Get-WATAnswerList -WorkloadId $workloadId -LensAlias "wellarchitected" -PillarId "operationalExcellence"
$foundQuestion = $questionReturn.AnswerSummaries | where { $_.QuestionTitle -like $questionSearch+"*" }
$questionId = $foundQuestion.QuestionId
Log-Message ("Found QuestionID of "+$questionId+" for the question text of '"+$questionSearch+"'")
$choiceSet = @()
$answerReturn = Get-WATAnswer -WorkloadId $workloadId -LensAlias "wellarchitected" -QuestionId $questionId
$answerSearch = "Use version control"
$foundAnswer = $answerReturn.Answer.Choices | where {$_.Title -like $answerSearch+"*" }
$choiceSet += $foundAnswer.ChoiceId
Log-Message ("Found choiceId of '"+$choiceSet+"' for the choice text of 'Use version control'")
$answerSearch = "Use configuration management systems"
$foundAnswer = $answerReturn.Answer.Choices | where {$_.Title -like $answerSearch+"*" }
$choiceSet += $foundAnswer.ChoiceId
$answerSearch = "Use build and deployment management systems"
$foundAnswer = $answerReturn.Answer.Choices | where {$_.Title -like $answerSearch+"*" }
$choiceSet += $foundAnswer.ChoiceId
$answerSearch = "Perform patch management"
$foundAnswer = $answerReturn.Answer.Choices | where {$_.Title -like $answerSearch+"*" }
$choiceSet += $foundAnswer.ChoiceId
$answerSearch = "Use multiple environments"
$foundAnswer = $answerReturn.Answer.Choices | where {$_.Title -like $answerSearch+"*" }
$choiceSet += $foundAnswer.ChoiceId
Log-Message ("All choices we will select for questionId of "+$questionId+" is " + $choiceSet)
Log-Message ("3 - STEP2 - Use the QuestionID and ChoiceID to update the answer in well-architected review")
Log-Message ("Current answer for questionId '"+$questionId+"' is '"+$answerReturn.Answer.SelectedChoices+"'")
Log-Message ("Adding answers found in choices above '"+$choiceSet+"'")
$updateResponse = Update-WATAnswer -WorkloadId $workloadId -LensAlias "wellarchitected" -QuestionId $questionId -SelectedChoices $choiceSet -Notes "Question modified by PowerShell script"
$answerReturn = Get-WATAnswer -WorkloadId $workloadId -LensAlias "wellarchitected" -QuestionId $questionId
Log-Message ("Now the answer for questionId '"+$questionId+"' is '"+$answerReturn.Answer.SelectedChoices+"'")
# STEP 4 - Saving a milestone
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/4_save_milestone/
Log-Message ("4 - Saving a Milestone")
Log-Message ("4 - STEP1 - Create a Milestone")
$milestoneReturn = Get-WATMilestoneList -WorkloadId $workloadId
Log-Message ("Workload "+$workloadId+" has "+($milestoneReturn.MilestoneSummaries | measure ).Count+" milestones")
$createMilestoneResponse = Create-New-Milestone $workloadId "Rev1"
$createdmilestoneNumber = $createMilestoneResponse.MilestoneNumber
Log-Message ("Created Milestone #"+$createMilestoneResponse.MilestoneNumber+" called Rev1")
Log-Message ("4 - STEP2 - List all Milestones")
$milestoneReturn = Get-WATMilestoneList -WorkloadId $workloadId
Log-Message ("Now workload "+$workloadId+" has "+($milestoneReturn.MilestoneSummaries | measure ).Count+" milestones")
Log-Message ("4 - STEP3 - Retrieve the results from a milestone")
$milestoneDetailReturn = Get-WATMilestone -WorkloadId $workloadId -MilestoneNumber $createdmilestoneNumber
$riskCounts = $milestoneDetailReturn.Milestone.Workload.RiskCounts
Log-Message ("Risk counts for all lenses for milestone "+$createdmilestoneNumber+" are: "+($riskCounts | Out-String))
Log-Message ("4 - STEP4 - List all question and answers based from a milestone")
$questionReturn = Get-WATAnswerList -WorkloadId $workloadId -LensAlias "wellarchitected" -MilestoneNumber $createdmilestoneNumber
# STEP 5 - Viewing and downloading the report
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/5_view_report/
Log-Message ("5 - Viewing and downloading the report")
Log-Message ("5 - STEP1 - Gather pillar and risk data for a workload")
$LensReviewReturn = Get-WATLensReview -WorkloadId $workloadId -LensAlias "wellarchitected"
$LensReviewRiskCounts = $LensReviewReturn.LensReview.RiskCounts
Log-Message ("The Well-Architected base framework has the following RiskCounts "+($LensReviewRiskCounts | Out-String))
Log-Message ("5 - STEP2 - Generate and download workload PDF")
$LensPDFReviewReturn = Get-WATLensReviewReport -WorkloadId $workloadId -LensAlias "wellarchitected"
$filename = 'WAReviewOutput.pdf'
$bytes = [Convert]::FromBase64String($LensPDFReviewReturn.LensReviewReport.Base64String)
[IO.File]::WriteAllBytes($filename, $bytes)
# STEP 6 - Teardown
# https://wellarchitectedlabs.com/well-architectedtool/200_labs/200_using_awscli_to_manage_wa_reviews/6_cleanup/
Log-Message ("6 - Teardown")
# Allow user to keep the workload
Read-Host -Prompt "Press any key to continue or CTRL+C to quit"
Log-Message ("6 - STEP1 - Delete Workload")
Remove-WATWorkload -WorkloadId $workloadId -ClientRequestToken "ClientRequestToken1" -Force
# ClientRequestToken is required at this time, but we are investigating if we can remove this in the future.
Now that you have completed this lab, make sure to update your Well-Architected review if you have implemented these changes in your workload.
Click here to access the Well-Architected Tool