StateStorage

Serverless state and configuration storage.

Overview

The AWS Parameter Store (SSM) was originally designed as a place to store configuration. It turns out that it is also a pretty handy place for storing small bits of state information in between serverless executions.

StateStorage is a simple wrapper for SSM getParameter and putParameter functions, abstracting it into a contextual storage of small JSON objects.

Why use this instead of AWS SSM API directly?

  • Simple Promise or async syntax
  • Automatic object serialization/deserialization
  • Logging
  • Consistent naming convention

Injector depends on one other utility to work:

Install

npm install @sailplane/state-storage @sailplane/logger @aws-sdk/client-ssm

Examples

Your Lambda will need permission to access the Parameter Store. Here’s an example in serverless.yml:

provider:
  name: aws

  environment:
    STATE_STORAGE_PREFIX: /${opt:stage}/myapp

  iamRoleStatements:
    - Effect: Allow
      Action:
        - ssm:GetParameter
        - ssm:PutParameter
      Resource: "arn:aws:ssm:${opt:region}:*:parameter${self:provider.environment.STATE_STORAGE_PREFIX}/*"

    - Effect: Allow
        Action:
          - kms:Decrypt
          - kms:Encrypt
        Resource: "arn:aws:kms:${opt:region}:*:alias/aws/ssm"
        Condition:
          StringEquals:
            "kms:EncryptionContext:PARAMETER_ARN": "arn:aws:ssm:${opt:region}:*:parameter${self:provider.environment.STATE_STORAGE_PREFIX}/*"

Note that this is the complete set of possible permissions. Not all are needed if only reading parameters or if not using the secure option.

Simple example storing state

import {StateStorage} from "@sailplane/state-storage";

const stateStore = new StateStorage(process.env.STATE_STORAGE_PREFIX!);

export async function myHandler(event, context): Promise<any> {
  let state = await stateStore.get('thing', 'state');
  const result = await processRequest(state, event);
  await stateStore.set('thing', 'state', state);
  return result;
}

See More Examples for another example.

Unit testing your services

Use StateStorageFake to unit test your services that use StateStorage. The fake will store data in instance memory, instead of the AWS Parameter Store.

Type Declarations

import { SSMClient } from "@aws-sdk/client-ssm";
interface StateStorageOptions {
    /** If true, do not log values. */
    quiet?: boolean;
    /**
     * If true, store as encrypted or decrypt on get. Uses account default KMS key.
     * Implies quiet as well.
     */
    secure?: boolean;
    /**
     * If set, set and get the value as is, not JSON. (Only works for string values.)
     */
    isRaw?: boolean;
}
/**
 * Service for storing state of other services.
 * Saved state can be fetched by any other execution of code in the AWS account, region,
 * and environment (dev/prod).
 *
 * Suggested use with Injector:
 *   Injector.register(StateStorage, () => new StateStorage(process.env.STATE_STORAGE_PREFIX));
 */
export declare class StateStorage {
    private readonly namePrefix;
    private readonly ssm;
    /**
     * Construct
     *
     * @param namePrefix prefix string to start all parameter names with.
     *                   Should at least include the environment (dev/prod).
     * @param ssm the SSMClient to use
     */
    constructor(namePrefix: string, ssm?: SSMClient);
    /**
     * Save state for a later run.
     *
     * @param {string} service name of the service (class name?) that owns the state
     * @param {string} name name of the state variable to save
     * @param value content to save
     * @param optionsOrQuiet a StateStorageOptions, or if true sets quiet option. (For backward compatibility.)
     * @returns {Promise<void>} completes upon success - rejects if lacking ssm:PutParameter permission
     */
    set(service: string, name: string, value: any, optionsOrQuiet?: boolean | StateStorageOptions): Promise<void>;
    /**
     * Fetch last state saved.
     *
     * @param {string} service name of the service (class name?) that owns the state
     * @param {string} name name of the state variable to fetch
     * @param optionsOrQuiet a StateStorageOptions, or if true sets quiet option. (For backward compatibility.)
     * @returns {Promise<any>} completes with the saved value, or reject if not found or lacking ssm:GetParameter permission
     */
    get(service: string, name: string, optionsOrQuiet?: boolean | StateStorageOptions): Promise<any>;
    protected generateName(service: string, name: string): string;
}
export {};
import { StateStorage } from "./state-storage";
/**
 * Version of StateStorage to use in unit testing.
 * This fake will store data in instance memory, instead of the AWS Parameter Store.
 */
export declare class StateStorageFake extends StateStorage {
    storage: {};
    constructor(namePrefix: string);
    set(service: string, name: string, value: any, options: any): Promise<void>;
    get(service: string, name: string, options: any): Promise<any>;
}