From 7b1f5c5ff19abbe04ea78e60a44f495143e0f363 Mon Sep 17 00:00:00 2001 From: Sanath Kumar Ramesh Date: Wed, 17 Jan 2018 12:31:41 -0800 Subject: [PATCH 1/6] Documentation updates with a Sphinx based website Docs are still a work in progress. But checking the initial version in for others to start contributing to it. --- .gitignore | 5 +- docs/api.rst | 4 + docs/conf.py | 204 ++++++++++++++ docs/function.rst | 4 + docs/getting_started.rst | 2 + docs/globals.rst | 2 +- docs/index.rst | 24 ++ docs/internals/generated_resources.rst | 362 +++++++++++++++++++++++++ docs/internals/index.rst | 10 + docs/safe_lambda_deployments.rst | 1 + docs/website/Makefile | 20 ++ docs/website/_static/custom.css | 35 +++ docs/website/_static/logo.png | Bin 0 -> 11797 bytes docs/website/_templates/about.html | 57 ++++ 14 files changed, 728 insertions(+), 2 deletions(-) create mode 100644 docs/api.rst create mode 100644 docs/conf.py create mode 100644 docs/function.rst create mode 100644 docs/getting_started.rst create mode 100644 docs/index.rst create mode 100644 docs/internals/generated_resources.rst create mode 100644 docs/internals/index.rst create mode 100644 docs/website/Makefile create mode 100644 docs/website/_static/custom.css create mode 100644 docs/website/_static/logo.png create mode 100644 docs/website/_templates/about.html diff --git a/.gitignore b/.gitignore index 496ee2ca6..2f3e46017 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.DS_Store \ No newline at end of file +.DS_Store +docs/website/_build +.vscode +.idea diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 000000000..6e9ed9c9f --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,4 @@ +Api +=== + +TBD diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..efffc632e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +# +# SAM documentation build configuration file, created by +# sphinx-quickstart on Thu Nov 23 18:15:59 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.todo', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Serverless Application Model' +copyright = u'2017, Amazon Web Services' +author = u'Amazon Web Services' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0.1.0' +# The full version, including alpha/beta/rc tags. +release = u'0.1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +html_theme_options = { + 'description': "Define your serverless infrastructure as a simple YAML file", + 'logo': 'logo.png', + 'logo_name': True, + 'logo_text_align': 'center', + 'github_user': 'awslabs', + 'github_repo': 'serverless-application-model', + 'github_button': True, + 'github_type': 'star', + 'github_banner': True, + 'sidebar_collapse': True, +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['website/_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html' + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'SAMdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'SAM.tex', u'SAM Documentation', + u'AWS', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'sam', u'SAM Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'SAM', u'SAM Documentation', + author, 'SAM', 'One line description of project.', + 'Miscellaneous'), +] + + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + diff --git a/docs/function.rst b/docs/function.rst new file mode 100644 index 000000000..c6b4b10d3 --- /dev/null +++ b/docs/function.rst @@ -0,0 +1,4 @@ +Function +======== + +TBD diff --git a/docs/getting_started.rst b/docs/getting_started.rst new file mode 100644 index 000000000..3ce1d78a1 --- /dev/null +++ b/docs/getting_started.rst @@ -0,0 +1,2 @@ +Getting Started +=============== diff --git a/docs/globals.rst b/docs/globals.rst index d7789e430..210e1b60e 100644 --- a/docs/globals.rst +++ b/docs/globals.rst @@ -122,7 +122,7 @@ Environment variables of ``MyFunction`` will be set to ``{ TABLE_NAME: "resource NEW_VAR: hello Lists are additivie -~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~ *Also called as arrays* List values in the resource will be **appended** with the map value from Global. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..1d6d0c465 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,24 @@ +.. SAM documentation master file, created by + sphinx-quickstart on Thu Nov 23 18:15:59 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Create & deploy serverless applications +======================================= + + +What's New? +----------- + +- ``Globals`` section +- Support for Traffic Shifting Lambda deployments +- Refer to resources automatically created by SAM + +.. toctree:: + :hidden: + :glob: + + globals.rst + safe_lambda_deployments.rst + policy_templates.rst + internals/index diff --git a/docs/internals/generated_resources.rst b/docs/internals/generated_resources.rst new file mode 100644 index 000000000..7b3acd3bd --- /dev/null +++ b/docs/internals/generated_resources.rst @@ -0,0 +1,362 @@ +CloudFormation Resources Generated By SAM +========================================= + +.. contents:: + :local: + :backlinks: none + +When you create a Serverless Function or a Serverlesss API, SAM will create additional AWS resources wire everything up. +For example, when you create a ``AWS::Serverless::Function``, SAM will create a Lambda Function resource +along with an IAM Role resource to give appropriate permissions for your function. This document describes all +such generated resources, how they are named, and how to refer to them in your SAM template. + + +AWS::Serverless::Function +------------------------- +Given a Function defined as follows: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + +Following resources will be generated: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::Lambda::Function MyFunction +AWS::IAM::Role MyFunction\ **Role** +================================== ================================ + +With AutoPublishAlias Property +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + AutoPublishAlias: live + ... + + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::Lambda::Version MyFunction\ **Version**\ *SHA* (10 digits of SHA256 of CodeUri) +AWS::Lambda::Alias MyFunction\ **Alias**\ *live* +================================== ================================ + + +With DeploymentPreference Property +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + AutoPublishAlias: live + DeploymentPreference: + Type: Linear10PercentEvery10Minutes + ... + + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::CodeDeploy::Application ServerlessDeploymentApplication (only one per stack) +AWS::CodeDeploy::DeploymentGroup MyFunction\ **DeploymentGroup** +AWS::IAM::Role CodeDeployServiceRole +================================== ================================ + +With Events +~~~~~~~~~~~ + +A common theme with all Events is SAM will generate a ``AWS::Lambda::Permission`` resource to give event source +permission to invoke the function. Other generated resources depend on the specific event type. + +API +^^^ +This is called an "Implicit API". There can be many functions in the template that define these APIs. Behind the +scenes, SAM will collect all implicit APIs from all Functions in the template, generate a Swagger, and create an +implicit ``AWS::Serverless::Api`` using this Swagger. This API defaults to a StageName called "Prod" that cannot be +configured. + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + Events: + ThumbnailApi: + Type: Api + Properties: + Path: /thumbnail + Method: GET + ... + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::ApiGateway::RestApi *ServerlessRestApi* +AWS::ApiGateway::Stage *ServerlessRestApi*\ **Prod**\ Stage +AWS::ApiGateway::Deployment *ServerlessRestApi*\ Deployment\ *SHA* (10 Digits of SHA256 of Swagger) +AWS::Lambda::Permissions MyFunction\ **ThumbnailApi**\ Permission\ **Prod** + (Prod is the default Stage Name for implicit APIs) +================================== ================================ + + + NOTE: ``ServerlessRestApi*`` resources are generated one per stack. + +S3 +^^^ + +Example: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + Events: + S3Trigger: + Type: S3 + Properties: + Bucket: !Ref MyBucket + Events: s3:ObjectCreated:* + ... + + MyBucket: + Type: AWS::S3::Bucket + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::Lambda::Permissions MyFunction\ **S3Trigger**\ Permission +AWS::S3::Bucket Existing MyBucket resource is modified to append ``NotificationConfiguration`` + property where the Lambda function trigger is defined +================================== ================================ + + NOTE: You **must** refer to an S3 Bucket defined in the same template. This is for two reasons: + + 1. SAM needs to add a ``NotificationConfiguration`` property to the bucket resource by reading and modifying the + resource definition + + 2. Lambda triggers are specified as a property on the bucket resource. Since CloudFormation cannot modify a resource + created outside of the stack, this bucket needs to be defined within the template. + +SNS +^^^ + +Example: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + Events: + MyTrigger: + Type: SNS + Properties: + Topic: arn:aws:sns:us-east-1:123456789012:my_topic + ... + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::Lambda::Permissions MyFunction\ **MyTrigger**\ Permission +AWS::SNS::Subscription MyFunction\ **MyTrigger** +================================== ================================ + +Kinesis +^^^^^^^ + +Example: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + Events: + MyTrigger: + Type: Kinesis + Properties: + Stream: arn:aws:kinesis:us-east-1:123456789012:stream/my-stream + StartingPosition: TRIM_HORIZON + ... + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::Lambda::Permissions MyFunction\ **MyTrigger**\ Permission +AWS::Lambda::EventSourceMapping MyFunction\ **MyTrigger** +================================== ================================ + +DynamoDb +^^^^^^^^ + +Example: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + Events: + MyTrigger: + Type: DynamoDb + Properties: + Stream: arn:aws:dynamodb:us-east-1:123456789012:table/TestTable/stream/2016-08-11T21:21:33.291 + StartingPosition: TRIM_HORIZON + ... + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::Lambda::Permissions MyFunction\ **MyTrigger**\ Permission +AWS::Lambda::EventSourceMapping MyFunction\ **MyTrigger** +================================== ================================ + +Schedule +^^^^^^^^ + +Example: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + Events: + MyTimer: + Type: Schedule + Properties: + Input: rate(5 minutes) + ... + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::Lambda::Permissions MyFunction\ **MyTimer**\ Permission +AWS::Events::Rule MyFunction\ **MyTimer** +================================== ================================ + +CloudWatchEvent +^^^^^^^^^^^^^^^ + +Example: + +.. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + Events: + OnTerminate: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + ... + +Additional generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::Lambda::Permissions MyFunction\ **OnTerminate**\ Permission +AWS::Events::Rule MyFunction\ **OnTerminate** +================================== ================================ + + +AWS::Serverless::Api +-------------------- + +In contrast to Implict APIs, you can explicitly define your API resource by providing an entire Swagger definition of +your API. + +Example: + +.. code:: yaml + + MyApi: + Type: AWS::Serverless::Api + Properties: + ... + DefinitionUri: s3://bucket/swagger.json + StageName: dev + ... + +Generated resources: + +================================== ================================ +CloudFormation Resource Type Logical ID +================================== ================================ +AWS::ApiGateway::RestApi MyApi +AWS::ApiGateway::Stage MyApi\ **dev**\ Stage +AWS::ApiGateway::Deployment MyApi\ Deployment\ *SHA* (10 Digits of SHA256 of DefinitionUri or DefinitionBody value) +================================== ================================ + + NOTE: By just specifying AWS::Serverless::Api resource, SAM will *not* add permission for API Gateway to invoke the + the Lambda Function backing the APIs. You should explicitly re-define all APIs under ``Events`` section of the + AWS::Serverless::Function resource but include a `RestApiId` property that references the AWS::Serverless::Api + resource. SAM will add permission for these APIs to invoke the function. + + Example: + + .. code:: yaml + + MyFunction: + Type: AWS::Serverless::Function + Properties: + ... + Events: + GetApi: + Type: Api + Properties: + Path: / + Method: GET + + # This is the property that instructs SAM to just add permissions for an explicitly defined API + RestApiId: !Ref MyApi + + + + diff --git a/docs/internals/index.rst b/docs/internals/index.rst new file mode 100644 index 000000000..7835729b0 --- /dev/null +++ b/docs/internals/index.rst @@ -0,0 +1,10 @@ +SAM Internals +============= + +Explore the topics in this section to learn more about the internals of how SAM works. + +.. toctree:: + :maxdepth: 1 + :glob: + + * diff --git a/docs/safe_lambda_deployments.rst b/docs/safe_lambda_deployments.rst index 16941cced..b63a7b6bf 100644 --- a/docs/safe_lambda_deployments.rst +++ b/docs/safe_lambda_deployments.rst @@ -209,6 +209,7 @@ Hooks are extremely powerful because: We recommend adding an Envrionment variable to the Hook function that maintains the current version of the function requiring safe deployments .. code:: yaml + Environment: Variables: CurrentVersion: !Ref MySafeLambdaFunction.Version diff --git a/docs/website/Makefile b/docs/website/Makefile new file mode 100644 index 000000000..55c135a98 --- /dev/null +++ b/docs/website/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = SAM +SOURCEDIR = ../ +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/website/_static/custom.css b/docs/website/_static/custom.css new file mode 100644 index 000000000..b405c3ae3 --- /dev/null +++ b/docs/website/_static/custom.css @@ -0,0 +1,35 @@ +img.logo { + max-height: 100px; +} + +h1.logo-name { + font-size: 1.5em; + text-align: center; +} + + +.sphinxsidebarwrapper .blurb { + text-align: center; +} + +.github-badge { + text-align: center; +} + +blockquote { + background: #f9f9f9; + border-left: 10px solid #ccc; + margin: 1.5em 10px; + padding: 0.5em 10px; + quotes: "\201C""\201D""\2018""\2019"; +} + +div.topic { + background-color: transparent; + border: none; +} + +div.sphinxsidebarwrapper > ul > li { + /* Add additional padding between first-level elements in side-bar navigation */ + padding-bottom: 10px; +} \ No newline at end of file diff --git a/docs/website/_static/logo.png b/docs/website/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..678d3e078ef4c8e600e25eda8fac4f4c1adb1ff9 GIT binary patch literal 11797 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