{"action":"created","comment":{"url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/comments/215191193","pull_request_review_id":152399338,"id":215191193,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxNTE5MTE5Mw==","diff_hunk":"@@ -0,0 +1,570 @@\n+---\n+layout: post\n+title: Data Continuity Service for DynamoDB\n+banner: /assets/posts/2018-09-05-Data-Continuity-Service-for-DynamoDB/solution-overview.png)\n+author: tvb\n+---\n+\n+This is the first post of three where I am going to explain how to build and configure a Data Continuity Service (DCS) for Amazon DynamoDB by using Amazon DynamoDB Streams, AWS Lambda and the Amazon S3 services.\n+\n+Why Data Continuity Service?\n+-----\n+Now, you might ask yourself why you would need such thing as Data Continuity Service (DCS) for DynamoDB because AWS launched the [backup and restore](https://aws.amazon.com/about-aws/whats-new/2017/11/aws-launches-amazon-dynamodb-backup-and-restore/) functionality for DynamoDB in November 2017.\n+Well, while this is true and it works really great there are some shortcomings to the backup and restore functionality. Which I will explain now.\n+\n+First, don't get me wrong! You should be using the backup and restore functionality integrated with Amazon DynamoDB always as it is really great at quickly making backup and restore your content. However it also means these backups are hosted in the same region and within the same account as your DynamoDB data is located.\n+\n+So although, highly unlikely, it could happen that a region becomes out of service and you won't be able to access your DynamoDB data. Now imagine your backups are in that same region. You would have no means to restore the service to your customers by restoring your DynamoDB tables because you are unable to access the data and the backups holding your precious data! It would mean disaster! And to make things even worse, imagine someone gets hold of your administrator access keys and starts deleting all of your AWS resources, data __and__ backups. [It would mean the end of your business](https://threatpost.com/hacker-puts-hosting-service-code-spaces-out-of-business/106761/) (no joke, must read!). This is where a Data Continuity Service for DynamoDB would be a perfect fit. The DCS solution I am going to show you will make sure your DynamoDB data is replicated into another region and to another account so you will always be able to access your data in case disaster really strikes.\n+\n+Overview\n+--------\n+![overview](/assets/posts/2018-09-05-Data-Continuity-Service-for-DynamoDB/solution-overview.png)\n+\n+\n+Definitions\n+--------\n+**Amazon DynamoDB** - Amazon DynamoDB is a fully managed proprietary NoSQL database service that supports key-value and document data structures.\n+\n+**Amazon DynamoDB Streams** - Amazon DynamoDB Streams captures a time-ordered sequence of item-level modifications in any DynamoDB table, and stores this information in a log for up to 24 hours.\n+\n+**AWS Lambda** - AWS Lambda is an event-driven, serverless computing platform. Run code without provisioning or managing servers.\n+\n+**Amazon S3** - Amazon S3 provides object storage at scale.\n+\n+\n+DynamoDB\n+-----------\n+For this post we are going to have a DynamoDB table called `users` which holds our company customers first name, last name, email address and an important field. Just for the sake of example.\n+\n+It might look like the following:\n+\n+![DynamoDB users table](/assets/posts/2018-09-05-Data-Continuity-Service-for-DynamoDB/dynamodb-users-table.png)\n+\n+Here is a snippet of a JSON formatted CloudFormation template to setup the DynamoDB table with CloudFormation:\n+\n+```json\n+{\n+  \"DynamoDbUsersTable\": {\n+    \"Properties\": {\n+      \"AttributeDefinitions\": [\n+        {\n+          \"AttributeName\": \"id\",\n+          \"AttributeType\": \"S\"\n+        }\n+      ],\n+      \"KeySchema\": [\n+        {\n+          \"AttributeName\": \"id\",\n+          \"KeyType\": \"HASH\"\n+        }\n+      ],\n+      \"ProvisionedThroughput\": {\n+        \"ReadCapacityUnits\": 1,\n+        \"WriteCapacityUnits\": 1\n+      },\n+      \"TableName\": \"users\"\n+    },\n+    \"Type\": \"AWS::DynamoDB::Table\"\n+  }\n+}\n+```\n+\n+\n+DynamoDB Streams\n+-----------\n+Now to get things going we need to configure a DynamoDB Stream on our `users` table so we can listen to the stream and trigger an action whenever a record in the table is changed.\n+\n+>To read more about DynamoDB Streams see the [AWS documentation on DynamoDB Streams](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html).\n+\n+Here is a snippet to configure a stream in CloudFormation:\n+\n+```json\n+{\n+  \"DynamoDbUsersTable\": {\n+    \"Properties\": {\n+      \"...\"\n+      \"StreamSpecification\": {\n+        \"StreamViewType\": \"NEW_AND_OLD_IMAGES\"\n+      },\n+      \"TableName\": \"users\"\n+    },\n+    \"Type\": \"AWS::DynamoDB::Table\"\n+  }\n+}\n+```\n+\n+Note I stripped some of the other properties needed for easier reading. The real magic is the property called `StreamSpecification` and which holds the key \"StreamViewType\" and the value as a the string __NEW_AND_OLD_IMAGES__. This is _crucial_.\n+>To read more about the CloudFormation properties available for your DynamoDB table see the [AWS Documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-streamspecification).\n+\n+Once we have configured our DynamoDB Stream, the Stream details on the DynamoDB overview page for the users table might look like the following:\n+\n+![dynamodb-streams-enabled](/assets/posts/2018-09-05-Data-Continuity-Service-for-DynamoDB/dynamodb-streams-enabled.png)\n+\n+We now have an Amazon Resource Name (ARN) available for us to use!\n+\n+Lambda\n+--------\n+Ok so far it was easy going, right? We created a DynamoDB table and configured it to setup a DynamoDB Stream. Now lets move on to the part where the magic starts. AWS Lambda! I really like Lambda as it is so powerful yet so flexible.\n+\n+We are going to use Lambda to listen to the DynamoDB Stream and whenever there is an event posted onto the stream Lambda will read the data on the stream and write it to Amazon S3.\n+\n+To configure Lambda we first need to create an IAM Service Role. Here is the CloudFormation snippet:\n+\n+```json\n+{\n+  \"DynamoDbStreamToS3LambdaRole\": {\n+    \"Properties\": {\n+      \"AssumeRolePolicyDocument\": {\n+        \"Statement\": [\n+          {\n+            \"Action\": [\n+              \"sts:AssumeRole\"\n+            ],\n+            \"Effect\": \"Allow\",\n+            \"Principal\": {\n+              \"Service\": \"lambda.amazonaws.com\"\n+            }\n+          }\n+        ],\n+        \"Version\": \"2012-10-17\"\n+      },\n+      \"Path\": \"/\",\n+      \"Policies\": [\n+        {\n+          \"PolicyDocument\": {\n+            \"Statement\": [\n+              {\n+                \"Action\": [\n+                  \"logs:CreateLogGroup\",\n+                  \"logs:CreateLogStream\",\n+                  \"logs:DeleteSubscriptionFilter\",\n+                  \"logs:PutLogEvents\",\n+                  \"logs:PutSubscriptionFilter\",\n+                  \"logs:TestMetricFilter\"\n+                ],\n+                \"Effect\": \"Allow\",\n+                \"Resource\": \"arn:aws:logs:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:*\"\n+              }\n+            ]\n+          },\n+          \"PolicyName\": \"AllowLogs\"\n+        },\n+        {\n+          \"PolicyDocument\": {\n+            \"Statement\": [\n+              {\n+                \"Action\": [\n+                  \"lambda:InvokeFunction\"\n+                ],\n+                \"Effect\": \"Allow\",\n+                \"Resource\": \"arn:aws:lambda:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:function:*\"\n+              }\n+            ]\n+          },\n+          \"PolicyName\": \"AllowLambaInvoke\"\n+        },\n+        {\n+          \"PolicyDocument\": {\n+            \"Statement\": [\n+              {\n+                \"Action\": [\n+                  \"s3:PutObject\"\n+                ],\n+                \"Effect\": \"Allow\",\n+                \"Resource\": \"arn:aws:s3:::source-account-dynamodb-bucket/*\"\n+              }\n+            ]\n+          },\n+          \"PolicyName\": \"AllowS3Access\"\n+        },\n+        {\n+          \"PolicyDocument\": {\n+            \"Statement\": [\n+              {\n+                \"Action\": [\n+                  \"dynamodb:GetRecords\",\n+                  \"dynamodb:GetShardIterator\",\n+                  \"dynamodb:DescribeStream\",\n+                  \"dynamodb:ListStreams\"\n+                ],\n+                \"Effect\": \"Allow\",\n+                \"Resource\": \"arn:aws:dynamodb:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:table/users/stream/*\"\n+              }\n+            ]\n+          },\n+          \"PolicyName\": \"AllowDynamoDbAccess\"\n+        }\n+      ]\n+    },\n+    \"Type\": \"AWS::IAM::Role\"\n+  }\n+}\n+```\n+\n+A few things to note. First we are allowing this role to assume on behalf of AWS Lambda by specifying the `sts:AssumeRole` permissions under the \"AssumeRolePolicyDocument\" key.\n+\n+Next we are defining some needed policies. We are allowing Lambda to write to CloudWatch logs for logging purposes. We also allowing Lambda to invoke the function or else it wouldn't be able start at all. We are allowing Lambda to write to the source bucket and lastly we allowing Lambda to access and read the DynamoDB Stream which is the hole point of this setup :smiley:\n+\n+Don't forget to enter the correct AWS Region and your own AWS Account Id.\n+\n+Once that is in place we can create our Lambda function and assign it our newly created execution role.\n+\n+![lambda-function](/assets/posts/2018-09-05-Data-Continuity-Service-for-DynamoDB/lambda-function.png)\n+\n+Or use the CloudFormation snippet below:\n+\n+```json\n+{\n+  \"DynamoDbStreamToS3LambdaFunction\": {\n+    \"Properties\": {\n+      \"Code\": {\n+        \"ZipFile\": \"exports.backup = function (event, context, callback){ console.log('Hello'); callback(null); }\"\n+      },\n+      \"Environment\": {\n+        \"Variables\": {\n+          \"BUCKET\": \"source-account-dynamodb-backup\",\n+          \"TABLE_NAME\": \"users\"\n+        }\n+      },\n+      \"Handler\": \"index.backup\",\n+      \"MemorySize\": 128,\n+      \"Role\": {\n+        \"Fn::GetAtt\": [\n+          \"DynamoDbStreamToS3LambdaRole\",\n+          \"Arn\"\n+        ]\n+      },\n+      \"Runtime\": \"nodejs6.10\",\n+      \"Timeout\": 300\n+    },\n+    \"Type\": \"AWS::Lambda::Function\"\n+  }\n+}\n+```\n+\n+After the function is created we need to attach the DynamoDB Stream to our Lambda function so the function can listen and work whenever there is an event posted:\n+\n+![configure lambda 1](/assets/posts/2018-09-05-Data-Continuity-Service-for-DynamoDB/configure-lambda-trigger-1.png)\n+\n+Resulting in:\n+\n+![configure lambda 2](/assets/posts/2018-09-05-Data-Continuity-Service-for-DynamoDB/configure-lambda-trigger-2.png)\n+\n+Or update your CloudFormation template with the content below below:\n+```json\n+{\n+  \"DynamoDbStreamToS3LambdaEventSourceMapping\": {\n+    \"Properties\": {\n+      \"EventSourceArn\": \"arn:aws:dynamodb:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:table/users/stream/<STREAM_ID>\",\n+      \"FunctionName\": {\n+        \"Ref\": \"DynamoDbStreamToS3LambdaFunction\"\n+      },\n+      \"StartingPosition\": \"LATEST\"\n+    },\n+    \"Type\": \"AWS::Lambda::EventSourceMapping\"\n+  }\n+}\n+```\n+\n+Deployed? Done? Good. Everything on the Lambda side is ready apart from the function itself. Luckily the function itself is rather simple:\n+\n+```javascript\n+'use strict';\n+\n+const aws = require('aws-sdk');\n+\n+const s3 = new aws.S3({ region: '<YOUR AWS_REGION>' });\n+\n+module.exports.backup = (event, context, callback) => {\n+  const records = event.Records;\n+\n+  Promise.all(records.map((record) => {\n+    const keysList = Object.keys(record.dynamodb.Keys).map((key) => {\n+      const keyDefinition = record.dynamodb.Keys[key];\n+      const type = Object.keys(keyDefinition)[0];\n+      const value = keyDefinition[type];\n+      return value;\n+    });\n+\n+    const keysString = keysList[0];\n+    let image = {}\n+    if(typeof record.dynamodb.NewImage !== 'undefined') {\n+      image = aws.DynamoDB.Converter.output({ M: record.dynamodb.NewImage });\n+    }\n+\n+    return s3.putObject({\n+      Bucket: process.env.BUCKET,\n+      Key: `${process.env.TABLE_NAME}/${keysString}/image.json`,\n+      Body: JSON.stringify(image),\n+      StorageClass: \"STANDARD_IA\"\n+    }).promise()\n+      .then((response) => {\n+        console.log(`${keysString} snapshot done`, response);\n+      })\n+      .catch((err) => {\n+        console.error('Error', err);\n+      });\n+  }))\n+    .then(v => callback(null, v), callback);\n+};\n+```\n+\n+Update the Lambda function with above code. Make sure you have set the Lambda handler to `index.backup`.\n+\n+In general the function will run for each DynamoDB __insert__ or __update__ action and read the event record. It will then write the \"NewImage\" data as a JSON file to S3 and store it as the *STANDARD_IA* storage class to save up some bucks as you most probably never have to use the data. Or as least that is what we are hoping for. :sweat_smile:\n+\n+It expects an environment variable named `TABLE_NAME` which holds the DynamoDB table name. In this case the table is called `users` so we are sending that with the function. It also expects another environment variable named `BUCKET`. Ahhh finally! We haven't really talked about buckets and S3 up to now so this would be a good moment to start talking about S3.\n+\n+S3\n+-------\n+This is where is really becomes interesting as we have to make sure the data is written to S3 and S3 is replicated into another region __and__ to another account.\n+\n+To do this you will have to configure two roles and two buckets. Both in different accounts each.","path":"_posts/2018-09-05-Data-Continuity-Service-for-DynamoDB.md","position":322,"original_position":322,"commit_id":"e386b43fa3a274af4456d5440fdf588610a54bf6","original_commit_id":"e386b43fa3a274af4456d5440fdf588610a54bf6","user":{"login":"donkersgoed","id":4188922,"node_id":"MDQ6VXNlcjQxODg5MjI=","avatar_url":"https://avatars2.githubusercontent.com/u/4188922?v=4","gravatar_id":"","url":"https://api.github.com/users/donkersgoed","html_url":"https://github.com/donkersgoed","followers_url":"https://api.github.com/users/donkersgoed/followers","following_url":"https://api.github.com/users/donkersgoed/following{/other_user}","gists_url":"https://api.github.com/users/donkersgoed/gists{/gist_id}","starred_url":"https://api.github.com/users/donkersgoed/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/donkersgoed/subscriptions","organizations_url":"https://api.github.com/users/donkersgoed/orgs","repos_url":"https://api.github.com/users/donkersgoed/repos","events_url":"https://api.github.com/users/donkersgoed/events{/privacy}","received_events_url":"https://api.github.com/users/donkersgoed/received_events","type":"User","site_admin":false},"body":"\"both\" and \"each\" are redundant. I'd change it into:\r\n\r\n> To do this you will have to configure two roles and two buckets. We will put one role and bucket in the source account, and the other role and bucket in the account containing the backups.","created_at":"2018-09-05T09:09:36Z","updated_at":"2018-09-05T09:12:43Z","html_url":"https://github.com/sentialabs/sentialabs.github.io/pull/34#discussion_r215191193","pull_request_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/34","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/comments/215191193"},"html":{"href":"https://github.com/sentialabs/sentialabs.github.io/pull/34#discussion_r215191193"},"pull_request":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/34"}},"in_reply_to_id":215151054},"pull_request":{"url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/34","id":213018539,"node_id":"MDExOlB1bGxSZXF1ZXN0MjEzMDE4NTM5","html_url":"https://github.com/sentialabs/sentialabs.github.io/pull/34","diff_url":"https://github.com/sentialabs/sentialabs.github.io/pull/34.diff","patch_url":"https://github.com/sentialabs/sentialabs.github.io/pull/34.patch","issue_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues/34","number":34,"state":"open","locked":false,"title":"Feature/dynamodb dcs","user":{"login":"tvb","id":1263414,"node_id":"MDQ6VXNlcjEyNjM0MTQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1263414?v=4","gravatar_id":"","url":"https://api.github.com/users/tvb","html_url":"https://github.com/tvb","followers_url":"https://api.github.com/users/tvb/followers","following_url":"https://api.github.com/users/tvb/following{/other_user}","gists_url":"https://api.github.com/users/tvb/gists{/gist_id}","starred_url":"https://api.github.com/users/tvb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvb/subscriptions","organizations_url":"https://api.github.com/users/tvb/orgs","repos_url":"https://api.github.com/users/tvb/repos","events_url":"https://api.github.com/users/tvb/events{/privacy}","received_events_url":"https://api.github.com/users/tvb/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-09-04T15:31:57Z","updated_at":"2018-09-05T09:12:43Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4dd9fb30d08b14135c92d7541ad1dbf8cb3493b0","assignee":null,"assignees":[],"requested_reviewers":[{"login":"svdgraaf","id":19777,"node_id":"MDQ6VXNlcjE5Nzc3","avatar_url":"https://avatars0.githubusercontent.com/u/19777?v=4","gravatar_id":"","url":"https://api.github.com/users/svdgraaf","html_url":"https://github.com/svdgraaf","followers_url":"https://api.github.com/users/svdgraaf/followers","following_url":"https://api.github.com/users/svdgraaf/following{/other_user}","gists_url":"https://api.github.com/users/svdgraaf/gists{/gist_id}","starred_url":"https://api.github.com/users/svdgraaf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/svdgraaf/subscriptions","organizations_url":"https://api.github.com/users/svdgraaf/orgs","repos_url":"https://api.github.com/users/svdgraaf/repos","events_url":"https://api.github.com/users/svdgraaf/events{/privacy}","received_events_url":"https://api.github.com/users/svdgraaf/received_events","type":"User","site_admin":false},{"login":"tader","id":35197,"node_id":"MDQ6VXNlcjM1MTk3","avatar_url":"https://avatars3.githubusercontent.com/u/35197?v=4","gravatar_id":"","url":"https://api.github.com/users/tader","html_url":"https://github.com/tader","followers_url":"https://api.github.com/users/tader/followers","following_url":"https://api.github.com/users/tader/following{/other_user}","gists_url":"https://api.github.com/users/tader/gists{/gist_id}","starred_url":"https://api.github.com/users/tader/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tader/subscriptions","organizations_url":"https://api.github.com/users/tader/orgs","repos_url":"https://api.github.com/users/tader/repos","events_url":"https://api.github.com/users/tader/events{/privacy}","received_events_url":"https://api.github.com/users/tader/received_events","type":"User","site_admin":false},{"login":"mircowienen","id":621017,"node_id":"MDQ6VXNlcjYyMTAxNw==","avatar_url":"https://avatars1.githubusercontent.com/u/621017?v=4","gravatar_id":"","url":"https://api.github.com/users/mircowienen","html_url":"https://github.com/mircowienen","followers_url":"https://api.github.com/users/mircowienen/followers","following_url":"https://api.github.com/users/mircowienen/following{/other_user}","gists_url":"https://api.github.com/users/mircowienen/gists{/gist_id}","starred_url":"https://api.github.com/users/mircowienen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mircowienen/subscriptions","organizations_url":"https://api.github.com/users/mircowienen/orgs","repos_url":"https://api.github.com/users/mircowienen/repos","events_url":"https://api.github.com/users/mircowienen/events{/privacy}","received_events_url":"https://api.github.com/users/mircowienen/received_events","type":"User","site_admin":false},{"login":"kbessas","id":21869767,"node_id":"MDQ6VXNlcjIxODY5NzY3","avatar_url":"https://avatars1.githubusercontent.com/u/21869767?v=4","gravatar_id":"","url":"https://api.github.com/users/kbessas","html_url":"https://github.com/kbessas","followers_url":"https://api.github.com/users/kbessas/followers","following_url":"https://api.github.com/users/kbessas/following{/other_user}","gists_url":"https://api.github.com/users/kbessas/gists{/gist_id}","starred_url":"https://api.github.com/users/kbessas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kbessas/subscriptions","organizations_url":"https://api.github.com/users/kbessas/orgs","repos_url":"https://api.github.com/users/kbessas/repos","events_url":"https://api.github.com/users/kbessas/events{/privacy}","received_events_url":"https://api.github.com/users/kbessas/received_events","type":"User","site_admin":false},{"login":"veranika-isakova","id":32074608,"node_id":"MDQ6VXNlcjMyMDc0NjA4","avatar_url":"https://avatars1.githubusercontent.com/u/32074608?v=4","gravatar_id":"","url":"https://api.github.com/users/veranika-isakova","html_url":"https://github.com/veranika-isakova","followers_url":"https://api.github.com/users/veranika-isakova/followers","following_url":"https://api.github.com/users/veranika-isakova/following{/other_user}","gists_url":"https://api.github.com/users/veranika-isakova/gists{/gist_id}","starred_url":"https://api.github.com/users/veranika-isakova/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/veranika-isakova/subscriptions","organizations_url":"https://api.github.com/users/veranika-isakova/orgs","repos_url":"https://api.github.com/users/veranika-isakova/repos","events_url":"https://api.github.com/users/veranika-isakova/events{/privacy}","received_events_url":"https://api.github.com/users/veranika-isakova/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/34/commits","review_comments_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/34/comments","review_comment_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues/34/comments","statuses_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/statuses/e386b43fa3a274af4456d5440fdf588610a54bf6","head":{"label":"sentialabs:feature/dynamodb-dcs","ref":"feature/dynamodb-dcs","sha":"e386b43fa3a274af4456d5440fdf588610a54bf6","user":{"login":"sentialabs","id":21081697,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxMDgxNjk3","avatar_url":"https://avatars3.githubusercontent.com/u/21081697?v=4","gravatar_id":"","url":"https://api.github.com/users/sentialabs","html_url":"https://github.com/sentialabs","followers_url":"https://api.github.com/users/sentialabs/followers","following_url":"https://api.github.com/users/sentialabs/following{/other_user}","gists_url":"https://api.github.com/users/sentialabs/gists{/gist_id}","starred_url":"https://api.github.com/users/sentialabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sentialabs/subscriptions","organizations_url":"https://api.github.com/users/sentialabs/orgs","repos_url":"https://api.github.com/users/sentialabs/repos","events_url":"https://api.github.com/users/sentialabs/events{/privacy}","received_events_url":"https://api.github.com/users/sentialabs/received_events","type":"Organization","site_admin":false},"repo":{"id":67581740,"node_id":"MDEwOlJlcG9zaXRvcnk2NzU4MTc0MA==","name":"sentialabs.github.io","full_name":"sentialabs/sentialabs.github.io","owner":{"login":"sentialabs","id":21081697,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxMDgxNjk3","avatar_url":"https://avatars3.githubusercontent.com/u/21081697?v=4","gravatar_id":"","url":"https://api.github.com/users/sentialabs","html_url":"https://github.com/sentialabs","followers_url":"https://api.github.com/users/sentialabs/followers","following_url":"https://api.github.com/users/sentialabs/following{/other_user}","gists_url":"https://api.github.com/users/sentialabs/gists{/gist_id}","starred_url":"https://api.github.com/users/sentialabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sentialabs/subscriptions","organizations_url":"https://api.github.com/users/sentialabs/orgs","repos_url":"https://api.github.com/users/sentialabs/repos","events_url":"https://api.github.com/users/sentialabs/events{/privacy}","received_events_url":"https://api.github.com/users/sentialabs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sentialabs/sentialabs.github.io","description":"Sentia Labs website","fork":false,"url":"https://api.github.com/repos/sentialabs/sentialabs.github.io","forks_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/forks","keys_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/teams","hooks_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/hooks","issue_events_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/events","assignees_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/tags","blobs_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/languages","stargazers_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/stargazers","contributors_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/contributors","subscribers_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/subscribers","subscription_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/subscription","commits_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/merges","archive_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/downloads","issues_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/labels{/name}","releases_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/deployments","created_at":"2016-09-07T07:13:16Z","updated_at":"2018-09-04T14:08:31Z","pushed_at":"2018-09-04T16:08:38Z","git_url":"git://github.com/sentialabs/sentialabs.github.io.git","ssh_url":"git@github.com:sentialabs/sentialabs.github.io.git","clone_url":"https://github.com/sentialabs/sentialabs.github.io.git","svn_url":"https://github.com/sentialabs/sentialabs.github.io","homepage":null,"size":6905,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4,"mirror_url":null,"archived":false,"open_issues_count":1,"license":null,"forks":4,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"sentialabs:master","ref":"master","sha":"25e27a06f17cb890864a4d1a4f7c73cf1965ff83","user":{"login":"sentialabs","id":21081697,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxMDgxNjk3","avatar_url":"https://avatars3.githubusercontent.com/u/21081697?v=4","gravatar_id":"","url":"https://api.github.com/users/sentialabs","html_url":"https://github.com/sentialabs","followers_url":"https://api.github.com/users/sentialabs/followers","following_url":"https://api.github.com/users/sentialabs/following{/other_user}","gists_url":"https://api.github.com/users/sentialabs/gists{/gist_id}","starred_url":"https://api.github.com/users/sentialabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sentialabs/subscriptions","organizations_url":"https://api.github.com/users/sentialabs/orgs","repos_url":"https://api.github.com/users/sentialabs/repos","events_url":"https://api.github.com/users/sentialabs/events{/privacy}","received_events_url":"https://api.github.com/users/sentialabs/received_events","type":"Organization","site_admin":false},"repo":{"id":67581740,"node_id":"MDEwOlJlcG9zaXRvcnk2NzU4MTc0MA==","name":"sentialabs.github.io","full_name":"sentialabs/sentialabs.github.io","owner":{"login":"sentialabs","id":21081697,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxMDgxNjk3","avatar_url":"https://avatars3.githubusercontent.com/u/21081697?v=4","gravatar_id":"","url":"https://api.github.com/users/sentialabs","html_url":"https://github.com/sentialabs","followers_url":"https://api.github.com/users/sentialabs/followers","following_url":"https://api.github.com/users/sentialabs/following{/other_user}","gists_url":"https://api.github.com/users/sentialabs/gists{/gist_id}","starred_url":"https://api.github.com/users/sentialabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sentialabs/subscriptions","organizations_url":"https://api.github.com/users/sentialabs/orgs","repos_url":"https://api.github.com/users/sentialabs/repos","events_url":"https://api.github.com/users/sentialabs/events{/privacy}","received_events_url":"https://api.github.com/users/sentialabs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sentialabs/sentialabs.github.io","description":"Sentia Labs website","fork":false,"url":"https://api.github.com/repos/sentialabs/sentialabs.github.io","forks_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/forks","keys_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/teams","hooks_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/hooks","issue_events_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/events","assignees_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/tags","blobs_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/languages","stargazers_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/stargazers","contributors_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/contributors","subscribers_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/subscribers","subscription_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/subscription","commits_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/merges","archive_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/downloads","issues_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/labels{/name}","releases_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/sentialabs/sentialabs.github.io/deployments","created_at":"2016-09-07T07:13:16Z","updated_at":"2018-09-04T14:08:31Z","pushed_at":"2018-09-04T16:08:38Z","git_url":"git://github.com/sentialabs/sentialabs.github.io.git","ssh_url":"git@github.com:sentialabs/sentialabs.github.io.git","clone_url":"https://github.com/sentialabs/sentialabs.github.io.git","svn_url":"https://github.com/sentialabs/sentialabs.github.io","homepage":null,"size":6905,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4,"mirror_url":null,"archived":false,"open_issues_count":1,"license":null,"forks":4,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/34"},"html":{"href":"https://github.com/sentialabs/sentialabs.github.io/pull/34"},"issue":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues/34"},"comments":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/issues/34/comments"},"review_comments":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/34/comments"},"review_comment":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/pulls/34/commits"},"statuses":{"href":"https://api.github.com/repos/sentialabs/sentialabs.github.io/statuses/e386b43fa3a274af4456d5440fdf588610a54bf6"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"donkersgoed"}}