3.4.4 • Published 4 years ago

infarep v3.4.4

Weekly downloads
-
License
ISC
Repository
-
Last release
4 years ago

Infarep (pronounced: In - Fa - Rep)

Infrastructure as code using a mix of declarative and imperative programming.

Getting Started

1) Install the Infarep DSL in your project.

`npm install infarep`

2) Write your infastructure.

`touch project.infarep.js`

```javascript
require('infarep').injectAll();

defineProjectMetadata({
    name: require('./package.json').name
})

defineProjectInstances({
    dev: {
        // Dev instance configuration goes here.
    },
    prod: {
        // Prod instance configuration goes here.
    }
});

defineProjectInfastructure(() => {
    
});
```

4) Deploy

`node project.infarep.js deploy --instance dev`

The Infarep DSL

The Domain Specific Language used to write infastructure. The DSL is based on NodeJS javascript.

require('infarep').injectAll();

The building blocks of your infastructure

Infarep is unopinionated. You declare or import building blocks specific to the needs of your project. The blocks are portable, configurable, and reusable.

For Example: If you are using AWS, your building blocks would be things like: Lambda Function, RDS Database, ElastiCache Redis Cluster, IAM Role, etc.

Building blocks are formally known as Service Instance Types.

DSL functions related to Service Instance Types

  • registerServiceInstanceType(serviceInstanceTypeDef, opts = {})

    Accepts a Service Instance Type Def.

    Returns a Service Instance Declarer Function.

    Example Usage:

    const lambdaFunction
        = registerServiceInstanceType(require('./aws/lambdaFunction'))
    
    defineProjectInfastructure(async () => {
        await lambdaFunction('func1', { src: './source-code' })
    });
  • registerServiceInstanceTypes(serviceInstanceTypeDefs, opts = {})

    Accepts an array of Service Instance Type Defs.

    Returns a map of Service Instance Type names to their Service Instance Declarer Functions.

    Example Usage:

    const aws = registerServiceInstanceTypes(require('./aws'))
    
    defineProjectInfastructure(async () => {
        await aws.lambdaFunction('func1', { src: './source-code' });
    })

Deprecating the use of Service Instance Types in your Project.

Special care must be taken when migrating from one Service Instance Type to another. You may be tempted to remove all traces of the deprecated Service Instance Type from your project before deploying the new version. This will not work. Because If you don't register the deprecated Service Instance Type, Infarep will not know how to destroy the old Service Instances.

The solution to this problem is to remove the Service Instance Declarations but still register the Service Instance Types. Then deploy your changes to all instances. Then, once there are no Service Instances using the old Service Instance Type, you can safely remove the registration statement.

The Infarep DSL allows you to mark certain Service Instance Types as deprecated for use in your project.

const aws = registerServiceInstanceTypes(require('./aws'), { deprecated: true })

This syntax can also be used with the singular registerServiceInstanceType function.

After every deployment, infarep-cli checks the state of all instances. Once no instances are using the deprecated Service Instance Type, Infarep will notify you that it ok to remove the registration statement.

Writing a Service Instance Type Def

module.exports = ({ projectInstance }) => ({
    name: 'com.icloud.duncpro.example',
    create: ({ config, exposes, stores, humanReadableName }) => {
        Object.assign(exposes, stores);
    },
    update: ({ config, exposes, stores }) => {
        Object.assign(exposes, stores);
    },
    delete: ({ stores }) => {}
});

Lifecycle event handlers like create, update, and delete may be async.

The deconstructed object accepted by these event handlers is called the Event Context.

  • config is the configuration that was passed to the Service Instance Declarer Function. This object is frozen.
  • exposes is the object that will be returned by getServiceInstance.
  • stores is the persistent state store object associated with the Service Instance. It is typically used to store identifiers. It is not used for storing credentials. This objcet is deeply frozen after your event handler executes.
  • humanReadableName is only available in the create event handler. It is useful to include the humanReadableName in the other identifiers of your Service Instances.

Wiring your project together

DSL functions related to Service Instances.

  • defineProjectInfastructure(func)

    Accepts a function. The function takes no arguments. Return values will not be used unless a Promise is returned in which case it is awaited.

    The defineProjectInfastructure function is used to specify the Deployer Function. All Service Instances used by your app are declared inside the Deployer Function.

    The defineProjectInfastructure function should only be called once.

    Example Usage

    defineProjectInfastructure(async () => {
        await aws.lambdaFunction('func1', { src: './func1' });
    });
  • getServiceInstance(name)

    Returns the service instance that has this human readable name.

    Example Usage:

    const { /* exposes */ } = getServiceInstance('database1');
  • edit(serviceInstance, editor)

    Sometimes it is necessary to make changes to certain Service Instances multiple times during deployment. edit provides this capability.

    Example: Using edit to allow AWS Lambda functions to invoke each other.

    await aws.iamRole('lamdbaExecRole');
    
    await aws.lambdaFunction('function0', { execRole: lambdaExecRole });
    await aws.lambdaFunction('function1', { execRole: lambdaExecRole });
    
    await aws.iamPolicy('invokeFuncsPolicy', [
        getServiceInstance('function0').permission.invoke,
        getServiceInstance('function1').permission.invoke
    ]);
    
    await edit('lambdaExecRole', ({ Policies }) => {
        Policies.push(getServiceInstance('invokeFuncPolicy').arn)
    });

    After edits are made to a Service Instance, its exposes object is recalculated.

Speeding up deployments

Some services take longer to deploy than others. Speed up deployment times by using Promise.all to deploy independent Service Instances concurrently.

Example Usage:

await Promise.all([
    sqlDatabase('myDatabase'),
    redisCluster('myCache')
])

Running Multiple Instances of Your Project

Infarep projects are completely portable. It is easy to have a seperate development instance and production instance

Example:

const localStateStore = require('infarep/localStateStore')

defineProjectInstances({
    dev: {
        stateStore: localStateStore
    },
    prod: {
        stateStore: require('./aws/s3StateStore')
    }
});

DSL functions related to Project Instances

  • defineProjectInstances(func | obj)

    Accepts a function. The function should return a map of project instance names to configurations. The function may return a promise.

    If only in-line computation is required, you can omit the function wrapper and just pass the map directly.

Writing a stateStore

Infarep expects every project instance to provide a stateStore. This is an object that is capable storing and retrieving perstent data that is related to the project instance.

The stateStore is expected to be able to store and load javascript objects as state.

Example:

module.exports = (projectInstance) => ({
    prepare: async () => {},
    setServiceInstancePersistentState: async (name, typeName, state) => {},
    getServiceInstancePersistentState: async (name, typeName) => {},
    getAllServiceInstancePersistentStates: async () => {},
    done: async () => {}
})

Indicator Data Types:

  • getServiceInstancePersistentState should return undefined if no Service Instance exists with the given human readable name.
  • setServiceInstancePersistentState will be passed undefined when the Service Instance is deleted.

If you are storing state in a remote netowrk location (database, ftp server, etc.), repeatedly making requests can become very time consuming. In these cases you should store the state locally in memory and the push all the state changes at once in the done method.

Using the built-in stateStore.

Infarep comes with one built in stateStore, localStateStore.

Usage Example:

defineProjectInstances({
    dev: {
        stateStore: require('infarep/localStateStore')
    }
})

Bundled Introspection Tools (Planned Feature, Not Yet Implemented)

The DSL also comes with a suite of tools to help you inspect the state of your infastructure.

These tools can be imported like so...

const { /* tools */ } = require('infarep/introspect');

Available Functions

  • getAllServiceInstanceStates(projectInstanceName)

    Returns a map of human readable Service Instance names to stores.

  • getServiceInstanceState(projectInstanceName, humanReadableServiceInstanceName)

    Returns the stores object of the Service Instance with this human readable name.

  • getProjectInstanceConfiguration(projectInstanceName)

    Return the configuration object of a specific project instance.

The Infarep CLI

The "project.infarep.js" file accepts the following commands...

  • deploy --instance <instance name>

    Deploys your project to the specified instance.

  • teardown --instance <instance name>

    Teardown the given project instance. Destroys all Service Instances. You should backup any databases before running this command.

    Service Instances are destroyed serially in the opposite order of their creation.

CLI FAQ

  • If (auto-awaited) appears next to the name of a Service Instance in the deployment log, it means you forgot to await the upserter function.
3.4.4

4 years ago

3.4.3

4 years ago

3.3.3

4 years ago

3.3.2

4 years ago

3.3.1

4 years ago

3.3.0

4 years ago

3.2.2

4 years ago

2.1.2

4 years ago

2.0.2

4 years ago

2.0.1

4 years ago

2.0.0

4 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago