Skip to content

Commit 51492fe

Browse files
authored
Merge pull request #140 from DavidS/clarify-composite-namevars
(MODULES-9428) Clarify composite namevar edgecases and add examples
2 parents 6bd1811 + dfc520a commit 51492fe

File tree

1 file changed

+111
-40
lines changed

1 file changed

+111
-40
lines changed

language/resource-api/README.md

Lines changed: 111 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Puppet::ResourceApi.register_type(
3737
},
3838
source: {
3939
type: 'String',
40+
behaviour: :parameter,
4041
desc: 'Where to retrieve the key from, can be a HTTP(s) URL, or a local file. Files get automatically required.',
4142
},
4243
# ...
@@ -69,16 +70,15 @@ The `Puppet::ResourceApi.register_type(options)` function takes the following ke
6970
* `autorequire`, `autobefore`, `autosubscribe`, and `autonotify`: a hash mapping resource types to titles. The titles must either be constants, or, if the value starts with a dollar sign, a reference to the value of an attribute. If the specified resources exist in the catalog, Puppet will create the relationsships requested here.
7071
* `features`: a list of API feature names, specifying which optional parts of this spec the provider supports. Currently defined features: `canonicalize`, `simple_get_filter`, and `supports_noop`. See below for details.
7172

72-
For autoloading work, this code needs to go into `lib/puppet/type/<name>.rb` in your module.
73+
For autoloading to work, this code needs to go into `lib/puppet/type/<name>.rb` in your module.
7374

7475
### Composite Namevars ("title_patterns")
7576

76-
Each resource being managed must be identified by a unique title. Usually this is fairly straightforward and a single attribute can be used to act as an identifier. Sometimes though, you need a composite of two attributes to uniquely identify the resource you want to manage.
77+
Each resource being managed must be identified by a unique title. Usually this is fairly straightforward and a single attribute can be used to act as an identifier. Sometimes though, you need a composite of two or more attributes to uniquely identify the resource you want to manage.
7778

78-
If multiple attributes are defined with the `namevar` behaviour, the type SHOULD specify `title_patterns` that will tell Resource API how to get at the attributes from the title. If `title_patterns` is not specified a default pattern is applied, and matches against the first declared `namevar`.
79+
If multiple attributes are defined with the `namevar` behaviour, the type must specify `title_patterns` that will tell Resource API how to get at the attributes from the title.
7980

80-
81-
> Note: The order of title_patterns is important. You should declare the most specific pattern first and end with the most generic.
81+
The `title_patterns` are evaluated in the order they are specified. Evaluation stops after the first match is achieved.
8282

8383
Each title pattern contains the:
8484
* `pattern`, which is a ruby regex containing named captures. The names of the captures MUST be that of the namevar attributes.
@@ -96,7 +96,7 @@ Puppet::ResourceApi.register_type(
9696
title_patterns: [
9797
{
9898
pattern: %r{^(?<package>.*[^-])-(?<manager>.*)$},
99-
desc: 'Where the package and the manager are provided with a hyphen separator',
99+
desc: 'Where the package and the manager are separated by a hyphen',
100100
},
101101
{
102102
pattern: %r{^(?<package>.*)$},
@@ -127,19 +127,20 @@ Matches the first title pattern:
127127
```puppet
128128
# /etc/puppetlabs/code/environments/production/manifests/site.pp
129129
software { php-yum:
130-
ensure=>'present'
130+
ensure => 'present',
131131
}
132132
133133
software { php-gem:
134-
ensure=>'absent'
134+
ensure => 'absent',
135135
}
136136
```
137+
137138
Matches the second title pattern:
138139
```puppet
139140
# /etc/puppetlabs/code/environments/production/manifests/site.pp
140141
software { php:
141-
manager='yum'
142-
ensure=>'present'
142+
ensure => 'present',
143+
manager => 'yum',
143144
}
144145
```
145146

@@ -176,58 +177,109 @@ class Puppet::Provider::AptKey::AptKey
176177
end
177178
```
178179

179-
The `get` method reports the current state of the managed resources. It returns an enumerable of all existing resources. Each resource is a hash with attribute names as keys, and their respective values as values. It is an error to return values not matching the type specified in the resource type. If a requested resource is not listed in the result, it is considered to not exist on the system. If the `get` method raises an exception, the provider is marked as unavailable during the current run, and all resources of this type will fail in the current transaction. The exception message will be reported to the user.
180+
The `get` method reports the current state of the managed resources. It returns an enumerable of all existing resources. Each resource is a hash with attribute names as keys, and their respective values as values.
181+
182+
* It is an error to return values not matching the type specified in the resource type.
183+
* If a requested resource is not listed in the result, it is considered to not exist on the system.
184+
* If the `get` method raises an exception, the provider is marked as unavailable during the current run, and all resources of this type will fail in the current transaction. The exception message will be reported to the user.
185+
* If the type has more than one namevar, each resource hash must have a `:title` key with a single formatted string representing all namevar values. The title must match one of the title patterns. The namevar values computed from that pattern must match their counterparts in the hash.
186+
> Note: This value allows the runtime environment to present resources in a way familiar to the user.
187+
188+
> Example: Referring back to the `software` example above, resources returned from the `get` method require a `title:` value matching the `package:` and `manager:` values:
189+
> ```
190+
> {
191+
> title: 'php-yum',
192+
> package: 'php',
193+
> manager: 'yum',
194+
> ensure: 'present',
195+
> }
196+
> ```
180197
181198
The `set` method updates resources to a new state. The `changes` parameter gets passed a hash of change requests, keyed by the resource's name. Each value is another hash with the optional `:is` and `:should` keys. At least one of the two has to be specified. The values will be of the same shape as those returned by `get`. After the `set`, all resources should be in the state defined by the `:should` values.
182199
200+
> Example for a `changes` request:
201+
> ```
202+
> changes = {
203+
> 'foo' => {
204+
> is: {
205+
> name: 'foo',
206+
> ensure: 'present',
207+
> value: 'original value',
208+
> },
209+
> should: {
210+
> name: 'foo',
211+
> ensure: 'present',
212+
> value: 'new value',
213+
> },
214+
> },
215+
> }
216+
> ```
217+
183218
A missing `:should` entry indicates that a resource should be removed from the system. Even a type implementing the `ensure => [present, absent]` attribute pattern still has to react correctly on a missing `:should` entry. `:is` may contain the last available system state from a prior `get` call. If the `:is` value is `nil`, the resources were not found by `get`. If there is no `:is` key, the runtime did not have a cached state available.
184219
220+
If a type has more than one namevar, the resource's name key is replaced by a hash of the namevars and their values:
221+
222+
> Example for a `changes` request with multiple namevars:
223+
> ```
224+
> changes = {
225+
> { package: 'php', manager: 'yum' } => {
226+
> is: {
227+
> package: 'php',
228+
> manager: 'yum',
229+
> ensure: 'present',
230+
> value: 'original value',
231+
> },
232+
> should: {
233+
> package: 'php',
234+
> manager: 'yum',
235+
> ensure: 'present',
236+
> value: 'new value',
237+
> },
238+
> },
239+
> }
240+
> ```
241+
185242
The `set` method should always return `nil`. Any progress signaling should be done through the logging utilities described below. If the `set` method throws an exception, all resources that should change in this call and haven't already been marked with a definite state, will be marked as failed. The runtime will only call the `set` method if there are changes to be made, especially in the case of resources marked with `noop => true` (either locally or through a global flag). The runtime will not pass them to `set`. See `supports_noop` below for changing this behaviour if required.
186243
187-
Both methods take a `context` parameter which provides utilties from the runtime environment, and is decribed in more detail there.
244+
Both methods take a `context` parameter which provides utilities from the runtime environment, and is described in more detail in its own section below.
188245
189246
## Implementing simple providers
190247
191248
In many cases, the resource type follows the conventional patterns of puppet, and does not gain from the complexities around batch-processing changes. For those cases, the `SimpleProvider` class supplies a proven foundation that reduces the amount of code necessary to get going.
192249
193250
`SimpleProvider` requires that your type follows some common conventions:
194251
195-
* `name` is the name of your namevar attribute
252+
* if the type has a single namevar attribute, it must be called `name`
196253
* `ensure` attribute is present and has the `Enum[absent, present]` type
197254
198255
To start using `SimpleProvider`, inherit from the class like this:
199256
200257
```ruby
201258
require 'puppet/resource_api/simple_provider'
202259
203-
# Implementation for the wordarray type using the Resource API.
260+
# Implementation for the apt_key type using the Resource API.
204261
class Puppet::Provider::AptKey::AptKey < Puppet::ResourceApi::SimpleProvider
205262
# ...
206263
```
207264
208265
Once all of that is in place, instead of the `set` method, the provider needs to implement the `create`, `update` or `delete` methods:
209266

210267
* `create(context, name, should)`: This is called when a new resource should be created.
211-
* `context`: provides utilties from the runtime environment, and is decribed in more detail there.
212-
* `name`: the name or hash of the new resource.
213-
* `should`: a hash of the attributes for the new instance.
214-
215268
* `update(context, name, should)`: This is called when a resource should be updated.
216-
* `context`: provides utilties from the runtime environment, and is decribed in more detail there.
217-
* `name`: the name or hash of the resource to change.
218-
* `should`: a hash of the desired state of the attributes.
219-
220269
* `delete(context, name)`: This is called when a resource should be deleted.
221-
* `context`: provides utilties from the runtime environment, and is decribed in more detail there.
222-
* `name`: the name or hash of the resource that should be deleted.
270+
271+
The parameters of these methods always carry the same values:
272+
273+
* `context`: provides utilities from the runtime environment, and is described in more detail in its own section below.
274+
* `name`: the name (if there is only one namevar) or hash of namevars (if there are multiple) of the resource.
275+
* `should`: a hash of the desired state of the attributes. This is not passed to the delete method.
223276

224277
The `SimpleProvider` takes care of basic logging, and error handling.
225278

226-
When a `type` has only a single namevar defined, `SimpleProvider` will pass the value of that attribute as `name` to the `create`, `update` and `delete` methods. If multiple namevars are defined, `SimpleProvider` will instead pass a hash. The hash contains the composite name of `title`, and all the namevars and their values, for example:
279+
When a `type` has only a single namevar defined, `SimpleProvider` will pass the value of that attribute as `name` to the `create`, `update` and `delete` methods. If multiple namevars are defined, `SimpleProvider` will instead pass a hash. The hash contains all the namevars and their values, for example:
227280

228281
```
229282
{
230-
title: 'foo/bar',
231283
name: 'bar',
232284
group: 'foo',
233285
}
@@ -252,11 +304,11 @@ Puppet::ResourceApi.register_type(
252304
class Puppet::Provider::AptKey::AptKey
253305
def canonicalize(context, resources)
254306
resources.each do |r|
255-
r[:name] = if r[:name].start_with?('0x')
256-
r[:name][2..-1].upcase
257-
else
258-
r[:name].upcase
259-
end
307+
r[:id] = if r[:id].start_with?('0x')
308+
r[:id][2..-1].upcase
309+
else
310+
r[:id].upcase
311+
end
260312
end
261313
end
262314
```
@@ -265,7 +317,21 @@ The runtime environment needs to compare user input from the manifest (the desir
265317

266318
The `canonicalize` method transforms its `resources` argument into the standard format required by the rest of the provider. The `resources` argument to `canonicalize` is an enumerable of resource hashes matching the structure returned by `get`. It returns all passed values in the same structure with the required transformations applied. It is free to reuse or recreate the data structures passed in as arguments. The runtime environment must use `canonicalize` before comparing user input values with values returned from `get`. The runtime environment always passes canonicalized values into `set`. If the runtime environment requires the original values for later processing, it protects itself from modifications to the objects passed into `canonicalize`, for example through creating a deep copy of the objects.
267319

268-
The `context` parameter is the same passed to `get` and `set`, which provides utilties from the runtime environment, and is decribed in more detail there.
320+
The `context` parameter provides utilities from the runtime environment, and is described in more detail there. It is the same as is passed to `get` and `set`.
321+
322+
> Example: The `resources` parameter is an array of resources hashes:
323+
> ```ruby
324+
> resources = [
325+
> {
326+
> id: '0x12345678',
327+
> source: '/net/gold/example.com/www/key1.gpg',
328+
> },
329+
> {
330+
> id: '1234567812345678123456781234567812345678',
331+
> source: '/net/gold/example.com/www/key2.gpg',
332+
> },
333+
> ]
334+
> ```
269335
270336
> Note: When the provider implements canonicalization, it aims to always log the canonicalized values. As a result of `get` and `set` producing and consuming canonically formatted values, this is not expected to present extra cost.
271337
@@ -304,17 +370,22 @@ Puppet::ResourceApi.register_type(
304370
# lib/puppet/provider/apt_key/apt_key.rb
305371
class Puppet::Provider::AptKey::AptKey
306372
def get(context, names = nil)
307-
[
308-
{
309-
name: 'name',
310-
# ...
311-
},
312-
]
373+
if names == nil
374+
return all_instances
375+
else
376+
names.collect { |n| find_instance(n) }
377+
end
313378
end
314379
```
315380

316381
Some resources are very expensive to enumerate. The provider can implement `simple_get_filter` to signal extended capabilities of the `get` method to address this. The provider's `get` method will be called with an array of resource names, or `nil`. The `get` method must at least return the resources mentioned in the `names` array, but may return more than those. If the `names` parameter is `nil`, all existing resources should be returned. The `names` parameter defaults to `nil` to allow simple runtimes to ignore this feature.
317382

383+
For types with multiple namevars, the `names` array will consist of hashes of the namevars and their values instead of simple values:
384+
385+
```ruby
386+
[ { package: 'php', manager: 'yum' }, {package: 'mysql', manager: 'yum'} ]
387+
```
388+
318389
The runtime environment calls `get` with a minimal set of names, and keeps track of additional instances returned to avoid double querying. To gain the most benefits from batching implementations, the runtime minimizes the number of calls into `get`.
319390

320391
### Provider feature: `supports_noop`
@@ -353,12 +424,12 @@ Puppet::ResourceApi.register_type(
353424

354425
# lib/puppet/provider/nx9k_vlan/nexus.rb
355426
class Puppet::Provider::Nx9k_vlan::Nexus
356-
def set(context, changes, noop: false)
427+
def set(context, changes)
357428
changes.each do |name, change|
358429
is = change.has_key? :is ? change[:is] : get_single(name)
359430
should = change[:should]
360431
# ...
361-
context.transport.do_something unless noop
432+
context.transport.do_something
362433
end
363434
end
364435
```

0 commit comments

Comments
 (0)