Infrastructure as code using a mix of declarative and imperative programming.
npm install infarepnpm install infarep2) 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
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.
$3
- registerServiceInstanceType(serviceInstanceTypeDef, opts = {}) Accepts a Service Instance Type Def.
Returns a Service Instance Declarer Function.
Example Usage:
`javascript
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:
`javascript
const aws = registerServiceInstanceTypes(require('./aws')) defineProjectInfastructure(async () => {
await aws.lambdaFunction('func1', { src: './source-code' });
})
`
$3
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.
`javascript
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.
$3
`javascript
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
$3
- 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
`javascript
defineProjectInfastructure(async () => {
await aws.lambdaFunction('func1', { src: './func1' });
});
`-
getServiceInstance(name) Returns the service instance that has this human readable name.
Example Usage:
`javascript
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.
`javascript
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.$3
Some services take longer to deploy than others. Speed up deployment times by using Promise.all to deploy independent Service Instances concurrently.Example Usage:
`javascript
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 instanceExample:
`javascript
const localStateStore = require('infarep/localStateStore')defineProjectInstances({
dev: {
stateStore: localStateStore
},
prod: {
stateStore: require('./aws/s3StateStore')
}
});
`$3
- 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.
$3
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:
`javascript
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.$3
Infarep comes with one built in stateStore, localStateStore.Usage Example:
`javascript
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...
`javascript
const { / tools / } = require('infarep/introspect');
`
$3
- 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 Deploys your project to the specified instance.
-
teardown --instance 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.