.. image:: https://img.shields.io/pypi/v/tropostack.svg
   :target: https://pypi.org/project/tropostack/
   :alt: pypi version

.. image:: https://img.shields.io/travis/gtie/tropostack/master.svg?label=Build
   :target: https://travis-ci.org/gtie/tropostack
   :alt: travis build status

About
-----

Tropostack is a CLI/workflow library that simplifies the creation and management
of `CloudFormation <https://aws.amazon.com/cloudformation/>`_ stacks, based on
the excellent `Troposphere Project <https://github.com/cloudtools/troposphere>`_.

Tropostack features:

 - Single stack template = single executable Python file = single CLI
 - Support for different configuration and CLI plugins
 - A collection of generic commands available to each stack (e.g. `create`)
 - Support for user-defined CLI commands (e.g. `upscale`)
 - Helper routines (e.g. locate the newest matching AMI)

Docs
----
Full docs are at https://tropostack.readthedocs.io/en/latest/

Installation
------------

.. code:: sh

    $ pip install tropostack

Or, you can use setup.py to install from a cloned repository:

.. code:: sh

    $ python setup.py install


First stack
---------------

You use `tropostack` as a library to:
 - Consisteny define CloudFormation templates in Python code
 - Have a CLI around each stack definition, enabling it to live as a standalone executable

Here is a minimalistic example of a stack that creates an S3 bucket, and exports the ARN as an Output:

.. code-block:: python

    #!/usr/bin/env python3

    from troposphere import s3
    from troposphere import Output, Export, Sub, GetAtt

    from tropostack.base import InlineConfStack
    from tropostack.cli import InlineConfCLI


    class MyS3BucketStack(InlineConfStack):
        # Name of the stack
        BASE_NAME = 'my-s3-bucket-stack'

        # Define configuration values for the stack
        CONF = {
            # Region is always explicitly required
            'region': 'eu-west-1',
            # Prefix the bucket name with the account ID
            'bucket_name': Sub('${AWS::AccountId}-my-first-tropostack-bucket')
        }

        # Stack Resources are defined as class properties prefixed with 'r_'
        @property
        def r_bucket(self):
            return s3.Bucket(
                'MyBucketResource',
                BucketName=self.conf['bucket_name']
            )

        # Stack Outputs are defined as class properties prefixed with 'o_'
        @property
        def o_bucket_arn(self):
            _id = 'BucketArn'
            return Output(
                _id,
                Description='The ARN of the S3 bucket',
                Value=GetAtt(self.r_bucket, 'Arn'),
                # We're exporting the output as <StackName>-<OutputId>
                # Other stacks can read the output relying on the same convention
                Export=Export(Sub("${AWS::StackName}-%s" % _id))
            )


    if __name__ == '__main__':
        # Wrap the stack in a CLI and run it
        cli = InlineConfCLI(MyS3BucketStack)
        cli.run()

The above already gives you a usable CLI around your stack definition.

Assuming you put it inside an executable file called `s3_minimal.py`, you'd be able to call it already:

.. code-block:: bash

   $ ./s3_minimal.py -h
   usage: s3_minimal.py [-h]
                        {apply,create,delete,outputs,print,update,validate}

   positional arguments:
     {apply,create,delete,outputs,print,update,validate}

   optional arguments:
     -h, --help            show this help message and exit

You can now inspect the "raw" CloudFormation code generated by the stack:

.. code-block:: yaml

   $ ./s3_minimal.py print
   Outputs:
     BucketArn:
       Description: The ARN of the S3 bucket
       Export:
         Name: !Sub '${AWS::StackName}-BucketArn'
       Value: !GetAtt 'MyBucketResource.Arn'
   Resources:
     MyBucketResource:
       Properties:
         BucketName: !Sub '${AWS::AccountId}-my-first-tropostack-bucket'
       Type: AWS::S3::Bucket

Assuming
`AWS credentials are present <https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#configuring-credentials>`_
in the environment, we can now fire up stack that would create our S3 bucket:

.. code-block:: bash

   $ ./s3_minimal.py create
   Stack creation initiated for: arn:aws:cloudformation:eu-west-1:472799024263:stack/my-s3-bucket-stack/dd5e93c0-225c-11ea-93d8-0641c159a77a
   TIMESTAMP (UTC)          RESOURCE TYPE                              RESOURCE ID                  STATUS                                   REASON
   2019-12-19 12:41:23      AWS::CloudFormation::Stack                 my-s3-bucket-stack           CREATE_IN_PROGRESS                       User Initiated
   2019-12-19 12:41:26      AWS::S3::Bucket                            MyBucketResource             CREATE_IN_PROGRESS
   2019-12-19 12:41:27      AWS::S3::Bucket                            MyBucketResource             CREATE_IN_PROGRESS                       Resource creation Initiated
   2019-12-19 12:41:48      AWS::S3::Bucket                            MyBucketResource             CREATE_COMPLETE
   2019-12-19 12:41:50      AWS::CloudFormation::Stack                 my-s3-bucket-stack           CREATE_COMPLETE

We can also inspect the stack Outputs - in this case, the ARN of the bucket:

.. code-block:: bash

   $ ./s3_minimal.py outputs
   Stack is in status: CREATE_COMPLETE
   OutputKey    OutputValue                                           Description               ExportName
   -----------  ----------------------------------------------------  ------------------------  ----------------------------
   BucketArn    arn:aws:s3:::472799024263-my-first-tropostack-bucket  The ARN of the S3 bucket  my-s3-bucket-stack-BucketArn


Finally, we can clean up and have our stack deleted:

.. code-block:: bash

   $ ./s3_minimal.py delete
   Destroy initiated for stack: my-s3-bucket-stack
   TIMESTAMP (UTC)          RESOURCE TYPE                              RESOURCE ID                  STATUS                                   REASON
   2019-12-19 12:44:59      AWS::CloudFormation::Stack                 my-s3-bucket-stack           DELETE_IN_PROGRESS                       User Initiated
   Stack is gone: my-s3-bucket-stack (An error occurred (ValidationError) when calling the DescribeStackEvents operation: Stack [my-s3-bucket-stack] does not exist)

Stock commands
--------------

While the CLI can be expanded/customized for each individual tropostack, there are several subcommands that come out of the box:

 - `print` - prints the resulting CloudFormation YAML to the screen
 - `validate` - Sends the CloudFormation template to the AWS API for validation, and reports back result
 - `create` - Initiates the stack creation (should only be used if the stack does not exist yet)
 - `update` - Updates an existing stack (should only be used if the stack exists)
 - `apply` - Idempotently updates or creates a stack, based on whether it exists or not
 - `outputs` - Shows the outputs of an existing stack
 - `delete` - Deletes an existing stack