Jackal CFN

Provides jackal integration for AWS CloudFormation custom resources and stack event notifications.

Requirements

This library currently uses the patron gem for sending notifications to AWS S3. It requires the curl development libraries to be available, so ensure it is installed.

Usage

There are two ways to use this library. The first is to process events and resources into proper payloads and inject them into the pipeline. The other is to re-process a formatted payload.

Pipeline Injection

Configuration for pipeline injection of resource and event notifications is very straightforward:

{
  "jackal": {
    "cfn": {
      "config": {
      },
      "sources": {
        "input": {
          "type": "sqs",
          "args": {
            SQS_CONFIG
          }
        },
        "output": {
          OUTPUT_SOURCE
        }
      },
      "callbacks": [
        "Jackal::Cfn::Resource",
        "Jackal::Cfn::Event"
      ]
    }
  }
}

With this configuration in place resources and events will be received, formatted, and delivered to the OUTPUT_SOURCE. It will be the job of a later service to handle reply notifications (for resources) in this style of usage.

Message Reprocessing

When using the re-processing configuration, messages do not continue down the pipeline. Instead, the original message is received, formatted, and then re-delivered to the originating source (generally an SQS queue). The message will be fetched again (this time properly formatted) and any matching callbacks will be executed. A sample configuration may look something like this:

{
  "jackal": {
    "cfn": {
      "config": {
        "reprocess": true,
        "ami": {
          "credentials": {
            CREDENTIALS
          }
        }
      },
      "sources": {
        "input": {
          "type": "sqs",
          "args": {
            SQS_CONFIG
          }
        },
        "output": {
          OUTPUT_SOURCE
        }
      },
      "callbacks": [
        "Jackal::Cfn::Resource",
        "Jackal::Cfn::AmiRegister"
      ]
    }
  }
}

The important item to note is the "reprocess": true which enables the automatic re-processing of messages.

Custom Resources

This library provides support for creating new custom resources. Creation is as simple as subclassing:

module Jackal
  module Cfn
    class LocalPrinter < Jackal::Cfn::Resource

      def execute(message)
        failure_wrap(message) do |payload|
          cfn_resource = rekey_hash(payload.get(:data, :cfn_resource))
          cfn_response = build_response(cfn_resource)
          info "CFN Resource: #{cfn_resource.inspect}"
          info "CFN Response: #{cfn_response.inspect}"
          respond_to_stack(cfn_response, cfn_resource[:response_url])
        end
      end

    end
  end
end

This will match Custom::LocalPrinter resource requests that are received. It will print information into the log about the request and then send a successful response back to the stack.

Builtin Custom Resources

This library provides a few custom resources builtin. These can be used directly or as examples for building new custom resources.

Jackal::Cfn::AmiRegister

Generates and registers an AMI based on an EC2 resource. This allows for building a single EC2 resource within a stack that is fully configured, creating and registering an AMI based on that EC2 resource, and using the new AMI for instances created within an ASG. It is an integrated way to speed up ASG instance launches, but keep all resources for the stack fully managed.

Resource usage:

{
  "Type": "Custom::AmiRegister",
  "Properties": {
    "Parameters": {
      "Name": String,
      "InstanceId": String,
      "Description": String,
      "NoReboot": Boolean,
      "BlockDeviceMappings": Array,
      "HaltInstance": Boolean,
      "Region": String
    }
  }
}

Resource Response:

{
  "AmiId": String
}

Configuration:

{
  "jackal": {
    "cfn": {
      "config": {
        "ami": {
          "credentials": {
            "compute": {
              FOG_CREDENTIALS
            }
          }
        }
      }
    }
  }
}

Jackal::Cfn::AmiManager

This resource is a simplification of the AmiRegister resource. The AmiManager is used to ensure an AMI is removed from the system when a stack is destroyed. This allows for customized AMI generation to be integrated and ensure that once the stack is destroyed all custom AMIs for that stack are destroyed as well.

Resource usage:

{
  "Type": "Custom::AmiManager",
  "Properties": {
    "Parameters": {
      "AmiId": "",
      "Region": ""
    }
  }
}

Resource Response:

{
}

Configuration:

{
  "jackal": {
    "cfn": {
      "config": {
        "ami": {
          "credentials": {
            "compute": {
              FOG_CREDENTIALS
            }
          }
        }
      }
    }
  }
}

Jackal::Cfn::HashExtractor

This resource will extract a nested hash value from a JSON string. Useful for when a result may be serialized JSON and a value from that structure is required elsewhere.

Resource usage:

{
  "Type": "Custom::HashExtractor",
  "Properties": {
    "Parameters": {
      "Key": "path.to.value.in.hash",
      "Value": JSON
    }
  }
}

Resource Response:

{
  "Payload": VALUE
}

Jackal::Cfn::JackalStack

This resource provides an integration point for building stacks on remote endpoints. Remote end points are provided via configuration and referenced via the Location property in the custom resource.

Resource usage:

{
  "Type": "Custom::JackalStack",
  "Properties": {
    "Parameters": {
      STACK_PARAMETERS
    },
    "Location": LOCATION,
    "TemplateURL": URL
  }
}

Resource Response:

The outputs of the stack will be proxied:

{
  "Outputs.OUTPUT_NAME": "OUTPUT_VALUE"
}

Configuration:

{
  "jackal": {
    "cfn": {
      "config": {
        "jackal_stack": {
          "credentials": {
            "storage": {
              TEMPLATE_S3_CREDENTIALS
            },
            LOCATION: {
              "provider": NAME,
              MIASMA_CREDENTIALS
            }
          }
        }
      }
    }
  }
}

Jackal::Cfn::OrchestrationUnit

This resource provides a custom "orchestration unit". The "orchestration unit" consists of a piece of code. It is similar to the AWS::Lambda::Function resource but is more freeform. The command to be executed can be provided as an inline string, or as a remote URL to a compressed zip file containing a run.sh file to execute. If the return value is JSON, the values will be accessible using the Fn::GetAtt intrinsic function on the resource.

Resource Usage:

{
  "Type": "Custom::OrchestrationUnit",
  "Properties": {
    "Exec": "STRING_COMMAND",
    "ExecZip": "REMOTE_URL_TO_ZIP",
    "Env": {
      "CUSTOM_ENV_VARS": "FOR_COMMAND"
    },
    "RawResult": true,
    "OnlyIf": "STRING_COMMAND",
    "NotIf": "STRING_COMMAND",
    "OnCreate": {
      "Exec": "STRING_COMMAND",
      "ExecZip": "REMOTE_URL_TO_ZIP",
      "Env": {
        "CUSTOM_ENV_VARS": "FOR_COMMAND"
      },
      "RawResult": true,
      "OnlyIf": "STRING_COMMAND",
      "NotIf": "STRING_COMMAND"
    },
    "OnUpdate": {
      "Exec": "STRING_COMMAND",
      "ExecZip": "REMOTE_URL_TO_ZIP",
      "Env": {
        "CUSTOM_ENV_VARS": "FOR_COMMAND"
      },
      "RawResult": true,
      "OnlyIf": "STRING_COMMAND",
      "NotIf": "STRING_COMMAND"
    },
    "OnDelete": {
      "Exec": "STRING_COMMAND",
      "ExecZip": "REMOTE_URL_TO_ZIP",
      "Env": {
        "CUSTOM_ENV_VARS": "FOR_COMMAND"
      },
      "RawResult": true,
      "OnlyIf": "STRING_COMMAND",
      "NotIf": "STRING_COMMAND"
    }
  }
}

The Exec inline string command has precedence over the ExecZip if both are provided. The root Exec or ExecZip are the default commands to be run on any action. Customized commands per action can be provided using the OnCreate, OnUpdate, or OnDelete properties. Environment variables defined in the root properties will be merged with environment variables defined for explicit action commands.

Resource response:

If command result is a non-JSON value:

{
  "OrchestrationUnitResult": "RESULT_OF_COMMAND"
}

If the result of the command is a JSON value (for example {"MyKey": "MyValue"}):

{
  "MyKey": "MyValue",
  "OrchestrationUnitResult": "{\"MyKey\": \"MyValue\"}"
}

Info