infarep
v3.4.4
Published
Infrastructure as code using a mix of declarative and imperative programming.
Downloads
4
Readme
Infarep (pronounced: In - Fa - Rep)
Infrastructure as code using a mix of declarative and imperative programming.
Getting Started
Install the Infarep DSL in your project.
npm install infarep
Write your infastructure.
touch project.infarep.js
require('infarep').injectAll(); defineProjectMetadata({ name: require('./package.json').name }) defineProjectInstances({ dev: { // Dev instance configuration goes here. }, prod: { // Prod instance configuration goes here. } }); defineProjectInfastructure(() => { });
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 bygetServiceInstance
.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 thecreate
event handler. It is useful to include thehumanReadableName
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 returnundefined
if no Service Instance exists with the given human readable name.setServiceInstancePersistentState
will be passedundefined
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.