200 - AppSync w/ Systems Manager integration

In this lab you will experiment with AWS AppSync and build an integration to AWS Systems Manager’s Parameter Store.

As result, these are the features that you are going to be exposed here:

  1. Creating a GraphQL API.
  2. Configuring the direct integration to another AWS Service.
  3. Using Resolver Mapping Templates to have data transformations executed by AppSync.

Prerequisites

Before getting started, you will need:

  • an IAM user with proper permissions to create AWS AppSync resources
  • CURL installed on your local machine.
    • If you are using Cloud9 or AWS CloudShell, you have CURL and AWS CLI available by default, but you need to be sure that the environment has the proper permissions.

Context

We are going to create an API that will consume services from AWS Systems Manager (SSM), using the Parameter Store feature.

Your API will consume the configuration for a system, stored on the Parameter Store. We are considering that all parameters for a certain system will have the form /systems/<systemName>/<param1>, where <systemName> is, obvioulsy, the name of the system to which we want to retrieve the configuration, and <param1> is one of the parameters.

This is an example of a request we want to send to AppSync

getSystemSettings(systemName : "system01")

And this is an example of a possible response:

{ 
    "SystemName" : "system01",
    "Parameters : [
        { 
            "Name" : "/systems/system01/parameter1",
            "Value": "value of parameter1"
        },
        { 
            "Name" : "/systems/system01/parameter2",
            "Value": "value of parameter2"
        },
        ...
    ]
}

In this lab we are considering that you are running the lab alone in a single account/region. If you are sharing the account with other people, please ensure that you have only one person per region to avoid name collisions.


Task - Creating some parameters

We are going to create some parameters so we can use them to test our GraphQL API.

  1. Copy and paste the content below to the terminal on Cloud9 or on CloudShell
aws ssm put-parameter --name /systems/system10/clientid --description "The application client ID" --value abcd01 --type String
aws ssm put-parameter --name /systems/system10/url --description "The system URL" --value "system01.mycompany.com" --type String
aws ssm put-parameter --name /systems/system10/latestReview --description "Date of the latest well-architected review" --value "null" --type String
aws ssm put-parameter --name /systems/system10/reviewPeriodicityInDays --description "Periodicity of WARs" --value "90" --type String

  1. Visit the Systems Manager Parameter Store Console to see that these parameters were properly created.

Task - Create your API and define the Schema

  1. Visit the AppSync Console
  2. Click on Create API.
  3. Select Build from scratch, and then click on the Start button.
  4. In the Create resources page:
    1. For APUIconfiguration, API Name, use myapi
    2. Click on the button Create.
      • AppSync is going to show you the Getting Started Page. At the left side of the page you are going to see myapi, and under it many options like Schema, Data Sources, and so on.
  5. On the menu on the left, click on Schema. Replace what’s under the Schema section with what is below:
type SystemSettings @model {
	SystemName: String!
	Parameters: [Parameter]
}

type Parameter @model {
	Name: String!
	Value: String!
}

type Query {
	getSystemSettings(systemName: String): SystemSettings
}

schema {
    query: Query
}
  1. On the top right, click on Save Schema

See that SystemSettings describes properly the model of the expected response from getSystemSettings: In it there is a string with the system name in SystemName, and the property Parameters contains an array of Parameter, and each Parameter object has a Name (with the name of the parameter), and a Value (with its value).

Learn more about Schema definitions here: https://docs.aws.amazon.com/appsync/latest/devguide/designing-your-schema.html


Task - Creating the role for your API

As you must know, when we have different services talking to each other, the caller needs to have the proper permissions in place to be able to access the callee API.

In our case, we need to have our API with the proper permissions to call the Systems Manager/Parameter Store.

So, first we are going to create the Role that is going to be used by your API.

  1. Go to the IAM Console.
  2. On the menu at the left, click on Roles.
  3. Click on the button Create role.
  4. In the Create Role page:
    1. select AWS Service.
    2. In the list of services, select AppSync.
    3. Click on Next: Permissions.
  5. On the Attached permissions policies page there is nothing to configure right now. Just click Next: Tags.
  6. On the Add tags (optional) page also there is nothing to configure right now. Just click Next: Review.
  7. On the Review page:
    1. For Role name* input MyAPIAppSyncRole.
    2. For Role description input Permissions for the Appsync API myapi.
    3. Click on Create Role. On the top of the page you are going to see the message The role MyAPIAppSyncRole has been created.
  8. Click on the link MyAPIAppSyncRole, so we can add a policy that is going to give permissions to SSM.
  9. On the role Summary page, take note of the Role ARN. You are going to need it. Copy it to a helper text file.
  10. While in the same page, click on Add inline policy. You are going to be taken to the Create policy page. On that page:
    1. For Service, select Systems Manager.
    2. For Actions, select GetParametersByPath.
    3. Click on the Resources section to expand it, and then click on Add ARN to restrict access. A pop-up window will appear.
      1. For Region, input the region where you are running this lab. Look at the URL filed in your browser and you are going to see it.
      2. The field Account is going to be filled already. Leave it as it is.
      3. For Fully qualified parameter name *, input systems/*.
      4. Click on Review Policy.
      5. Give the name SSMPermissions to the policy.
      6. Click on Create policy. With that we have defined the role that is going to be used by AppSync

Again, take note of the role ARN for later use.


Task - Defining the data source

Get back to the AppSync console, and be sure of being working on your API (named myapi).

If you look at the menu at left, you are going to see that we have an option to define a new Data Source. However, on the web console, at least at this moment, there is no way for you to configure the roles for a Data Source. So, as we are not using any resource like CDK, AWS Amplify, SAM, or CloudFormation, we are going to use the AWS CLI that comes within Cloud9 or CloudShell to create a file that defines the data source configuration, and then use it to create the data source in your API

In our case, we need a Data Source pointing towards SSM/Parameter Store, but we also need to bind the permissions - the role that we have just created - to that data source.

  1. On the terminal of Cloud9 or CloudShell create a file named ssmconfig.json, and put the following content in it:
    {
            "endpoint" : "https://ssm.<region>.amazonaws.com/",
            "authorizationConfig" : {
                    "authorizationType" : "AWS_IAM",
                    "awsIamConfig" : {
                            "signingRegion" : "<region>",
                            "signingServiceName" : "ssm"
                    }
            }
    }
    
  2. In that file, replace <region> with the region where you are working on.
  3. Save the file.
  4. Go back to the AppSync web console for your API, and click on Settings. Get the API ID from that page.
  5. Open a helper text file, and copy and paste the content below:
    aws appsync create-data-source  --api-id <api-id> \
                                    --name ssm_iam \
                                    --type HTTP \
                                    --http-config file://<file-path> \
                                    --service-role-arn <role-arn>
    
  6. Do the following substitutions:
    1. Replace <api-id> with the API ID that you got from the step 4 above.
    2. Replace <file-path> with the full path to the ssmconfig.json file that you have created.
    3. Replace <role-arn> with the ARN of the IAM Role that we have previously created.
  7. Copy the command and run it in the Cloud9/CloudShell terminal.

If your command has run properly, you are going to get a response similar to this one:

{
    "dataSource": {
        "dataSourceArn": "arn:aws:appsync:<region>:<account>:apis/<api-id>/datasources/ssm_iam",
        "name": "ssm_iam",
        "type": "HTTP",
        "serviceRoleArn": "<role-arn>",
        "httpConfig": {
            "endpoint": "https://ssm.<region>.amazonaws.com/",
            "authorizationConfig": {
                "authorizationType": "AWS_IAM",
                "awsIamConfig": {
                    "signingRegion": "<region>",
                    "signingServiceName": "ssm"
                }
            }
        }
    }
}

If at any moment you want to check the data sources configured to your API, just run the following command (replacing <api-id> with the ID for your API):

aws appsync list-data-sources --api-id <api-id> 

Now if you get back to your API page on AppSync, and click on Data Sources you are going to see a data source named ssm_iam there (be sure of refreshing the page if it does not appear).

Now we can get back and define the resolver for our query.


Task - Defining the resolvers

Resolvers are the mechanism that we use to retrive the fields that we need to build the response of a query. A model or a query can use different resolvers to “resolve” what should be the content of each one of the fields.

As you see that we have defined the getSystemSettings query, we need now to teach AppSync how to build the response for us.

Learn more about Resolver mapping templates here: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-overview.html.

  1. In the menu at the left of the page, click on Schema.

  2. In the section at the right of the page, Resolvers, scroll down and find the subsection for Query.

    • You are going to see something like this
    Field Resolver
    getSystemSettings(…): SystemSettings [Attach]
  3. Click on the button Attach. You are going to be redirected to a Create new Resolver page.

  4. On the section Resolver for Query.getSystemSettings, for Data source name, select the data source that we have just created (ssm_iam). Below this section, a new section will appear for you to configure the mapping templates.

    • You are going to see two sections:
      • Configure the request mapping template is where you use a mapping template to shape the REQUEST you want to go to SSM/Parameter Store;
      • Configure the response mapping template is where you shape the RESPONSE from SSM to send it back to requester.
  5. For the REQUEST section, replace the content with what is below:

    #**
    Given a SystemId, this returns all the parameters related to that system
    *#
    #set( $ssmRequestBody = 
        {
        "Path":  "/systems/$context.args.systemName",
        "Recursive" : true
        }
    )
    {
        "version": "2018-05-29",
        "method": "POST",
        "resourcePath": "/",
        "params":{
            "headers": {
                "X-Amz-Target" : "AmazonSSM.GetParametersByPath",
                "Content-Type" : 	"application/x-amz-json-1.1"
            },
            "body" : $util.toJson($ssmRequestBody)
        }
    }
    
  6. For the RESPONSE section, replace the content with what is below:

    #if($ctx.error)
        $util.error($ctx.error.message, $ctx.error.type)
    #end
    #if($ctx.result.statusCode == 200)
        #set( $body = $util.parseJson($ctx.result.body) )
        #set($arrayOfParameters = [])
        #foreach( $item in $body.Parameters )
            $util.qr( $arrayOfParameters.add( { "Name" : $item.Name, "Value" : $item.Value } ) )
        #end
        $util.toJson( { "SystemName" : $ctx.arguments.systemName , "Parameters" : $arrayOfParameters }  )
    #else
        $util.toJson($ctx.error)
        $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
    #end
    
  7. On the top right, click on Save Resolver.

Can you understand what is happening inside each one of these mapping templates? If you are not sure, ask the presenter!


Task - Run the Query

Using the AppSync console
  1. In the menu at the left of the page, click on Queries.
  2. Expand all fields and mark all checkboxes that appear under getSystemSettings.
  3. You are going to see a space between quotes where you can insert the system name. Put system10in that space. If you did everything properly, you should see something like what is shown below in the middle of the page, right below an orange run button:
query MyQuery {
    getSystemConfiguration(systemName: "system10") {
        SystemName
        Parameters {
            Name
            Value
        }
    }
}
  1. Click on the run button (or hit CTRL+Enter)
  2. If everything is correct, you are going to see the results of your query:
{
  "data": {
    "getSystemConfiguration": {
      "SystemName": "system10",
      "Parameters": [
        {
          "Name": "/systems/system10/clientid",
          "Value": "abcd01"
        },
        {
          "Name": "/systems/system10/latestReview",
          "Value": "null"
        },
        {
          "Name": "/systems/system10/reviewPeriodicityInDays",
          "Value": "90"
        },
        {
          "Name": "/systems/system10/url",
          "Value": "system01.mycompany.com"
        }
      ]
    }
  }
}
Using CURL
  1. Visit the settings of your API and in a helper document take note of:
    1. the API KEY
    2. the API URL
  2. Copy the content below to a helper document, and replace appropriately the fields <API-KEY> and <API-URL>
    curl -H "content-Type:application/graphql" -H "<API-KEY>" -X POST -d '{ "query": "query MyQuery { getSystemSettings( systemName : \"system10\" ) { Parameters { Name Value } SystemName } }" }' <API-URL>
    
  3. Go to the terminal on Cloud9 or on CloudShell, and run the command that you have just configured.

See that the command that the data we are providing in the body for the POST request (right after the -d) is exactly the same command you see built in the Appsync query page.


Finishing the lab

Congratulations! You have created your GraphQL API integrated to SSM. As this is a 200-level lab, we are pretty sure that you know how to destroy the resources if you want to.

We hope you enjoyed it!

Lab Author: Fabian Da Silva