-
Notifications
You must be signed in to change notification settings - Fork 5
Workflow Syntax
Factor.io DSL uses the JS-inspired Promises design to define the work to be done by the workflow.
Here is what you need to know about promises...
- The
run
command returns aFactor::Promise
. - Promises can be chained together in a tree structure with zero or more children using the
then
method. - The
then
method returns another Promise. It accepts a block that is called on successfull fullfilment of the Promise. - Promises can also be aggregated with methods like
any
andall
so you can execute multiple promises in parallel and wait for the intended result. more info - Promises can also fail, in which case you can use
rescue
method with a block to capture the exception. - Creating a Promise merely defines the work, it does not queue it or execute it.
- To queue the work, you must call
execute
on the Promise. -
execute
queues the work, but it does not wait for it to complete. It too returns a Promise. - To wait for a Promise to complete (i.e. block the thread), you can call
wait
. - As you can gather from the last points, a Promise can be in four states,
unscheduled
,pending
,rejected
andfulfilled
, and you can get the state of a Promise at any point by callingstate
. You also get the convenientunscheduled?
,pending?
,rejected?
andfulfilled?
methods to check the state. There is alsocompleted?
method which checks if the Promise is eitherrejected
orfulfilled
. - If a Promise is fulfilled you get the value in the
then
block, but you can also get it by callingvalue
. - Similarly, if a Promise is rejected, you get the exception in the
rescue
block, but you also get it by callingreason
.
# Here we create the Promise request to create the Github issue
comment_post = run 'github::issues::create', repo:'skierkowski/hello', title:'', message:msg
# This above method returns immediately without doing any work.
# Now we define what we should do when the issue is created successfully.
comment_complete = comment_post.then do |comment_create_response|
success "The issue was posted successfully"
end.rescue do |ex|
error "Something went wrong: #{ex}"
end
# With the above we chained together two promise promises (then & rescue)
# Now lets queue the work.
executing = comment_complete.execute
# The above queues the work, but doesn't block on it. If we want to block, we an do this...
executing.wait
# In the real world you would probably do all of this in a single go...
# github_comment = run('github::issues::create',...).then{ ... }.rescue{...}
# github_comment.execute.wait
Most Connectors execute commands and return the result when complete; however, some connectors are also capable of emitting events to which a workflow can subscribe. This is particularly useful for doing things like creating a chat bot or listening for a git push
on a Github repo. These Connectors are implemented in a way that they never finish executing, that is, when you run wait
on it's Promise, it will never complete unless you terminate it.
You can listen to events by calling on
on the promise and passing in the event name and a block to execute. The :trigger
event is an event the connector will emit when the event occurs. There are also logging event types, those are cover later.
git_push = run('github::repo::push', repo:'skierkowski/hello')
git_push.on :trigger do |push_info|
success "User #{push_info[:username]} just pushed commit #{push_info[:commit][:id]}"
end
git_push.execute.wait
skierkowski_push = run 'github::repo::push', repo:'skierkowski/hello'
foo_push = run 'github::repo::push', repo:'skierkowski/foo'
listener = on :trigger, skierkowski_push, foo_push do |push_info|
success "User #{push_info[:username]} just pushed commit #{push_info[:commit][:id]}"
end
listener.execute.wait
In addition to subscribing to a single event, sometimes you may want to subscribe to multiple events to take the same action. For example, if you have a test run that depends on two repos, you'll want to run the tests when either one of the repos changes.
git_push = run('github::repo::push', repo:'skierkowski/hello')
git_push.on :log do |type, push_info|
log type, push_info
end
git_push.on :error do |push_info|
error push_info
end
git_push.execute.wait
In addition to :trigger
events, Connectors can also emit logging events. There are four types of logging events, :info
, :warn
, :success
, and :error
. Each of those events will be emitted passing in the string message to the block. Additionally there is the more generic :log
trigger which passes in the type and string.
Sometimes you may want to execute a number of tasks at the same time, like provisioning 5 new EC2 instances, and then continue if either one, all, or n of m are completed. In these cases you can use the any
or all
aggregator methods.
s1 = run 'aws::ec2::create', ...
s2 = run 'aws::ec2::create', ...
s3 = run 'aws::ec2::create', ...
all_ready = all(s1, s2, s3).then |results|
success "ALL the servers were provisioned"
end.execute.wait
at_least_one_ready = any(s1, s2, s3).then |results|
success "At least one server is provisioned"
end.execute.wait
two_ready = any?(2, s1, s2, s3).then |results|
success "At least two servers were provisioned"
end
In the above three cases we have different ways of aggregating. With all
you can wait for all the promises to be fulfilled, with any
you can wait for just one to complete, and with any
with a Integer variable you can make sure that at least n
were fulfilled.
The all
and any
methods also return Promises which is why we use then
to wait for them to be complete. You can also use rescue
to catch cases where the condition isn't met.
Additionally, you can also provide a block to evaluate the results.
s1 = run 'aws::ec2::create', ...
s2 = run 'aws::ec2::create', ...
s3 = run 'aws::ec2::create', ...
all_ready = all s1, s2, s3 do |result|
result.status == :ready
end
all_ready.then |results|
success "ALL the servers were provisioned and in :ready state"
end.execute.wait
Without the block any
and all
just make sure that the child Promises are fulfilled. They don't evaluate the results. By passing in the block you can evaluate the values too (e.g. status==:ready
).