Files
strapi-elb/documentation/runningnotes.md
2020-07-28 23:01:36 +01:00

22 KiB

Running notes

deocument that the db has to be done from cli arg, but the configs can be done via files.

SSL? https://levelup.gitconnected.com/beginners-guide-to-aws-beanstalk-using-node-js-d061bb4b8755

If doesnt work, try installing yarn in the ELB instance

Create seperate sql database + VPC rules: http://blog.blackninjadojo.com/aws/elastic-beanstalk/2019/01/28/adding-a-database-to-your-rails-application-on-elastic-beanstalk-using-rds.html

Tie this in with a cloudformation template + hooking it up

/opt/elasticbeanstalk/node-install/node-v12.16.1-linux-x64/bin

Try setting the database name using cloudformation template

Running strapi in different modes

You should use development for developing strapi and then deploy it to production.

If you run strapi in production, you cannot edit content types. See this git issue for the thread.

If you're running Strapi in a multiple instance you should:

  • Run strapi locally in develop mode.
  • Create content types.
  • Build strapi in production.
  • Push to ELB.

If you're running a single instance, you can alternatively just run it in develop mode in ELB.

Strapi stores its models locally on the instance and not on the database.

https://github.com/strapi/strapi/issues/4798

This is not a bug and is intended, as the CTB (Content-Type builder) saves model configurations to files doing so in production would require Strapi to restart and thus could potentially knock your production API offline. Along with the previous reason, strapi is also very much pushed as a scale able application which would mean these changes would not be replicated across any clustered configurations.

There is no current plans to allow for this, as well as no plans to move these model definitions into the database. The enforcement of using the proper environment for the proper task (Production, Staging, and Development) is something that has been pushed from day 1.

Due to the reasons I explained above I am going to mark this as closed but please do feel free to discuss.

Strapi documentation

https://strapi.io/blog/api-documentation-plugin

You can install the strapi documentation plugin by running: npm run strapi install documentation.

You can then access it through the Strapi Admin panel.

You should change the production URL server url in the documentation settings.

Edit the file ./extensions/documentation/documentation/1.0.0/full_documentation.json and change YOUR_PRODUCTION_SERVER to the ELB URL of your environment.

API Examples using HTTPIE

Authenticate with the API

http http://strapi-prod.eu-west-1.elasticbeanstalk.com/auth/local identifier=apiuser password=password

Get a Single Content Type

http http://strapi-prod.eu-west-1.elasticbeanstalk.com/tests Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiaWF0IjoxNTg3ODY3NzQ4LCJleHAiOjE1OTA0NTk3NDh9.McAi1b-F3IT2Mw90652AprEMtknJrW66Aw5FGMBOTj0"

Use query parameters to filter for Multiple Content Type

You can use query parameters to filter requests made to the API.

https://strapi.io/documentation/3.0.0-beta.x/content-api/parameters.html#parameters

The syntax is ?field_operator=value, e.g ?title_contains=test, after the endpoint URL for the content type.

http "http://strapi-prod.eu-west-1.elasticbeanstalk.com/tests?title_contains=test" Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNTg3ODY3NzMwLCJleHAiOjE1OTA0NTk3MzB9.XXdoZUk_GuOION2KlpeWZ7qwXAoEq9vTlIeD2XTnJxY"

S3 Upload Addon

You should add the strapi-provider-upload-aws-s3 extension using NPM. Make sure you add the same version of Strapi you are using.

npm i strapi-provider-upload-aws-s3@3.0.0-beta.20

AWS Resources

You should have an S3 bucket with public access, and an AWS account that has a policy to access the bucket.

Configuration

You should create a settings file at ./extensions/upload/config/settings.json.

This file defines an S3 object as in: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property.

You can use Strapi dynamic config files to set environment variables:

  • provider
  • providerOptions
    • accessKeyId
    • secretAccessKey
    • region
    • params
      • Bucket
{
  "provider": "aws-s3",
  "providerOptions": {
    "accessKeyId": "${ process.env.STRAPI_S3_ACCESS_KEY || 'AKIA23D4RF6OZWGDKV7W' }",
    "secretAccessKey": "${ process.env.STRAPI_S3_SECRET_KEY || '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' }",
    "region": "${ process.env.STRAPI_S3_REGION || 'eu-west-1' }",
    "params": {
      "Bucket": "${ process.env.STRAPI_S3_BUCKET || 'elb-example-bucket' }"
    }
  }
}

Alternatively if you want to use different options for different environments, you can use a settings.js file instead.

https://strapi.io/documentation/3.0.0-beta.x/plugins/upload.html#using-a-provider

if (process.env.NODE_ENV === "production") {
  module.exports = {
    provider: "aws-s3",
    providerOptions: {
      accessKeyId: process.env.STRAPI_S3_ACCESS_KEY,
      secretAccessKey: process.env.STRAPI_S3_SECRET_KEY,
      region: process.env.STRAPI_S3_REGION,
      params: {
        Bucket: process.env.STRAPI_S3_BUCKET,
      },
    },
  };
} else {
  module.exports = {};
}

Fix Version Numbers

When using Strapi you should make sure the version numbers for all dependencies in ./package.json are fixed for Strapi modules. You cannot mix and match and upgrade arbitrarily.

An example is:

{
  "dependencies": {
    "knex": "<0.20.0",
    "pg": "^8.0.3",
    "sqlite3": "latest",
    "strapi": "3.0.0-beta.20",
    "strapi-admin": "3.0.0-beta.20",
    "strapi-connector-bookshelf": "3.0.0-beta.20",
    "strapi-plugin-content-manager": "3.0.0-beta.20",
    "strapi-plugin-content-type-builder": "3.0.0-beta.20",
    "strapi-plugin-documentation": "3.0.0-beta.20",
    "strapi-plugin-email": "3.0.0-beta.20",
    "strapi-plugin-upload": "3.0.0-beta.20",
    "strapi-plugin-users-permissions": "3.0.0-beta.20",
    "strapi-provider-upload-aws-s3": "3.0.0-beta.20",
    "strapi-utils": "3.0.0-beta.20"
  }
}

Strapi in git

To have a strapi project in github you should remove the:

  • ./build
  • ./node_modules

folders.

When cloning from the repo you should then do a:

  • NODE_ENV=development npm install
  • NODE_ENV=development npm run build

You can then run Strapi with npm run develop or NODE_ENV=production npm run start.

Cloudformation

https://adamtheautomator.com/aws-cli-cloudformation/ (example of deploying an S3 bucket with static site index.html.)

Output naming convention

You should follow a standard naming convention for your CF outputs.

For example:

Outputs:
  PublicVPCOutput:
    Description: The VPC ID.
    Value: !Ref PublicVPC
    Export:
      Name: !Sub "${AWS::StackName}-EBStrapiPublicVPC"

Defines a VPC. We can then pass in the stackname to another CF template and it can reference this VPC. The VPC names are static between projects (they don't have to be but here they are).

Creating templates

To create a cloudformation template you should create a template.yaml. This yaml file should have at the top:

AWSTemplateFormatVersion: 2010-09-09
Description: A simple CloudFormation template

Then you should add a Resources key and populate this with all the infrastructure you need to provision.

Adding resources

Documentation for all AWS resources is: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html.

A good approach is to use the GUI to create an object, and then lookup the cloudformation template as you go along.

Using parameters

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html

You can use parameters in your templates. This allows you to use names/resources from other templates, or specify them at creation on the CLI.

To use a parameter you should create a Parameters section in the yaml on the same level as a Resources.

Parameters:
  InstanceTypeParameter:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - m1.small
      - m1.large
    Description: Enter t2.micro, m1.small, or m1.large. Default is t2.micro.

Using outputs

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html

Using functions

A list of all Cloudformation functions is: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html.

Fn::Select will select a single object from a list of objects by index.

Fn::GetAZs returns an array that lists all availability zones for a specified region.

!Ref returns the value of the specified parameter or resource.

Examples

Select, GetAZs and Ref

Example of Fn::Select, Fn::GetAZs and !Ref:

PublicSubnet1:
  Type: AWS::EC2::Subnet
  Properties:
    AvailabilityZone:
      Fn::Select:
        - 0
        - Fn::GetAZs: !Ref "AWS::Region"
GetAtt

Fn::GetAtt differs from Ref in that !GetAtt gets an attribute of a resource, whereas Ref will reference the actual resource itself. An attribute is a return value of a resource. For example, a VPC resource has a DefaultSecurityGroup as an attribute that you can access.

To see attributes that you can reference with !GetAtt, you should check the Cloudformation documentation for the resource in question and look at the "Return Values" header: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html#aws-resource-ec2-vpc-return-values.

An example would be using Fn::GetAtt to export a return value for some object in a template:

Outputs:
  PublicVPCIDDefaultSecurityGroup:
    Description: The VPC ID.
    Value: !GetAtt PublicVPC.DefaultSecurityGroup
    Export:
      Name: !Sub "${AWS::StackName}-PublicVPCIDDefaultSecurityGroup"

Long syntax: Fn::GetAtt: [ logicalNameOfResource, attributeName ]

Sub

A really good resource for Cloudformation functions is: https://www.fischco.org/technica/2017/cloud-formation-sub/.

Using Fn::Sub allows you to substitue a variable into the string you are trying to create. You might want to substitue an input parameter in for example.

AppDnsRecord:
  Type: AWS::Route53::RecordSet
  Properties:
    HostedZoneId: !ImportValue HostedZone-zone-id
    Name:
      Fn::Sub:
        - "myapp.${HostedZoneName}"
        - HostedZoneName: !ImportValue HostedZone-zone-name

Here we have referenced ${HostedZoneName} - this is a temporary parameter in the sub command. At this point it does not exist, which is why we create a map which defines this variable as the second argument to Sub. In this example it is using Fn::ImportValue to import a resource from another Cloudformation stack.

As this second argument is a map (denoted by the :), we can have multiple key,value pairs.

Name:
  Fn::Sub:
    - "myapp.${SubDomain}.${HostedZoneName}"
    - HostedZoneName: !ImportValue HostedZone-zone-name
      SubDomain: !ImportValue HostedZone-subzone-name

Note here that the second definition of the key, value pair does not have a leading -. We don't want to pass another argument to the Sub command, rather, we want to define additional key,value pair to be substituted in.

If our import value name also depended on an input parameter (say our imported value name depeneded on a stack name) we would have to use nested sub functions. In the above example we are simply importing a static import value, the string is hardcoded, if we wanted this to be dynamic, and be populated from an input parameter, then we can use:

Parameters:
  Route53StackName:
    Type: String

Resources:
  AppDnsRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName:
        Fn::ImportValue: !Sub "${Route53StackName}-zone-name"
      Name:
        Fn::Sub:
          - "myapp.${ZoneName}"
          - ZoneName:
              Fn::ImportValue: !Sub "${Route53StackName}-zone-name"

Pay attention to the double indentation after ZoneName!.

We have to use the long form of Fn::ImportValue here and not the shorthand - this is a Cloudformation restriction.

Short form

If you are writing templates in yaml there is a long and shortform available.

An example for the Sub function:

  • Longform Fn::Sub: String
  • Shortform !Sub String

Outputs

You can use the Outputs: header in your Cloudformation templates to specify outputs to be used in other Cloudformation templates.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html

Outputs:
  PublicVPCID:
    Description: The VPC ID.
    Value: !Ref PublicVPC
    Export:
      Name: ELBStrapiPublicVPC

Value returns the value of the property by an aws cloudformation describe-stacks command. The value can contain literals, parameter references, pseudo-parameters, mapping values or functions.

Name goes under Export: and is used for cross-stack reference. This name should be unique within a region. You can use this name in other Cloudformation templates to reference the Value you have specified above. You can set content in other cloudformation templates this way.

You can refer to these in ELB ./config files for example - allowing you to dynamically link to other AWS resources in your ELB environment.

Referencing other resources internally.

You can reference other resources in the template. This is useful say if you want to define a VPC and a subnet and reference the VPC from the subnet.

To do this you should use the !Ref function:

VpcId: !Ref PublicVPC

Note that this is a special syntax, it doesn't have Fn:: in the long form and the short form, !Ref is actually longer than the long form in this case.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html

Pesudeo references

You can also reference certain AWS references: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html.

Examples include AWS::AccountId and AWS::StackName among others.

Referencing other resources from external templates

Say we have a Cloudformation template where we have created a VPC:

Outputs:
  PublicSubnet0ID:
    Description: The ID of the subnet.
    Value: !Ref PublicSubnet0
    Export:
      Name: !Sub "${AWS::StackName}-PublicSubnet0"

We want to be able to use this, dynamically, in another template.

To do this we can use the Fn::Sub and Fn::ImportValue functions.

Parameters:
  StackName:
    Description: The stack name of another CloudFormation template. This is used
      to prepend the name of other resources in other templates.
    Type: String
Resources:
  RDSSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: A subnet group for the RDS instance.
      SubnetIds:
        - Fn::ImportValue: !Sub "${StackName}-PublicSubnet0"
        - Fn::ImportValue: !Sub "${StackName}-PublicSubnet1"

Deploy a stack/template

To deploy, you should run the command: aws cloudformation deploy --template-file template.yaml --stack-name static-website

Passing in parameters

You can define parameters in its own section in a Cloudformation template:

Parameters:
  StackName:
    Description: The stack name of another CloudFormation template. This is used
      to prepend the name of other resources in other templates.
    Type: String

You can set a default value which will be used if no value is passed in.

To pass values in using the CLI you should use the --parameter-overrides argument and pass them in as key=value pairs seperated by a space:

--parameter-overrides StackName=temp-vpc

Tags

When setting tags you can set them on individual resources in the Cloudformation template:

Tags:
  - Key: git
    Value: web-dev
  - Key: owner
    Value: home
  - Key: project
    Value: strapi-elb
  - Key: test
    Value: true
  - Key: deployment
    Value: cloudformation

Alternatively if you have many tags to be shared across all resources you can set them when you use the CLI to deploy: --tags git=web-dev owner=home project=strapi-elb test=true deployment=cloudformation

Updating stack

To update a stack you can use deploy. Note that the default behaviour is to create the new resources side by side, then once successful remove the old ones. You may run into errors when updating certain resources (updating a VPC subnet will fail as it has to create the new subnet alongside the existing one). You should remove the old stack by doing delete-stack first.

aws cloudformation delete-stack --stack-name temp-vpc --profile admin

Failure

If something goes wrong, you can use describe-stack-events and pass the stack-name to find the events leading up to the failure: aws cloudformation describe-stack-events --stack-name strapi-s3.

If this is the first time you are creating a stack you will not be able to re-deploy the stack. You must first delete the stack entirely and then re-deploy with any fixes.

You can delete a stack by running: aws --profile admin cloudformation delete-stack --stack-name strapi-s3.

Stacks

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacks.html

A cloudformation stack is a collection of AWS resources that you can manage as a single unit. You can group different resources under one stack then create, update or destroy everything under this stack.

Using stacks means AWS will treat all resources as a single unit. They must all be created or destroyed successfully to be created or deleted. If a resource cannot be created, Cloudformation will roll the stack back to the previous configuration and delete any interim resources that were created.

Snippets

Deploy a template/stack

aws --profile admin cloudformation deploy --template-file ./01-stack-storage.yaml --stack-name strapi-s3

You can pass paramter values in with --paramter-overrides KEY=VALUE:

--parameter-overrides TestParameter="some test string"

Destroy a stack

aws --profile admin cloudformation delete-stack --stack-name strapi-s3

Tags

Suggested tags for all AWS resources are:

Tag Description Example
git git repo that contains the code web-dev
owner who the resource is for/owned home, work, elliot
project what project it belongs to strapi-elb
test flag for a temporary test resource true
environment environment resource belongs to dev, prod
deployment AWS tool used for deployment cloudformation, elb

Cloudformation default tags

For Cloudformation resources the following tags get applied automatically:

Tag Description Example
aws:cloudformation:stack-name stack-name of the resource strapi-s3
aws:cloudformation:logical-id resource name given in template ELBExampleBucket
aws:cloudformation:stack-id ARN of the cloudformation stack arn:aws:cloudformation:eu-west-1:745437999005:stack/strapi-s3/459ebbf0-88aa-11ea-beac-02f0c9b42810