A simple helper to create CircleCI configs
I noticed that a lot of my CircleCI config was repetitive. I had written a small lib to help generate the config, but it got messy quickly. So I thought it through a little more and wrote a decent config generation tool. This is the result of that.
It allows the generation of workflows and jobs for CircleCI 2.0, and the sharing of parts of config.
This lib is written in JS. I'd recommend adding it to your devDependencies
so
that everyone on your team can use it.
yarn add -D circle-config-creator
- create a file to generate your config. I use
.circleci/config.js
. - create a
Config
,Workflow
andJob
to get started- see API for details
import Config, { Workflow, Job, executors } from 'circle-config-creator';
const buildContainer = new executors.Docker('circleci/node:latest');
const config = new Config();
const build = new Job('build')
.executor(buildContainer)
.checkout()
.run('./my-build-script.sh')
.saveCache('v1-repo-{{ .Revision }}', '~/project');
const test = new Job('test')
.executor(buildContainer)
.restoreCache('v1-repo-{{ .Revision }}')
.run({ command: './tests.sh', workingDirectory: '~/project/test' });
const workflow = new Workflow('build-and-test')
.job(build)
.job(test, [build]);
config
.workflow(workflow)
.writeSync();
- Ensure you add
Config#write
orConfig#writeSync
to put your config into.circleci/config.yml
Methods are designed to build out configs in a chain. All chainable methods return a copy (operations are immutable).
constructor() => Config
;
workflow(workflow: Workflow) => Config
Add a workflow to the config
location(directory: string) => Config
Params
Name | Type | Default | Description |
---|---|---|---|
directory | string | (required) | The project directory this config is for. Saves to .circleci/config.yml of that directory. |
Change the location that the config will be saved to. This will always save to
the directory you pass in, in the .circleci/config.yml
file.
Defaults to __dirname
(the current directory)
compose() => Object
Generate a JavaScript object based on the Job
s and Workflow
s added
dump() => string
Generate yaml
from the Job
s and Workflow
s added
write(disclaimer: boolean, callback: ?((?ErrnoError) => mixed)) => Promise<void>
Write the config to .circleci/config.yml
Params
Name | Type | Default | Description |
---|---|---|---|
disclaimer | boolean | true | Add a disclaimer to the top of the generated file, warning that changes will be overwritten. |
callback | (optional) (?ErrnoError) => any | undefined | Node-style callback for write completion |
writeSync(disclaimer: boolean) => void
Write the config synchronously to .circleci/config.yml
Params
Name | Type | Default | Description |
---|---|---|---|
disclaimer | boolean | true | Add a disclaimer to the top of the generated file, warning that changes will be overwritten. |
constructor(name: string) => Workflow
job(
job: Job,
requires: ?Array<Job>,
filter: ?Branches,
type: ?'approval', // Deprecated - use [Job](#job)#type
context: ?string,
) => Workflow
Add a job to this workflow
Params
Name | Type | Default | Description |
---|---|---|---|
job | Job | (required) | The job to add to the workflow |
requires | (optional) Array<Job> | [] | Any jobs that this job requires |
filter | (optional) Branches | undefined | Filter the branches that this job runs for |
type | (optional) oneOf('approval') | undefined | The job type. See CircleCI config docs for more (Deprecated - use Job#type) |
context | (optional) string | undefined | The context of the job. See CircleCI config docs for more |
schedule(cron: string, filter: Branches) => Workflow
Add a schedule to run this job against (see triggers on CircleCI docs)
Params
Name | Type | Default | Description |
---|---|---|---|
cron | string | (required) | The cron string to run this job on |
filter | Branches | (required) | The branches to run this schedule against |
constructor(name: string) => Job
updateName(name: string) => Job
Update the name of a job. Useful if you want to take an existing job and modify it slightly to create a new job from it.
Params
Name | Type | Default | Description |
---|---|---|---|
name | string | (required) | The shell to run this job in |
shell(shell: string) => Job
Change the shell this job runs with
Params
Name | Type | Default | Description |
---|---|---|---|
shell | string | (required) | The shell to run this job in |
workingDirectory(directory: string) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
directory | string | (required) | The directory this job runs in |
parallelism(p: number) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
p | number | (required) | The number to run in parallel |
executor(executor: Executor) => Job
The executor to run the job in. This must be set.
Params
Name | Type | Default | Description |
---|---|---|---|
executor | Executor | (required) | The executor to run this job in |
environment(environment: { [key: string]: string }) => Job
environment(key: string, value: string) => Job
This will append to any environment you've already added. Callable multiple times.
Params
Name | Type | Default | Description |
---|---|---|---|
environment | { [string]: string } | (required) | A map of environment variables to inject into the job |
key | string | (required) | The environment key |
value | string | (required) | The environment value |
branches(branches: Branches) => Job
The branch filter config to apply to this job. Note that this will apply at the
workflow level, not at the job level. See CircleCI Docs.
You can take advantage of immutability by settings branches on a job as you pass
it in to the workflow. This was, you have one Job
, and can set branches
differently per-workflow.
Params
Name | Type | Default | Description |
---|---|---|---|
branches | Branches | (required) | The branch config for this job to run inside of |
resourceClass(resourceClass: 'small' | 'medium' | 'medium+' | 'large' | 'xlarge') => Job
Params
Name | Type | Default | Description |
---|---|---|---|
resourceClass | string | (required) | The resource class for this job's container |
run(command: string) => Job
run(config: {
background?: boolean,
command: string,
environment?: { [string]: string },
name?: string,
noOutputTimeout?: string,
shell?: string,
when?: 'always' | 'on_success' | 'on_fail',
workingDirectory?: string,
}) => Job
See the docs for the param meanings
checkout(path: ?string) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
path | (optional) string | ~/project | The path to checkout the code to |
setupRemoteDocker(dockerLayerCaching: boolean = false) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
dockerLayerCaching | (optional) boolean | false | Enable docker layer caching |
saveCache(
key: string,
paths: string | Array<string>,
name: string = 'Saving Cache',
when: 'always' | 'on_success' | 'on_fail' = 'on_success',
) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
key | string | (required) | The key to save the cache to |
paths | string | Array | (required) |
name | string | 'Saving Cache' | The message to display when this step is running |
when | oneOf('always', 'on_success', 'on_fail') | 'on_success' | When to save this cache. Defaults to saving when the job is successful |
restoreCache(key: string | Array<string>, name: string = 'Restoring Cache') => Job
Params
Name | Type | Default | Description |
---|---|---|---|
key | string | (required) | The keys to (attempt) to restore from |
name | string | 'Restoring Cache' | The message to display when this step is running |
progressiveRestoreCache(key: string, base: ?string) => Job
Experimental: This will likely not work for everyone's existing config. It works for most of my use-cases, and it's super handy.
It makes use the CircleCI ability to fallback on caches. It takes the key you
pass in, and sets the restoreCache
job with each key, falling back to base
(
which defaults to two chunks). It only splits on -
s.
Example: with a key of v1-dependencies-{{ checksum "yarn.lock" }}
, this will
result in attempting to restore the cache in the following order:
v1-dependencies-{{ checksum "yarn.lock" }}
v1-dependencies-
If you have a different base, you can specify it. For example, calling
job.progressiveRestoreCache(
'v1-yarn-deps-{{ checksum "yarn.lock" }}-{{ .Revision }}',
'v1-yarn-deps'
)
Will result in trying to restore the following
v1-yarn-deps-{{ checksum "yarn.lock" }}-{{ .Revision }}
v1-yarn-deps-{{ checksum "yarn.lock" }}-
v1-yarn-deps-
Params
Name | Type | Default | Description |
---|---|---|---|
key | string | (required) | The full key to try restoring from |
base | (optional) string | (see examples) | The base key of the cache |
deploy(command: string) => Job
deploy(config: {
background?: boolean,
command: string,
environment?: { [string]: string },
name?: string,
noOutputTimeout?: string,
shell?: string,
when?: 'always' | 'on_success' | 'on_fail',
workingDirectory?: string,
}) => Job
See Job#run and the related CircleCI docs for param information.
storeArtifacts(path: string, destination: ?string) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
path | string | (required) | The directory to save as build artifacts |
destination | (optional) string | undefined | Prefix added to the artifact paths in the artifacts API |
storeTestResults(path: string) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
path | string | (required) | The path to the test results |
persistToWorkspace(root: string, paths: string | Array<string>) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
root | string | (required) | An absolute path or one relative to the working_directory |
paths | string | Array | (required) |
attachWorkspace(at: string) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
at | string | (required) | The directory to attach the workspace at |
addSSHKeys(fingerprints: ?(string | Array<string>)) => Job
Params
Name | Type | Default | Description |
---|---|---|---|
fingerprints | (optional) string | Array | (all SSH keys) |
type(type: 'approval') => Job
This is technically a configuration setting for a Workflow, but is
used in the workflow config as a job. If you apply this setting to a job, it
will not appear in your Job list (so it makes no sense to give it any other
configuration options). Jobs with a type
of approval
also do not need to
have an executor set.
At this point, there is only one type
supported by CircleCI: approval
.
Params
Name | Type | Default | Description |
---|---|---|---|
type | 'approval' | (required) | The type for the job |
Executors are the environment in which your job will run. Every job must be assigned exactly one environment. The generator will throw an error if none are specified. If you try and set more than one, the second will overwrite the first.
It is likely a good idea to create your standard executor as a variable, and pass the same one into all the jobs that need it.
You can access the executors in the following way:
import { executors } from 'circle-config-generator';
constructor(): Docker
There is an alternative constructor, which is likely the one you'll use. It accepts an image string which is a convenient way to instantiate the executor if you don't need multiple images to run together.
constructor(image: string) => Docker
The remainder of the Docker
API is adding an image, configuring that image,
and finally calling .done()
on the image to close it and add it to the Docker
executor. You can add as many images as you like to the Docker
container.
To add an image,
image(image: string) => Image
Params
Name | Type | Default | Description |
---|---|---|---|
image | string | (required) | The name of the docker image to be used |
See the non-required fields of the CircleCI Docker Docs for the meanings of the fields
auth(auth: { username: string, password: string }) => Image
awsAuth(auth: {
aws_access_key_id: string,
aws_secret_access_key: string,
}) => Image
command(...command: Array<string>) => Image
entrypoint(...entrypoint: Array<string>) => Image
environment(env: { [key: string]: string }) => Image
name(name: string) => Image
user(user: string) => Image
done() => Docker
This closes up the image and returns back the parent Docker
container.
Creates the machine executor.
constructor(enabled: ?boolean) => Machine
Params
Name | Type | Default | Description |
---|---|---|---|
enabled | boolean | true | Is the machine enabled? |
enabled(enabled: boolean) => Machine
Params
Name | Type | Default | Description |
---|---|---|---|
enabled | boolean | (required) | Is the machine enabled? |
image(image: string) => Machine
Params
Name | Type | Default | Description |
---|---|---|---|
image | string | (required) | The image to use for the machine |
dockerLayerCaching(enabled: boolean) => Machine
Params
Name | Type | Default | Description |
---|---|---|---|
enabled | boolean | (required) | Should this machine enable Docker layer caching |
Creates a macOS executor.
constructor(version: string) => MacOS
Params
Name | Type | Default | Description |
---|---|---|---|
version | string | (required) | Version of macOS to run - check here for full list |
This is used to set which branches a particular Job
or Workflow
will run on.
constructor() => Branches
ignore(...branches: Array<string>) => Branches
Sets branches that should be ignored by the Job or Trigger using this instance
Note: Setting this will override any Branches#only
branches that have been
set.
only(...branches: Array<string>) => Branches
Filter down to only run on the branches passed in.