Giter VIP home page Giter VIP logo

cfn-resource-provider's Introduction

This ResourceProvider base class makes it very simple to implement a Custom CloudFormation Resource.

First, you inherit from the base class and specify a JSON schema which defines the resource properties you require:

from cfn_resource_provider import ResourceProvider

class SecretProvider(ResourceProvider):
    def __init__(self):
            super(SecretProvider, self).__init__()
            self.request_schema =  {
                "type": "object",
                "required": ["Name"],
                "properties": {
                    "Name": {"type": "string",
                             "minLength": 1,
                             "pattern": "[a-zA-Z0-9_/]+",
                             "description": "the name of the value in the parameters store"},
                    "Description": {"type": "string",
                                    "default": "",
                                    "description": "the description of the value in the parameter store"},
                    "Alphabet": {"type": "string",
                                 "default": "abcdfghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_",
                                 "description": "the characters from which to generate the secret"},
                    "ReturnSecret": {"type": "boolean",
                                     "default": False,
                                     "description": "return secret as attribute 'Secret'"},
                    "KeyAlias": {"type": "string",
                                 "default": "alias/aws/ssm",
                                 "description": "KMS key to use to encrypt the value"},
                    "Length": {"type": "integer",
                               "minimum": 1, "maximum": 512,
                               "default": 30,
                               "description": "length of the secret"}
                }
            }

The JSON schema allows you to specify the expected properties, constraints and default values. After that, you only need to implement the methods create, update and delete:

class SecretProvider(ResourceProvider):
    ...
    def create(self):
        try:
            value = "".join(choice(self.get('Alphabet') for x in range(0, self.get('Length')))
            self.ssm.put_parameter(Name=self.get('Name'), KeyId=self.get('KeyAlias'),
                                   Type='SecureString', Overwrite=False, Value=value)
            self.set_attribute('Arn', self.arn)
            if self.get('ReturnSecret'):
                self.set_attribute('Secret', value)

            self.physical_resource_id = self.arn
        except ClientError as e:
            self.physical_resource_id = 'could-not-create'
            self.fail(str(e))

    def update(self):
        ....

    def delete(self):
        ....

In these methods, you can safely access all the properties defined in your JSON schema. The methods are only called after validation of the request against your schema.

  • to return values which can be accessed by Fn::GetAtt, you can call the method set_attribute.
  • to return a resource id for your resource, you can set the property physical_resource_id.
  • to indicate a failed request, you can call the method fail.
  • to indicate a succesful request, you can call the method success.

Finally, at the end of your module implement the AWS Lambda handle function:

provider = SecretProvider()
def handle(request, context):
    provider.handle(request, context)

Processing boolean and integer properties

AWS CloudFormation passes all properties in string format, eg 'true', 'false', '123'. This does not go down well with the json schema validator. Therefore, before the validator is called, it calls the method convert_property_types. Use this method to do the conversion of the non string properties:

def convert_property_types(self):
     try:
         if 'Length' in self.properties and isinstance(self.properties['Length'], (str, unicode,)):
             self.properties['Length'] = int(self.properties['Length'])
         if 'ReturnSecret' in self.properties and isinstance(self.properties['ReturnSecret'], (str, unicode,)):
             self.properties['ReturnSecret'] = (self.properties['ReturnSecret'] == 'true')
     except ValueError as e:
         log.error('failed to convert property types %s', e)

it is ok if you cannot convert the values: the validator will report the error for you :-)

Alternatively, you may use the heuristic_convert_property_types method:

def convert_property_types(self):
     self.heuristic_convert_property_types(self.properties)

it will convert all integer strings to int type, and 'true' and 'false' strings to a boolean type. Recurses through your dictionary.

Using SNS Backed custom resource provider

Next to AWS Lambda you can also use a SNS Topic to handle your custom resources. AWS calls these Amazon Simple Notification Service-backed custom resources. When you subscribe your AWS Lambda function to this topic the event structure is different than when you directly invoke the Lambda function using a custom resource. The payload of a Lambda function that is invoked via a SNS Topic contains 1 or more events. For this reason we provide a SnsEnvelope class that will process each event in the event:

def handler(request, context):
    provider = SnsEnvelope(SampleProvider)
    requests = provider.handle(request, context)

The SampleProvider is the same provider that you directly would use. But by passing it into the envelope class it will be used for each event in the payload.

cfn-resource-provider's People

Contributors

dependabot[bot] avatar laikan57 avatar mvanholsteijn avatar nr18 avatar vschurink avatar wdegeus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

cfn-resource-provider's Issues

Schema validation error gives "Response object is too long."

Thanks for this library! We're using it quite heavily for our DevOps solution, and it has been working really well.

I just ran into a case where a resource failed the schema validation, but the CloudFormation console simply shows "Response object is too long." as the error message.

I think sending the entire schema where the validation failed might be too much. If we just cut off the error message a little earlier, that would be great.

I just looked at where that message is coming from, and it appears to be a simple str(e) on the validation error, which resolves to this big message. My suggested fix would be to catch the ValidationError and only pass along the message attribute.

If that sounds good, I would be happy to whip up a PR. Thanks!

Built-in Polling

I need to support an async call to register a Directory Service with Workspaces. Unlike cfn-certificate-provider, I don't need to do anything "in between" -- just wait for completion -- so I don't need a separate "wait" resource. It would be great if this library had something like the automatic polling methods found in aws-cloudformation/custom-resource-helper. Another polling pattern can be found at cfn-lambda-handler with some documentation about using stack status for rollback detection.

If it doesn't take longer than the Lambda timeout, I assume the half solution is to loop and sleep inside the create/delete methods.

[Question] binxio/cfn-resource-provider vs aws-cloudformation/custom-resource-helper

I am wondering if this project has purpose now that https://github.com/aws-cloudformation/custom-resource-helper exists? Should we just fold in the features from this repo into custom-resource-helper, add a deprecation warning, and start supporting efforts on a "blessed" repo?

While I highly appreciate the authors of this repo for supporting us while we did not have the AWS version, I think it would be helpful to not split efforts and grow the community around an official repo.

Would love feedback and any comments or thoughts on the subject.

Preserve Error Class

I had a KeyError in my code. It resulted in the following ambiguous line:

image

I had a pretty good idea what was going on, but it's certainly possible that this won't be the case in other places. For example, something trivial like "DirectoryId" would be hard to understand. It would have been far more clear if (KeyError) was included somewhere in the error handling.

convert_property_types behavior

request_schema already defines property types. Why isn't that definition used for property conversion (where an exception in the conversion is a schema failure)? I'm not even sure it even makes sense to keep the heuristic for nested values. It seems like it would be better to explicitly support the official nested parameter types:

List<Number> – An array of integers or floats
CommaDelimitedList – An array of literal strings that are separated by commas
List<AWS::EC2::VPC::Id> – An array of VPC IDs
List<AWS::EC2::SecurityGroup::Id> – An array of security group IDs
List<AWS::EC2::Subnet::Id> – An array of subnet IDs

If it isn't already happening, it could make sense to validate the complex string-types as well (both top-level and nested):

AWS::EC2::KeyPair::KeyName – An Amazon EC2 key pair name
AWS::EC2::SecurityGroup::Id – A security group ID
AWS::EC2::Subnet::Id – A subnet ID
AWS::EC2::VPC::Id – A VPC ID

You can default a bare list to either number or string but probably shouldn't be heuristically matching mixed content.

Add secrets-manager secrets rotation support

The secrets manager implementation as currently implemented does not support automatic secrets rotation.
The two extra fields needed are a lambda and an interval.

PR coming soon

Strings starting with one or multiple 0's are converted to integers

Hiya,

First of all, thanks for a great library! I just ran into an issue where having a tag on a resource with value '00000014' was identified as an integer and then casted to int 14. Updating the resource is then not allowed because tags require a string value. We use those kind of padded values to map resources to internal order numbers of a fixed length.

Would it be possible to change the is_int() function to not detect those kind of values as an integer by - for example - making an exception on strings that have a length > 1 and start with a '0'?

Thanks,
Wessel

Response object is too long

I had an exception in my jsonschema (used list vs. array) which result in the following error:

image

I'm not sure if I can tell exactly what the response was, but it looks like it included the entire JSON Schema:

image

The error handling could probably be improved to avoid this issue e.g.. by catching jsonschema.exceptions.UnknownType explicitly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.