Class: Furnish::Protocol
- Inherits:
-
Object
- Object
- Furnish::Protocol
- Defined in:
- lib/furnish/protocol.rb
Overview
Furnish::Protocol implements a validating protocol for state transitions. It is an optional feature and not necessary for using Furnish.
A Furnish::ProvisionerGroup looks like this:
thing_a -> thing_b -> thing_c
Where these things are furnish provisioners. When the scheduler says it’s ready to provision this group, it executes thing_a’s startup routine, passes its results to thing_b’s startup routine, and then thing_b’s startup routine passes its things to thing_c’s startup routine.
Presuming this all succeeds (and returns truthy values), the group is marked as ‘solved’, the scheduler considers it finished and will ignore future requests to provision it again. It also will start working on anything that depends on its solved state.
A problem is that you have to know ahead of time how a, b, and c interact for this to be successful. For example, you can’t allocate an EC2 security group, then an instance, then a VPC, and expect the security group and instance to live in that VPC. It’s not only out of order, but the security group doesn’t know enough at the time it runs to leverage the VPC, because the VPC doesn’t exist yet.
Furnish::Protocol lets you describe what each provisioner requires, what it accepts, and what it yields, so that analysis can be performed at scheduler time (when it’s configured) instead of provisioning time (when it actually runs). This surfaces issues quicker and has some additional advantages for interfaces where users may not have full visibility into what the provisioners do (such as closed source provisioners, or inadequately documented ones).
Here’s a description of how this logic works for two adjacent provisioners in the group, a and b:
-
if Provisioner A and Provisioner B implement Furnish::Protocol
-
if B requires anything, and A yields all of it with the proper types
-
if B accepts anything, and A yields any of it with the proper types
-
success
-
-
else failure
-
-
if B accepts anything, and A yields any of it with the proper types
-
success
-
-
if B has #accepts_from_any set to true
-
success
-
-
if B accepts nothing
-
success
-
-
else failure
-
-
else success
Provisioners at the head and tail do not get subject to acceptance tests because there’s nothing to yield, or nothing to accept what is yielded.
Constant Summary collapse
- VALIDATOR_NAMES =
:method: :call-seq:
yields(name) yields(name, description) yields(name, description, type)
Specifies what the provisioner is expected to yield. This is the producer for the consumer counterparts #requires and #accepts. The configuration made here will be used in both #requires_from and #accepts_from for determining if two provisioners can talk to each other.
See the logic explanation in Furnish::Protocol for more information.
See Furnish::Provisioner::API.configure_startup for a usage example.
[:requires, :accepts, :yields]
Instance Method Summary collapse
-
#[](key) ⇒ Object
look up a rule set – generally should not be used by consumers.
-
#accepts_from(protocol) ⇒ Object
For a passed Furnish::Protocol object, ensures that at least one thing this protocol object accepts is satisfied by what that Furnish::Protocol object yields.
-
#accepts_from_any(val) ⇒ Object
Allow #accepts_from to completely mismatch with yields from a compared provisioner and still succeed.
-
#accepts_from_any? ⇒ Boolean
Predicate for the value of #accepts_from_any.
-
#configure(&block) ⇒ Object
This runs the block given instance evaled against the current Furnish::Protocol object.
-
#initialize ⇒ Protocol
constructor
Construct a Furnish::Protocol object.
-
#requires_from(protocol) ⇒ Object
For a passed Furnish::Protocol object, ensures that this protocol object satisfies its requirements based on what it yields.
Constructor Details
#initialize ⇒ Protocol
Construct a Furnish::Protocol object.
112 113 114 115 |
# File 'lib/furnish/protocol.rb', line 112 def initialize @hash = Hash[VALIDATOR_NAMES.map { |n| [n, { }] }] @configuring = false end |
Instance Method Details
#[](key) ⇒ Object
look up a rule set – generally should not be used by consumers.
152 153 154 |
# File 'lib/furnish/protocol.rb', line 152 def [](key) @hash[key] end |
#accepts_from(protocol) ⇒ Object
For a passed Furnish::Protocol object, ensures that at least one thing this protocol object accepts is satisfied by what that Furnish::Protocol object yields.
See the logic discussion in Furnish::Protocol for a deeper explanation.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/furnish/protocol.rb', line 184 def accepts_from(protocol) not_configurable(__method__) return true unless protocol yp = protocol[:yields] ap = self[:accepts] return true if ap.keys.empty? if (yp.keys & ap.keys).empty? return self[:accepts_from_any] end return ap.keys.any? { |k| yp.has_key?(k) && ap[k][:type].ancestors.include?(yp[k][:type]) } end |
#accepts_from_any(val) ⇒ Object
Allow #accepts_from to completely mismatch with yields from a compared provisioner and still succeed. Use with caution.
See the logic discussion in Furnish::Protocol for a deeper explanation.
138 139 140 |
# File 'lib/furnish/protocol.rb', line 138 def accepts_from_any(val) @hash[:accepts_from_any] = val end |
#accepts_from_any? ⇒ Boolean
Predicate for the value of #accepts_from_any.
145 146 147 |
# File 'lib/furnish/protocol.rb', line 145 def accepts_from_any? !!@hash[:accepts_from_any] end |
#configure(&block) ⇒ Object
This runs the block given instance evaled against the current Furnish::Protocol object. It is used by Furnish::Provisioner::API’s syntax sugar.
Additionally it sets a simple lock to ensure the assertions Furnish::Protocol provides cannot be used during configuration time, like #accept_from and #requires_from.
126 127 128 129 130 |
# File 'lib/furnish/protocol.rb', line 126 def configure(&block) @configuring = true instance_eval(&block) @configuring = false end |
#requires_from(protocol) ⇒ Object
For a passed Furnish::Protocol object, ensures that this protocol object satisfies its requirements based on what it yields.
See the logic discussion in Furnish::Protocol for a deeper explanation.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/furnish/protocol.rb', line 162 def requires_from(protocol) not_configurable(__method__) return true unless protocol yp = protocol[:yields] rp = self[:requires] rp.keys.empty? || ( (yp.keys & rp.keys).sort == rp.keys.sort && rp.keys.all? { |k| rp[k][:type].ancestors.include?(yp[k][:type]) } ) end |