<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Software Engineering Stories</title>
<description>Software Engineer</description>
<link>https://stories.rdok.co.uk/</link>
<atom:link href="https://stories.rdok.co.uk/sitemap.xml" rel="self" type="application/rss+xml"/>
<pubDate>Wed, 07 Jul 2021 20:03:03 +0100</pubDate>
<lastBuildDate>Wed, 07 Jul 2021 20:03:03 +0100</lastBuildDate>
<generator>Jekyll v4.2.0</generator>

<item>
    <title>Aurora for serverless Laravel</title>
    <description>&lt;p&gt;&lt;a href=&quot;/assets/images/posts/aurora-for-serverless-laravel.jpg&quot; title=&quot;Screenshot Showcase&quot;&gt;&lt;img src=&quot;/assets/images/posts/aurora-for-serverless-laravel.jpg&quot; alt=&quot;Aurora for serverless Laravel&quot; title=&quot;Screenshot Showcase&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://aurora-for-serverless-laravel.rdok.co.uk/&quot; class=&quot;btn btn--md&quot;&gt;Production Showcase&lt;/a&gt;
&lt;a href=&quot;https://github.com/rdok/aurora-for-serverless-laravel&quot; class=&quot;btn btn--md&quot;&gt;Source Code&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;the-why-the-how-and-the-what&quot;&gt;The why, the how and the what&lt;/h1&gt;
&lt;blockquote&gt;
  &lt;p&gt;An open source, alternative to Laravel Vapor.  For instructions for how to this up, see &lt;a href=&quot;https://stories.rdok.co.uk/2021/06/serverless-laravel/#setup&quot;&gt;https://stories.rdok.co.uk/2021/06/serverless-laravel/#setup&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use this repository to automate creating a serverless Laravel with Aurora MySQL environment.&lt;/p&gt;

&lt;p&gt;It uses AWS SAM with GitHub CI/CD actions to create the AWS resources, including the Aurora &amp;amp; Laravel Lambdas.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;aurora&quot;&gt;Aurora&lt;/h2&gt;
&lt;p&gt;Aurora has the capability to auto pause after a given amount of time with no activity. During testing, it took around 30s for it to resume after paused.&lt;/p&gt;

&lt;h2 id=&quot;private-links&quot;&gt;Private Links&lt;/h2&gt;
&lt;p&gt;Private Link VPC endpoints are a better alternative to NAT Gateways. Laravel lambdas use said private links to connect to Aurora, S3, and Secrets Manager AWS resources.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Network bandwidth is cheaper than NAT Gateway by four times.&lt;/li&gt;
  &lt;li&gt;More secure than NAT Gateway as the connection always stays in AWS private network.&lt;/li&gt;
  &lt;li&gt;Laravel &amp;amp; artisan fetch the secrets with database credentials on each cycle using a custom &lt;a href=&quot;https://github.com/rdok/aurora-for-serverless-laravel/blob/070f6cd0f1bddae448628073598648860a84a289/laravel/app/Providers/AuroraSecretsServiceProvider.php#L35&quot;&gt;provider&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;AWS does not allow duplicate private links DNS for secrets manager. As such, said private link are re-used for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;secrets&quot;&gt;Secrets&lt;/h2&gt;
&lt;p&gt;Using IAM auth the Laravel lambda would not need to use a password when connecting to the database. This would also remove the need to rotate the database secrets. However, Aurora does not support IAM auth. Instead, &lt;a href=&quot;https://github.com/rdok/aurora-for-serverless-laravel/blob/070f6cd0f1bddae448628073598648860a84a289/infrastructure-aurora.yaml#L12&quot;&gt;SecretsManager&lt;/a&gt; are used.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Secrets rotation goes outside the scope of this repo. It requires creating a lambda to do so.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;cicd&quot;&gt;CI/CD&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;The CI/CD user &lt;a href=&quot;https://github.com/rdok/aurora-for-serverless-laravel/blob/070f6cd0f1bddae448628073598648860a84a289/.github/workflows/deploy.yml#L57&quot;&gt;assumes&lt;/a&gt; the role with &lt;a href=&quot;https://github.com/rdok/cicd-iam-terraform/blob/8a8c4242a46acece0d5df863c4a501561c764beb/aurora-for-serverless-laravel.tf#L1&quot;&gt;relevant authorisation&lt;/a&gt; for creating the relevant resources.&lt;/li&gt;
  &lt;li&gt;For a new environment it takes ~15 minutes to build everything, while ~2 minutes to re-deploy any simple change.&lt;/li&gt;
  &lt;li&gt;Migrations automatically run through the Laravel artisan lambda on each deployment.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; environment is auto deployed for any change on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; branch.&lt;/li&gt;
  &lt;li&gt;Developers have to manually trigger &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; environment deployments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;costs&quot;&gt;Costs&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Cost almost 2 dollars every day for the private link VPC endpoints. Minor to no costs for Aurora when paused.&lt;/li&gt;
  &lt;li&gt;To ensure low costs, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; environment is auto deleted every midnight.&lt;/li&gt;
&lt;/ul&gt;

</description>
    <pubDate>Wed, 07 Jul 2021 00:00:00 +0100</pubDate>
    <link>https://stories.rdok.co.uk/2021/07/aurora-for-serverless-laravel/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2021/07/aurora-for-serverless-laravel/</guid>
    
    <category>laravel</category>
    
    <category>serverless</category>
    
    <category>cicd</category>
    
    <category>aws</category>
    
    <category>aurora</category>
    
    
    <category>serverless</category>
    
    <category>laravel</category>
    
    <category>aurora</category>
    
</item>

<item>
    <title>Serverless Laravel</title>
    <description>&lt;p&gt;&lt;a href=&quot;/assets/images/posts/serverless-laravel.jpg&quot; title=&quot;Screenshot Showcase&quot;&gt;&lt;img src=&quot;/assets/images/posts/serverless-laravel.jpg&quot; alt=&quot;AWS SAM GraphQL Design&quot; title=&quot;Screenshot Showcase&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://serverless-laravel.rdok.co.uk/&quot; class=&quot;btn btn--md&quot;&gt;Production Showcase&lt;/a&gt;
&lt;a href=&quot;https://github.com/rdok/serverless-laravel&quot; class=&quot;btn btn--md&quot;&gt;Source Code&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;the-why-the-how-and-the-what&quot;&gt;The why, the how and the what&lt;/h1&gt;

&lt;p&gt;Use this repository when you want to automate creating a serverless Laravel environment.&lt;/p&gt;

&lt;p&gt;It uses AWS SAM to create the AWS resources including:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Route 53 entries &amp;amp;&amp;amp; SSL certificates&lt;/li&gt;
  &lt;li&gt;CloudFront&lt;/li&gt;
  &lt;li&gt;API Gateway for Lambda &amp;amp; Artisan&lt;/li&gt;
  &lt;li&gt;Assets S3 bucket for static files&lt;/li&gt;
  &lt;li&gt;Storage S3 bucket for internal files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also makes use of GitHub actions for CI/CD automation.&lt;/p&gt;

&lt;h1 id=&quot;setup&quot;&gt;Setup&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;Fork &lt;a href=&quot;https://github.com/rdok/serverless-laravel/fork&quot;&gt;rdok/serverless-laravel&lt;/a&gt; template&lt;/li&gt;
  &lt;li&gt;Update the repository &lt;a href=&quot;/assets/images/posts/github-secrets-showcase.jpg&quot; title=&quot;GitHub Secrets Showcase&quot;&gt;secrets&lt;/a&gt; with AWS credentials &amp;amp; others.&lt;/li&gt;
  &lt;li&gt;Update environment variables of &lt;a href=&quot;/assets/images/posts/artisan_yml.jpg&quot; title=&quot;artisan.yml image&quot;&gt;artisan.yml&lt;/a&gt; &amp;amp; &lt;a href=&quot;/assets/images/posts/deploy_yml.jpg&quot; title=&quot;deploy.yml image&quot;&gt;deploy.yml&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;The IAM policies required for the CI/CD user are outside the scope of this post. However, you can reference the automated policies for the showcase &lt;a href=&quot;https://github.com/rdok/cicd-iam-terraform/blob/main/serverless-laravel.tf&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Deploy using &lt;a href=&quot;/assets/images/posts/github-action-deploy.jpg&quot; title=&quot;GitHub Action Deploy&quot;&gt;Deploy&lt;/a&gt; Github action.&lt;/li&gt;
  &lt;li&gt;Run artisan commands using the &lt;a href=&quot;/assets/images/posts/github-action-artisan.jpg&quot; title=&quot;GitHub Action Artisan&quot;&gt;Artisan&lt;/a&gt; GitHub action.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;pre-requisites&quot;&gt;Pre-requisites&lt;/h1&gt;
&lt;blockquote&gt;
  &lt;p&gt;To automate the creation of the subdomain &amp;amp; it’s certificate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An existing base domain &amp;amp; wild card certificate.&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/.github/workflows/deploy.yml&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    .github/workflows/deploy.yml &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/.github/workflows/deploy.yml&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/serverless-laravel/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/.github/workflows/deploy.yml&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;BASE_DOMAIN_ROUTE_53_HOSTED_ZONE_ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.BASE_DOMAIN_ROUTE_53_HOSTED_ZONE_ID }}&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;BASE_DOMAIN_WILD_CARD_CERTIFICATE_ARN&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.BASE_DOMAIN_WILD_CARD_CERTIFICATE_ARN }}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;future-additions&quot;&gt;Future additions&lt;/h1&gt;
&lt;p&gt;The following Laravel features will be supported in future posts:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Aurora serverless MySQL database&lt;/li&gt;
  &lt;li&gt;Broadcasting through SNS&lt;/li&gt;
  &lt;li&gt;Job dispatching through SQS&lt;/li&gt;
  &lt;li&gt;Scheduling through additional Lambda&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;environment-variables&quot;&gt;Environment variables&lt;/h1&gt;
&lt;p&gt;Instruct Laravel how to load static assets &amp;amp; access storage bucket:&lt;/p&gt;
&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/template.yaml&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    template.yaml &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/template.yaml&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/serverless-laravel/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/template.yaml&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;Variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;APP_KEY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AppKey&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;APP_STORAGE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/tmp&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;AWS_PUBLIC_BUCKET&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Assets&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;MIX_ASSET_URL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Join&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://'&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;DomainName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/assets'&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ASSET_URL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Join&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://'&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;DomainName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/assets'&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;LOG_CHANNEL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;stderr&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;FILESYSTEM_DRIVER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;s3&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;AWS_BUCKET&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Storage&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;domain-certificate&quot;&gt;Domain Certificate&lt;/h1&gt;
&lt;p&gt;CloudFront only supports ACM certificates in the US East (N. Virginia) Region. As such, a separate stack is created in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;us-east-1&lt;/code&gt; region.&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/template-certificate.yml&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    template-certificate.yml &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/template-certificate.yml&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/serverless-laravel/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/template-certificate.yml&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;Certificate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::CertificateManager::Certificate&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ValidationMethod&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DNS&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DomainValidationOptions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;DomainName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DomainName&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;HostedZoneId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;BaseDomainRoute53HostedZoneId&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DomainName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!Ref&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DomainName&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;storage&quot;&gt;Storage&lt;/h1&gt;
&lt;p&gt;Instruct Laravel to properly get AWS credentials by adding the AWS session token:&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/e53712f686af121ad67c452514fdd9d270ef07f8/laravel/config/filesystems.php&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    laravel/config/filesystems.php &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/e53712f686af121ad67c452514fdd9d270ef07f8/laravel/config/filesystems.php&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/serverless-laravel/e53712f686af121ad67c452514fdd9d270ef07f8/laravel/config/filesystems.php&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&lt;span class=&quot;s1&quot;&gt;'token'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'AWS_SESSION_TOKEN'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;cicd&quot;&gt;CI/CD&lt;/h1&gt;
&lt;p&gt;CI/CD uses AWS SAM to build &amp;amp; deploy AWS resources:&lt;/p&gt;
&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/.github/workflows/deploy.yml&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    .github/workflows/deploy.yml &lt;a href=&quot;https://github.com/rdok/serverless-laravel/blob/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/.github/workflows/deploy.yml&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/serverless-laravel/daa833b68ee1aefa8f42ec2a3059c60d1d28d528/.github/workflows/deploy.yml&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;s&quot;&gt;sam deploy \&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;--region &quot;eu-west-1&quot; \&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;--s3-bucket &quot;${{ steps.env.outputs.cicd-bucket-laravel }}&quot; \&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;--s3-prefix &quot;${NAME}&quot; \&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;--stack-name &quot;${{ steps.env.outputs.stack-laravel-name }}&quot; \&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;--capabilities CAPABILITY_IAM \&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;--no-fail-on-empty-changeset \&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;--no-confirm-changeset \&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;--parameter-overrides \&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;CertificateARN=&quot;${CERTIFICATE_ARN}&quot; \&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;AppKey=&quot;${APP_KEY}&quot; \&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;DomainName=&quot;${{ steps.env.outputs.domain-name }}&quot; \&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;WildcardCertificateARN=&quot;${BASE_DOMAIN_WILD_CARD_CERTIFICATE_ARN}&quot; \&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;BaseDomainRoute53HostedZoneId=&quot;${BASE_DOMAIN_ROUTE_53_HOSTED_ZONE_ID}&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
    <pubDate>Sat, 12 Jun 2021 00:00:00 +0100</pubDate>
    <link>https://stories.rdok.co.uk/2021/06/serverless-laravel/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2021/06/serverless-laravel/</guid>
    
    <category>laravel</category>
    
    <category>serverless</category>
    
    <category>cicd</category>
    
    <category>aws</category>
    
    <category>bref</category>
    
    
    <category>serverless</category>
    
    <category>laravel</category>
    
</item>

<item>
    <title>Cyberpunk 2077 R6 Config Editor</title>
    <description>&lt;p&gt;&lt;a href=&quot;/assets/images/posts/cyberpunk2077-r6-config-editor.jpg&quot; title=&quot;GUI Showcase&quot;&gt;&lt;img src=&quot;/assets/images/posts/cyberpunk2077-r6-config-editor.jpg&quot; alt=&quot;GUI Showcase&quot; title=&quot;GUI Showcase&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cyberpunk2077 R6 Config editor, is a Windows app built on Python.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/rdok/cyberpunk2077-r6-config-editor/releases/tag/v0.9.1&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Download&amp;nbsp;Latest&amp;nbsp;Release-blue?style=flat-square&amp;amp;logo=Windows&quot; alt=&quot;testing-site&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://github.com/rdok/cyberpunk2077-r6-config-editor&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Source&amp;nbsp;Code-green?style=flat-square&amp;amp;logo=GitHub&quot; alt=&quot;code&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://www.nexusmods.com/cyberpunk2077/mods/341&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Nexus-Mods-f6a234?style=flat-square&amp;amp;logo=data:image/png;base64,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&quot; alt=&quot;nexus-mods&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;why--how&quot;&gt;Why &amp;amp; How&lt;/h1&gt;

&lt;p&gt;Use this app to customize your &lt;a href=&quot;https://www.cyberpunk.net/gb/en/&quot;&gt;Cyberpunk 2077&lt;/a&gt; experience and immersion.&lt;/p&gt;

&lt;p&gt;It modifies the game configuration files for player movement &amp;amp; other gameplay characteristics.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;tdd&quot;&gt;TDD&lt;/h1&gt;
&lt;p&gt;I build this app entirely using test driven development, since the first code line.&lt;/p&gt;

&lt;p&gt;A huge advantage, shown almost immediately, when having to re-design the architecture due to additional unexpected complex customisations.&lt;/p&gt;

&lt;p&gt;Without the tests, there would have been less freedom to change the architecture to conform with new requirements.&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/cyberpunk2077-r6-config-editor/blob/d4149e91bbeeb641c7e8f85be468085b251c6ae7/tests/test_main.py&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    tests/test_main.py &lt;a href=&quot;https://github.com/rdok/cyberpunk2077-r6-config-editor/blob/d4149e91bbeeb641c7e8f85be468085b251c6ae7/tests/test_main.py&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/cyberpunk2077-r6-config-editor/d4149e91bbeeb641c7e8f85be468085b251c6ae7/tests/test_main.py&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_it_renders_the_gui&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gui&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mainloop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;assert_called_once&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_it_renders_remap_walk_frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gui&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;render_walk_frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;assert_called_once&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_it_renders_crafting_frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gui&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;render_crafting_frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;assert_called_once&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;cicd&quot;&gt;CI/CD&lt;/h1&gt;
&lt;p&gt;The CI/CD pipeline runs on GitHub’s Windows 2019 servers.&lt;/p&gt;

&lt;p&gt;It consists of running linter, unit tests, building the pyinstaller and the Windows executable app.&lt;/p&gt;

&lt;p&gt;It builds pyinstaller natively, to lower the probability of &lt;a href=&quot;https://www.virustotal.com/gui/file/553fd791282a21e111309a8436fe5a0737311944af7c2758101aa1150f1a2bfd/detection&quot;&gt;TotalVirus Scanning&lt;/a&gt; marking it as false-positive virus. A requirement from NexusMod &lt;a href=&quot;https://www.nexusmods.com/news/12378&quot;&gt;commitment&lt;/a&gt; to testing each file uploaded against viruses.&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/cyberpunk2077-r6-config-editor/blob/d4149e91bbeeb641c7e8f85be468085b251c6ae7/.github/workflows/cd.yml&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    .github/workflows/cd.yml &lt;a href=&quot;https://github.com/rdok/cyberpunk2077-r6-config-editor/blob/d4149e91bbeeb641c7e8f85be468085b251c6ae7/.github/workflows/cd.yml&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/cyberpunk2077-r6-config-editor/d4149e91bbeeb641c7e8f85be468085b251c6ae7/.github/workflows/cd.yml&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build pyinstaller&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
   &lt;span class=&quot;s&quot;&gt;python -m pip install --upgrade pip&lt;/span&gt;
   &lt;span class=&quot;s&quot;&gt;git clone --depth 1 --branch v4.1 https://github.com/pyinstaller/pyinstaller&lt;/span&gt;
   &lt;span class=&quot;s&quot;&gt;cd .\pyinstaller\bootloader\&lt;/span&gt;
   &lt;span class=&quot;s&quot;&gt;python ./waf all&lt;/span&gt;
   &lt;span class=&quot;s&quot;&gt;cd ..&lt;/span&gt;
   &lt;span class=&quot;s&quot;&gt;python setup.py install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
    <pubDate>Sat, 05 Jun 2021 00:00:00 +0100</pubDate>
    <link>https://stories.rdok.co.uk/2021/06/cyberpunk2077-r6-config-editor/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2021/06/cyberpunk2077-r6-config-editor/</guid>
    
    <category>windows</category>
    
    <category>python</category>
    
    <category>cicd</category>
    
    <category>tdd</category>
    
    <category>cyberpunk2077</category>
    
    
    <category>cyberpunk2077</category>
    
</item>

<item>
    <title>Serverless GraphQL Implementation</title>
    <description>&lt;p&gt;&lt;a href=&quot;/assets/images/posts/serverless-graphql.jpg&quot; title=&quot;GUI Showcase&quot;&gt;&lt;img src=&quot;/assets/images/posts/serverless-graphql.jpg&quot; alt=&quot;Infrastructure&quot; title=&quot;GUI Showcase&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a serverless GraphQL implementation ready production, including CI/CD.&lt;/p&gt;

&lt;p&gt;Developers should be able to just clone, add AWS credentials, and deploy away using GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://kngg029ho4.execute-api.eu-west-1.amazonaws.com/Prod/graphql&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/GraphQL&amp;nbsp;Playground-purple?style=flat-square&amp;amp;logo=amazon-aws&quot; alt=&quot;AWS Playground&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://github.com/rdok/serverless-graphql&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Source&amp;nbsp;Code-green?style=flat-square&amp;amp;logo=GitHub&quot; alt=&quot;GitHub Repo&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;implementation-details&quot;&gt;Implementation details&lt;/h1&gt;

&lt;p&gt;Use &lt;a href=&quot;https://www.npmjs.com/package/apollo-server-lambda&quot;&gt;apollo-server-lambda&lt;/a&gt; npm package to serve the Apollo GraphQL through AWS Lambda.&lt;br /&gt;
It exposes the API to the &lt;a href=&quot;https://www.apollographql.com/&quot;&gt;Apollo&lt;/a&gt; implementation.&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/serverless-graphql/blob/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/src/main.js#L1&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    src/main.js#L1 &lt;a href=&quot;https://github.com/rdok/serverless-graphql/blob/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/src/main.js#L1&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/serverless-graphql/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/src/main.js#L1&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ApolloServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;gql&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;apollo-server-lambda&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;graphql-playground&quot;&gt;GraphQL playground&lt;/h3&gt;
&lt;blockquote&gt;
  &lt;p&gt;The &lt;a href=&quot;https://www.apollographql.com/docs/apollo-server/testing/graphql-playground/&quot;&gt;playground&lt;/a&gt; provides a GUI for making GraphQL requests. E.g. &lt;a href=&quot;https://docs.github.com/en/graphql/overview/explorer&quot;&gt;GitHub’s Playground&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When running locally, the playground is enabled by default. However, if you wish to have the playground available when deploying to AWS infrastructure, there is an additional step to take.&lt;br /&gt;
Instruct the Lambda, when the requested HTTP method is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt;, to server playground UI:&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/serverless-graphql/blob/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/src/main.js&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    src/main.js &lt;a href=&quot;https://github.com/rdok/serverless-graphql/blob/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/src/main.js&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/serverless-graphql/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/src/main.js&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;requestContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;lambdaContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;cors&quot;&gt;CORS&lt;/h2&gt;
&lt;p&gt;Modify the AWS SAM template to allow all CORS.&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/serverless-graphql/blob/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/template.yml#L5&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    template.yml#L5 &lt;a href=&quot;https://github.com/rdok/serverless-graphql/blob/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/template.yml#L5&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/serverless-graphql/05ba96ad3f1cb2a9821d46439454b4afe7ebe4a7/template.yml#L5&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;ApiGatewayApi&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::Serverless::Api&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;StageName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Prod&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Cors&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'*'&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
    <pubDate>Sat, 29 May 2021 00:00:00 +0100</pubDate>
    <link>https://stories.rdok.co.uk/2021/05/serverless-graphql/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2021/05/serverless-graphql/</guid>
    
    <category>nodejs</category>
    
    <category>aws-sam</category>
    
    <category>lambda</category>
    
    <category>graphql</category>
    
    <category>api-gateway</category>
    
    <category>apollo</category>
    
    <category>minimalistic</category>
    
    <category>serverless</category>
    
    
    <category>serverless</category>
    
</item>

<item>
    <title>Minimalistic AWS SAM PHP</title>
    <description>&lt;p&gt;&lt;a href=&quot;/assets/images/posts/aws-sam-php.jpg&quot; title=&quot;Infrastructure Showcase&quot;&gt;&lt;img src=&quot;/assets/images/posts/aws-sam-php.jpg&quot; alt=&quot;AWS SAM PHP&quot; title=&quot;Infrastructure Showcase&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Automates a serverless PHP Lambda implementation ready production, including CI/CD.&lt;/p&gt;

&lt;p&gt;The PHP Lambda function generates a JSON response &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;status&quot;:&quot;OK.&quot;}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://tmxh5kyio7.execute-api.eu-west-1.amazonaws.com/Prod/&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/API&amp;nbsp;Gateway&amp;nbsp;Showcase-purple?style=flat-square&amp;amp;logo=amazon-aws&quot; alt=&quot;Showcase&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://github.com/rdok/aws-sam-php&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Source&amp;nbsp;Code-green?style=flat-square&amp;amp;logo=GitHub&quot; alt=&quot;code&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;when--why&quot;&gt;When &amp;amp; Why&lt;/h1&gt;
&lt;p&gt;Use this as a template to quickstart a serverless PHP based service.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h3 id=&quot;implementation-details&quot;&gt;Implementation Details&lt;/h3&gt;

&lt;p&gt;Use &lt;a href=&quot;https://runtimes.bref.sh/&quot;&gt;Bref Runtimes&lt;/a&gt; Layers, for running PHP on Lambda.&lt;/p&gt;

&lt;div class=&quot;github-sample-reference&quot;&gt;
  &lt;div class=&quot;author-info&quot;&gt;
    &lt;a href=&quot;https://github.com/rdok/aws-sam-php/blob/813eef2369bcbb4e69fd7d98f553102146291d0e/template.yaml&quot;&gt;This Github Sample&lt;/a&gt; is by &lt;a href=&quot;https://github.com/rdok&quot;&gt;rdok&lt;/a&gt;
  &lt;/div&gt;
  &lt;div class=&quot;meta-info&quot;&gt;
    template.yaml &lt;a href=&quot;https://github.com/rdok/aws-sam-php/blob/813eef2369bcbb4e69fd7d98f553102146291d0e/template.yaml&quot;&gt;view&lt;/a&gt; &lt;a href=&quot;https://raw.githubusercontent.com/rdok/aws-sam-php/813eef2369bcbb4e69fd7d98f553102146291d0e/template.yaml&quot;&gt;raw&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AWS::Serverless::Function&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;CodeUri&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;src/&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Layers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:lambda:eu-west-1:209497400698:layer:php-80:9&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;main.php&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;composer&quot;&gt;Composer&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bref/bref&lt;/code&gt; composer package is &lt;a href=&quot;https://github.com/rdok/aws-sam-php/blob/813eef2369bcbb4e69fd7d98f553102146291d0e/src/composer.json#L9&quot;&gt;required&lt;/a&gt; to create communication between the PHP and the Lambda API.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer.json&lt;/code&gt; &amp;amp; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer.lock&lt;/code&gt; should be added on the same level as the entry php file.&lt;/li&gt;
  &lt;li&gt;Finally &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer install&lt;/code&gt; should be run before deploying the infrastructure.&lt;/li&gt;
&lt;/ul&gt;

</description>
    <pubDate>Sat, 22 May 2021 00:00:00 +0100</pubDate>
    <link>https://stories.rdok.co.uk/2021/05/aws-sam-php/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2021/05/aws-sam-php/</guid>
    
    <category>aws-sam</category>
    
    <category>lambda</category>
    
    <category>php</category>
    
    <category>api-gateway</category>
    
    
    <category>aws-sam</category>
    
</item>

<item>
    <title>What are the least privileges required for AWS SAM?</title>
    <description>&lt;p&gt;&lt;img src=&quot;/assets/images/posts/aws-sam-and-iam-logo.jpeg&quot; alt=&quot;AWS SAM and IAM Logo&quot; title=&quot;AWS SAM and IAM Logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When setting up IAM users for continuous delivery it is difficult to find privileges required using the failed AWS SAM deployment logs. 
This is due to these errors being too generic, and not specifying the exact resource. 
This page documents the basic privileges for running AWS SAM with a Lambda, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAPABILITY_IAM&lt;/code&gt; capability.&lt;/p&gt;

&lt;h2 id=&quot;policy&quot;&gt;Policy&lt;/h2&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2012-10-17&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Statement&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Sid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;VisualEditor0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Effect&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Allow&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudformation:CreateChangeSet&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudformation:GetTemplateSummary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudformation:DescribeStacks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudformation:DescribeStackEvents&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudformation:DeleteStack&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudformation:DescribeChangeSet&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudformation:ExecuteChangeSet&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:AttachRolePolicy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:CreateRole&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:DeleteRole&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:GetRole&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:UntagRole&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:ListRoleTags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:TagRole&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:PassRole&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;iam:DetachRolePolicy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lambda:GetFunction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lambda:CreateFunction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lambda:DeleteFunction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lambda:GetFunctionConfiguration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;s3:PutObject&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;s3:GetObject&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Resource&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arn:aws:cloudformation:((region)):((account-id)):stack/((stack-name))/*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arn:aws:cloudformation:((region)):aws:transform/Serverless-2016-10-31&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arn:aws:iam::((account-id)):role/((stack-name))-*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arn:aws:lambda:((region)):((account-id)):function:((stack-name))-*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arn:aws:s3:::((deployment-bucket))/*&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
    <pubDate>Sun, 22 Nov 2020 00:00:00 +0000</pubDate>
    <link>https://stories.rdok.co.uk/2020/11/aws-sam-least-priveleges/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2020/11/aws-sam-least-priveleges/</guid>
    
    <category>AWS IAM</category>
    
    <category>CI/CD</category>
    
    <category>Least Privileges</category>
    
    <category>How To</category>
    
    
    <category>how-to</category>
    
</item>

<item>
    <title>How to find the least privileges for AWS IAM policies</title>
    <description>&lt;h2 id=&quot;context--approach&quot;&gt;Context &amp;amp; Approach&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/aws-iam-logo.png&quot; alt=&quot;AWS IAM Logo&quot; title=&quot;AWS IAM Logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;An effective approach to finding the least privileges for AWS IAM policies is to use AWS CloudTrail. 
This page provides a detailed how-to; useful for continuous delivery IAM users.&lt;/p&gt;

&lt;h3 id=&quot;approach&quot;&gt;Approach&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Use a programmatic access only user, with full permissions. This is to filter out events that trigger from AWS console interaction.&lt;/li&gt;
  &lt;li&gt;Deploy your CloudFormation infrastructure using this user from your development machine.
    &lt;blockquote&gt;
      &lt;p&gt;⚠ It takes around 10 minutes for these logs to be shown in the AWS CloudTrail. So take a coffee in the meantime.&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;Visit AWS CloudTrail console, and limit results to the time period since you started the deployment. 
  This ensures you’re not getting unrelated logs.&lt;/li&gt;
  &lt;li&gt;Filter by username instead of AWS KEY for immediate results. The AWS Key responds with inconsistent results. 
  The probable cause is the eventual consistency. 
  Waiting some time, it starts showing results with AWS Key as well.&lt;/li&gt;
  &lt;li&gt;Download all the events as CSV; open said file, and order records by column &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event name&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Start adding permission using these event names
    &lt;ul&gt;
      &lt;li&gt;As per AWS recommended best practise, limit scope to relevant environments.
        &lt;ul&gt;
          &lt;li&gt;E.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arn:aws:cloudformation:us-east-1:000000000000:stack/name-production-stories/*&lt;/code&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Categorise permission based on their logical names. This helps with creating a more logical picture in your mind.&lt;/li&gt;
      &lt;li&gt;Use inline policies, and if limit reached, start extracting them to normal policies. Saves some time.&lt;/li&gt;
      &lt;li&gt;When you need a specific resource, go back to CloudTrail, find the event name. Expanding said log, the event source should have the data in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestParameters&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;As you add the permission, delete the event records from the CSV. 
 This greatly speeds up finding events that haven’t been added yet, as there can be dozens of duplicate events.&lt;/li&gt;
  &lt;li&gt;Remember to also destroy the stack, to find the relevant access policies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, while still using your development machine, 
 do a final deployment with a user having the new privileges you added on the previous steps. 
 This catches any missing events not logged in CloudTrail, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TagResource&lt;/code&gt;. 
 And it’s the fastest way to trigger &amp;amp; complete the deployment.&lt;/p&gt;
</description>
    <pubDate>Sat, 21 Nov 2020 00:00:00 +0000</pubDate>
    <link>https://stories.rdok.co.uk/2020/11/least-priveleges-for-iam-policies/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2020/11/least-priveleges-for-iam-policies/</guid>
    
    <category>AWS IAM</category>
    
    <category>CI/CD</category>
    
    <category>Least Privileges</category>
    
    <category>CloudTrail</category>
    
    <category>How To</category>
    
    
    <category>how-to</category>
    
</item>

<item>
    <title>Server Testing CI</title>
    <description>&lt;h1 id=&quot;pre-requisite-setup-and-tests&quot;&gt;Pre-requisite, setup, and tests&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/server-tests-ci-jenkins-build-history-and-stage-view.jpg&quot; alt=&quot;Jenkins Build Page showcasing quest completion&quot; title=&quot;Jenkins Build Page showcasing quest completion&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You’ve been given the quest to automate the process of maintaining a web server’s configurations.&lt;/p&gt;

&lt;p&gt;Redirecting www to non-www pages, http to https pages, and a simple health check.&lt;/p&gt;

&lt;p&gt;Completing this quest, rewards you with the automated testing process notifying you when the web server 
configurations misbehave.&lt;/p&gt;

&lt;p&gt;You decide to complete this quest by setting up a &lt;a href=&quot;https://jenkins.io/doc/book/pipeline/#overview&quot;&gt;Jenkins Pipeline&lt;/a&gt;.
 It will run the server tests, and notify you via emails.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#pre-requisite-setup-and-tests&quot; id=&quot;markdown-toc-pre-requisite-setup-and-tests&quot;&gt;Pre-requisite, setup, and tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pre-requisites&quot; id=&quot;markdown-toc-pre-requisites&quot;&gt;Pre-requisites&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#setup-pipeline&quot; id=&quot;markdown-toc-setup-pipeline&quot;&gt;Setup Pipeline&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#server-tests&quot; id=&quot;markdown-toc-server-tests&quot;&gt;Server Tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#jenkinsfile&quot; id=&quot;markdown-toc-jenkinsfile&quot;&gt;Jenkinsfile&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#summary&quot; id=&quot;markdown-toc-summary&quot;&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;pre-requisites&quot;&gt;Pre-requisites&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04&quot;&gt;Nginx&lt;/a&gt;, &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-install-jenkins-on-ubuntu-16-04&quot;&gt;Jenkins&lt;/a&gt;, &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-16-04&quot;&gt;Docker&lt;/a&gt;, Jenkins SSH Credentials, Jenkins Secret text for webhook&lt;/p&gt;

&lt;h1 id=&quot;setup-pipeline&quot;&gt;Setup Pipeline&lt;/h1&gt;
&lt;p&gt;You login to your Jenkins server. Create a new item, name it, and select Pipeline type.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[ Jenkins -&amp;gt; New Item -&amp;gt; Pipeline &amp;amp; Name -&amp;gt; OK ]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You now find yourself on the configuration page.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Discard old builds&lt;/code&gt; checkbox, to preserve disk space as your current environment has up to 20GB. You also select 30 days to keep builds, and up to 100 builds to keep.&lt;/li&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Do not allow concurrent builds&lt;/code&gt; to maintain good server performance.&lt;/li&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pipeline speed/durability/override&lt;/code&gt; checkbox, and select the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Performance-optimized&lt;/code&gt; custom pipeline 
level for fast builds.&lt;/li&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GitHub hook trigger for GITScm polling&lt;/code&gt; checkbox. Thus you enable the webhook feature.&lt;/li&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Build periodically&lt;/code&gt; checkbox
    &lt;ul&gt;
      &lt;li&gt;You insert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H 19 * * *&lt;/code&gt; so this piple to be executed daily at ~19:30&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;You select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pipeline script from SCM&lt;/code&gt;, to instruct Jenkins to retrieve the Jenkinsfile from the source control.
    &lt;ul&gt;
      &lt;li&gt;You select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Git&lt;/code&gt; as SCM, add the repository URL, and select credentials giving jenkins access to this repo.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;You finally click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Save&lt;/code&gt; button to complete the configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;server-tests&quot;&gt;Server Tests&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://codeception.com/&quot;&gt;Codeception&lt;/a&gt; is one of the most popular PHP testing Framework. It provides the ability to 
test web server configurations.&lt;/p&gt;

&lt;p&gt;Since you want to test three configurations, you create &lt;a href=&quot;https://github.com/rdok/system-testing-rdok-dev/tree/master/tests&quot;&gt;three codecept test suites&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example one of these tests verifies http://jenkins.rdok.dev redirects to https://jenkins.rdok.dev/&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public function nonWwwHttpRedirectsToNonWwwHttps(Rdok_devTester $I)
{
    $I-&amp;gt;wantToTest('non-www http redirects to non-www https.');
    $I-&amp;gt;sendGET('http://jenkins.rdok.dev');
    $I-&amp;gt;seeResponseCodeIsRedirection();
    $I-&amp;gt;seeHttpHeader('Location', 'https://jenkins.rdok.dev/');
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you create the Jenkinsfile that will execute these three testing suites.&lt;/p&gt;

&lt;h1 id=&quot;jenkinsfile&quot;&gt;&lt;a href=&quot;https://github.com/rdok/system-testing-rdok-dev/blob/master/Jenkinsfile&quot;&gt;Jenkinsfile&lt;/a&gt;&lt;/h1&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pipeline {
    environment {
        AUTHOR_EMAIL = &quot;&quot;&quot;${sh(
            returnStdout: true,
            script: 'git show -s --format=&quot;%ae&quot; HEAD | sed &quot;s/^ *//;s/ *$//&quot;'
        )}&quot;&quot;&quot;
    }
    agent {
        docker {
            image 'codeception/codeception:2.5.2'
            args '''
                --volume=&quot;$PWD:/app&quot;
                --workdir=/app
                --entrypoint=''
            '''
        }
    }
    stages {
        stage('code-quests.rdok.dev') {
            steps {
                sh 'codecept run codequests_rdok_dev --no-colors'
            }
        }
        stage('rdok.dev') {
            steps {
                sh 'codecept run rdok_dev --no-colors'
            }
        }
        stage('jenkins.rdok.dev') {
            steps {
                sh 'codecept run jenkins_rdok_dev --no-colors'
            }
        }
    }
    post {
        failure {
            mail (
                to: &quot;${AUTHOR_EMAIL}&quot;,
                subject: &quot;❌ ${BUILD_TAG} - Failure&quot;,
                body: &quot;Job Url: ${env.JOB_URL}&quot;
            )
        }
        success {
            mail (
                to: &quot;${AUTHOR_EMAIL}&quot;,
                subject: &quot;✔ ${BUILD_TAG} - Stable&quot;,
                body: &quot;Job Url: ${env.JOB_URL}&quot;
            )
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, you instruct Jenkinsfile to notify the author of the latest commit with the status of the tests. Initially you 
planned to use a local mail server, however with the Gmail being quite picky, and blocking the mails, you decided to 
use Mailgun smtp instead.&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;

&lt;p&gt;In this quest, you managed to setup a Jenkins Pipeline that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Executes daily, or through GitHub WebHooks&lt;/li&gt;
  &lt;li&gt;Jenkins pulling the new code from the GitHub&lt;/li&gt;
  &lt;li&gt;Jenkins triggering the Pipeline&lt;/li&gt;
  &lt;li&gt;Pipeline verifying the behavior for each of the different web server configurations.&lt;/li&gt;
  &lt;li&gt;Pipeline notifying the author of the commit used for testing&lt;/li&gt;
&lt;/ul&gt;
</description>
    <pubDate>Mon, 25 Mar 2019 00:00:00 +0000</pubDate>
    <link>https://stories.rdok.co.uk/2019/03/server-testing-ci/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2019/03/server-testing-ci/</guid>
    
    <category>docker</category>
    
    <category>nginx</category>
    
    <category>beginner</category>
    
    
    <category>ci</category>
    
    <category>jenkins</category>
    
    <category>docker</category>
    
</item>

<item>
    <title>Setup Jenkins Pipeline</title>
    <description>&lt;h1 id=&quot;pre-requisite-setup-and-the-nightmare&quot;&gt;Pre-requisite, setup, and the nightmare&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/jenkins-build-history-and-stage-view.jpg&quot; alt=&quot;Jenkins Build History And Stage View&quot; title=&quot;Jenkins Build History And Stage View&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You’ve been given the code quest to learn how to setup a simple Jenkins automation.&lt;/p&gt;

&lt;p&gt;Completing this quest, rewards you with the automated release process of your jekyll based blog post, in under 10 seconds.&lt;/p&gt;

&lt;p&gt;You decide to complete this quest by setting up a &lt;a href=&quot;https://jenkins.io/doc/book/pipeline/#overview&quot;&gt;Jenkins Pipeline&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;pre-requisite-software&quot;&gt;Pre-requisite Software&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;Jenkins&lt;/li&gt;
  &lt;li&gt;Docker&lt;/li&gt;
  &lt;li&gt;Jenkins SSH Credentials&lt;/li&gt;
  &lt;li&gt;Jenkins Secret text for webhook&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;setup-pipeline&quot;&gt;Setup Pipeline&lt;/h1&gt;
&lt;p&gt;You log in to your Jenkins server. Create a new item, name it, and select Pipeline type.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[ Jenkins -&amp;gt; New Item -&amp;gt; Pipeline &amp;amp; Name -&amp;gt; OK]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You now find yourself on the configuration page.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Discard old builds&lt;/code&gt; checkbox, to preserve disk space as your current environment has up to 20GB. You also select 30 days to keep builds, and up to 100 builds to keep.&lt;/li&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Do not allow concurrent builds&lt;/code&gt; to maintain good server performance.&lt;/li&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pipeline speed/durability/override&lt;/code&gt; checkbox, and select the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Performance-optimized&lt;/code&gt; custom pipeline 
level for fast builds.&lt;/li&gt;
  &lt;li&gt;You tick the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GitHub hook trigger for GITScm polling&lt;/code&gt; checkbox. Thus you enable the webhook feature.&lt;/li&gt;
  &lt;li&gt;You select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pipeline script from SCM&lt;/code&gt;, to instruct Jenkins to retrieve the Jenkinsfile from the source control.
    &lt;ul&gt;
      &lt;li&gt;You select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Git&lt;/code&gt; as SCM, add the repository URL, and select credentials giving jenkins access to this repo.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;You finally click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Save&lt;/code&gt; button to complete the configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;the-nightmare&quot;&gt;The Nightmare&lt;/h1&gt;
&lt;p&gt;Creating the Jenkinsfile to work with jekyll Docker image turned out to be a nightmare.&lt;/p&gt;

&lt;p&gt;Jenkins uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jenkin&lt;/code&gt; user, but the docker image uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll&lt;/code&gt; user. Which is a problem because the jekyll user does
 not have permission to write to the Jenkins workspace. Meaning you cannot use the jekyll gem to build the site.&lt;/p&gt;

&lt;p&gt;You manage to solve this problem by changing the ownership of the site in the working space to jekyll.&lt;/p&gt;

&lt;p&gt;In order to be able to change the ownership you instruct docker to use the root user. Normally docker image uses root
 by default, however &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll/minimal&lt;/code&gt; uses jekyll user instead.&lt;/p&gt;

&lt;p&gt;After jekyll builds the site, you change the ownership of the site back to jenkins:www-data.&lt;/p&gt;

&lt;p&gt;With this work you manage to create the following Jenkinsfile.&lt;/p&gt;

&lt;h2 id=&quot;jenkinsfile&quot;&gt;Jenkinsfile&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pipeline {
    environment {
        SITE_PATH = '/var/www/your-domain/_site'
        JENKINS_UID = 113
        WWW_DATA_GID = 33
        JEKYLL_SID = 1000
        JEKYLL_GID = 1000
    }
    agent any
    stages {
        stage('Build') {
            agent {
                docker {
                    image 'jekyll/minimal:3.8'
                    args '''
                        --volume=&quot;$PWD:/srv/jekyll&quot;
                        -u root:root
                    '''
                }
            }
            steps {
                sh '''
                    rm -rf _site &amp;amp;&amp;amp; mkdir _site
                    chown -R $JEKYLL_SID:$JEKYLL_GID _site
                    jekyll build
                    chown -R $JENKINS_UID:$WWW_DATA_GID _site
                '''
            }
        }
        stage('Deploy') {
            agent any
            steps {
                sh '''
                    rm -rf ${SITE_PATH}
                    mv -T _site ${SITE_PATH}
                '''
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;

&lt;p&gt;In this quest, you managed to setup a Jenkins Pipeline that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Starts with you pushing a git commit to GitHub&lt;/li&gt;
  &lt;li&gt;GitHub doing a POST request to our Jenkins server&lt;/li&gt;
  &lt;li&gt;Jenkins pulling the new code from the GitHub&lt;/li&gt;
  &lt;li&gt;Jenkins triggering the Pipeline&lt;/li&gt;
  &lt;li&gt;Pipeline building the project&lt;/li&gt;
  &lt;li&gt;Pipeline deploying the project&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;By configuring the pipeline to be highly efficient, you managed to bring down the build &amp;amp; deploy process to 10 
seconds.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    <pubDate>Wed, 20 Mar 2019 00:00:00 +0000</pubDate>
    <link>https://stories.rdok.co.uk/2019/03/setup-jenkins-pipeline/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2019/03/setup-jenkins-pipeline/</guid>
    
    <category>docker</category>
    
    <category>jekyll/minimal</category>
    
    <category>beginner</category>
    
    
    <category>ci</category>
    
    <category>jenkins</category>
    
    <category>jekyll</category>
    
</item>

<item>
    <title>Install browser extension in codeception</title>
    <description>&lt;h1 id=&quot;problem-solution-implementation--showcase&quot;&gt;Problem, Solution, Implementation &amp;amp; Showcase&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/testing-browsers-extension-with-codeception.webp&quot; alt=&quot;Jenkins Build History And Stage View&quot; title=&quot;Jenkins Build History And Stage View&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We need to run browser UI tests with codeception. However these test are features requiring browser extensions. 
At this time there is no clear documentation for enabling said extensions on codeception with selenium.&lt;/p&gt;

&lt;p&gt;In summary, we need for the codeception to install browse extenion(s) when it initializes browsers.&lt;/p&gt;

&lt;h1 id=&quot;the-solution&quot;&gt;The Solution&lt;/h1&gt;
&lt;p&gt;In order to install a chrome extension we need to get it’s source code from the Chrome store, and load it through
a yaml configuration file in codeception.&lt;/p&gt;

&lt;p&gt;Similarly for firefox, we just need to extract the extension (xpi). However, the geckodriver does not accept
arguments like chrome above for loading an extension. Also loading a firefox_profile does not currently work [source_here]. 
As such we’ll need to build a codeception module, and load the extension there.&lt;/p&gt;

&lt;h1 id=&quot;the-implementation&quot;&gt;The Implementation&lt;/h1&gt;
&lt;p&gt;You can find a solid template installing extension in codeception at our repo &lt;a href=&quot;https://github.com/rdok/installing-browser-extension-in-codeception&quot;&gt;rdok/installing-browser-extension-in-codeception&lt;/a&gt;
This repo also contains &lt;a href=&quot;https://github.com/rdok/installing-browser-extension-in-codeception/tree/master/scripts&quot;&gt;installation scripts&lt;/a&gt; 
which will autodownload all required files for our tests: selenium-server-standalone.jar, chromedriver, geckodriver, as 
well as the two extensions we install. When executing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer install&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;If you already know setting up codeception and environements, feel free to skip the rest of the blog and just 
check the above repository.&lt;/p&gt;

&lt;p&gt;Following are descriptions of what I believe to be the major difficulties when instructing codeception to install
browser extensions.&lt;/p&gt;

&lt;h2 id=&quot;install-chrome-extension&quot;&gt;Install chrome extension&lt;/h2&gt;
&lt;p&gt;Download the extension you are interested in. &lt;a href=&quot;https://github.com/rdok/installing-browser-extension-in-codeception/blob/master/scripts/install_adblockers.sh#L8&quot;&gt;Here’s&lt;/a&gt; the way we do it on repo.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;adBlockerFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./tests/_data/adBlock&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;adBlockerId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gighmmpiobklfepjocnamgkkbiglidom
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$adBlockerFile&lt;/span&gt;

wget &lt;span class=&quot;s2&quot;&gt;&quot;https://clients2.google.com/service/update2/crx?response=redirect&amp;amp;os=mac&amp;amp;arch=x86-64&amp;amp;nacl_arch=x86-64&amp;amp;prod=chromecrx&amp;amp;prodchannel=stable&amp;amp;prodversion=44.0.2403.130&amp;amp;x=id%3D&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$adBlockerId&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%26uc&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$adBlockerId&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.zip&quot;&lt;/span&gt;
unzip &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$adBlockerFile&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$adBlockerId&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.zip&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$adBlockerId&lt;/span&gt;.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instruct codeception to add arguments to load the extension directory when initializing the chrome driver.&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;\Helper\ChromeHelper&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;\Helper\ChromeHelper&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;chrome'&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://www.detectadblock.com/'&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;capabilities&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;chromeOptions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;load-extension=tests/_data/adBlock'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://github.com/rdok/installing-browser-extension-in-codeception/blob/master/tests/_support/Helper/ChromeHelper.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\Helper\Chrome&lt;/code&gt;&lt;/a&gt;
 is a custom codeception module extending the WebDriver module. It is a workaround for an issue 
with firefox to open a new tab, rather a new window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;install-firefox-extension&quot;&gt;Install Firefox Extension&lt;/h2&gt;
&lt;p&gt;Download the extension you are interested in. &lt;a href=&quot;https://github.com/rdok/installing-browser-extension-in-codeception/blob/master/scripts/install_adblockers.sh#L24&quot;&gt;Here’s&lt;/a&gt; the way we do it in our repo.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget &lt;span class=&quot;s2&quot;&gt;&quot;https://addons.mozilla.org/firefox/downloads/latest/adblock-plus/addon-1865-latest.xpi&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;adblocker.xpi&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instruct codeception to load our own custom FirefoxHelper.&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;\Helper\FirefoxHelper&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;\Helper\FirefoxHelper&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;firefox'&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://www.detectadblock.com/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Currently loading a firefox profile, does not work in codeception. This issue seems to be have been persisted from 2016 &lt;a href=&quot;https://github.com/Codeception/Codeception/issues/2690&quot;&gt;Firefox Profile Not Working Properly for Selenium Webdriver #2690
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As such, we override the codeception’s initialization of the WebDriver command with our own custom 
&lt;a href=&quot;https://github.com/rdok/installing-browser-extension-in-codeception/blob/master/tests/_support/Helper/FirefoxHelper.php&quot;&gt;FirefoxHelper&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$profile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FirefoxProfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$profile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addExtension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'tests/_data/adblock-plus.xpi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$capabilities&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DesiredCapabilities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;firefox&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$capabilities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setCapability&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;FirefoxDriver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PROFILE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$profile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;capabilities&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$capabilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;The browser installation scripts are quite handy, as every time we execute the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer install&lt;/code&gt; command it
will upgrade the browser extension. Thus we do not need to manually redownload them in the log term future.&lt;/li&gt;
  &lt;li&gt;I might setup a docker in the future. This should make the development independent. As it is now, 
the repo can test drived only by Linux OS Desktops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, here’s the overall steps you’ll need to take for setting up the repo in your Linux Desktop&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/rdok/installing-browser-extension-in-codeception
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;installing-browser-extension-in-codeception
composer &lt;span class=&quot;nb&quot;&gt;install
&lt;/span&gt;java &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; selenium-server-standalone&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
./vendor/bin/codecept run &lt;span class=&quot;nt&quot;&gt;--env&lt;/span&gt; chrome
./vendor/bin/codecept run &lt;span class=&quot;nt&quot;&gt;--env&lt;/span&gt; firefox
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
    <pubDate>Sun, 14 May 2017 00:00:00 +0100</pubDate>
    <link>https://stories.rdok.co.uk/2017/05/install-browser-extension-in-codecept/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2017/05/install-browser-extension-in-codecept/</guid>
    
    <category>php</category>
    
    <category>codeception</category>
    
    <category>ui-testing</category>
    
    <category>browser</category>
    
    <category>browser-extension</category>
    
    <category>selenium</category>
    
    
    <category>testing</category>
    
</item>

<item>
    <title>Do not Expect Jobs</title>
    <description>&lt;h1 id=&quot;the-problem&quot;&gt;The Problem&lt;/h1&gt;
&lt;p&gt;Usually on tests where jobs are dispatched the Laravel would throw some kind of exceptions. In order to fix
that I would add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$this-&amp;gt;expectsJobs(SampleJob::class);&lt;/code&gt; to the relevant test&lt;/p&gt;

&lt;p&gt;And on tests where jobs were not expected to be dispatched, the Laravel would not throw the mentioned&lt;br /&gt;
exception.&lt;/p&gt;

&lt;p&gt;However, there are some cases, we need to be absolutely sure no job occur. Especially, since Laravel 5.1 
 there has been instance in which it does not throw the previously mentioned example.&lt;/p&gt;

&lt;p&gt;We have to be sure Laravel is not dispatching any 
Job, or else it would send thousands of invalid emails, as well as spending a lot credits of our account 
at our email service provider.&lt;/p&gt;

&lt;p&gt;In summary, we need an unit test assertion, validating no job is dispatched.&lt;/p&gt;

&lt;h1 id=&quot;the-solution&quot;&gt;The Solution&lt;/h1&gt;
&lt;p&gt;Laravel already has an &lt;a href=&quot;https://github.com/laravel/framework/blob/5.1/src/Illuminate/Foundation/Testing/ApplicationTrait.php#L104&quot;&gt;expectsJobs&lt;/a&gt;
We’ll modify this to feet our need, and insert it to our TestCase.&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;doesntExpectJobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$mock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mockery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Illuminate\Bus\Dispatcher[dispatch]'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$mock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shouldReceive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'dispatch'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;never&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Illuminate\Contracts\Bus\Dispatcher'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$mock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;the-implementation&quot;&gt;The Implementation&lt;/h1&gt;
&lt;p&gt;The source code is located 
&lt;a href=&quot;https://github.com/rdok/laravel-doesnt-expect-jobs#testing-larvel-51---doesnt-expect-jobs&quot;&gt;here&lt;/a&gt;&lt;br /&gt;
And the &lt;a href=&quot;https://github.com/rdok/laravel-doesnt-expect-jobs/blob/doesnt-expect-jobs-fails/tests/DoesntExpectJobsTest.php#L19&quot;&gt;expected failing&lt;/a&gt; and 
&lt;a href=&quot;https://github.com/rdok/laravel-doesnt-expect-jobs/blob/master/tests/DoesntExpectJobsTest.php#L23&quot;&gt;expected passing&lt;/a&gt; tests.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This above code snippet is useful for Laravel 5.1 LTS.&lt;/p&gt;

&lt;p&gt;On the other hand the latest version of Laravel (5.4+), has already the above implementation.&lt;/p&gt;

</description>
    <pubDate>Mon, 08 May 2017 00:00:00 +0100</pubDate>
    <link>https://stories.rdok.co.uk/2017/05/do-not-expect-jobs/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2017/05/do-not-expect-jobs/</guid>
    
    <category>php</category>
    
    <category>tdd</category>
    
    <category>larave5.1</category>
    
    
    <category>php</category>
    
</item>

<item>
    <title>Send Daily Transactional Emails</title>
    <description>&lt;h1 id=&quot;the-problem&quot;&gt;The Problem&lt;/h1&gt;
&lt;p&gt;The system needs to send daily emails to its users according to some complex criteria.
How do we implement this?&lt;/p&gt;

&lt;h1 id=&quot;the-solution&quot;&gt;The Solution&lt;/h1&gt;
&lt;p&gt;The cron job hits daily our system. At such time, the system will call the handler method of the Daily Notification Layer.&lt;/p&gt;

&lt;h1 id=&quot;the-implementation&quot;&gt;The Implementation&lt;/h1&gt;
&lt;p&gt;The Daily Notification Layer acts as the traffic controller. It controls uses three other layers of the system:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Daily Favorite Shop Notification fetcher. At this layer, the shops are filtered by the required criteria.&lt;/li&gt;
  &lt;li&gt;Daily Favorite Shop View generator. Generates responsive email view.&lt;/li&gt;
  &lt;li&gt;Email Api sender. Sends the emails.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The Daily Notification Layer retrieves all the shops, for which it needs to notify users about daily changed.
For each of this shops, it generates the relevant email view from the Daily Favorite Shop View generator.
Finally, it sends to all interested users, this email view.&lt;/p&gt;

</description>
    <pubDate>Mon, 01 May 2017 00:00:00 +0100</pubDate>
    <link>https://stories.rdok.co.uk/2017/05/daily-transactional-emails/</link>
    <guid isPermaLink="true">https://stories.rdok.co.uk/2017/05/daily-transactional-emails/</guid>
    
    
</item>

</channel>
</rss>