File: 06-implementing-the-provider.md

package info (click to toggle)
ruby-puppet-resource-api 1.9.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,232 kB
  • sloc: ruby: 9,573; sh: 4; makefile: 2
file content (227 lines) | stat: -rw-r--r-- 7,895 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# Implementing the provider

To expose resources from the HUE Hub to Puppet, a type and provider define and implement the desired interactions. The *type*, like the transport schema, defines the shape of the data using Puppet data types. The implementation in the *provider* takes care of the communication and data transformation.

For this hands on lab, we'll now go through implementing a simple `hue_light` type and provider to manage the state of the light bulbs connected to the HUE Hub.

## Generating the Boilerplate

In your module directory, run `pdk new provider hue_light`. This creates another set of files with a bare-bones type and provider, as well as unit tests.

```
david@davids:~/tmp/hue_workshop$ pdk new provider hue_light
pdk (INFO): Creating '/home/david/tmp/hue_workshop/lib/puppet/provider/hue_light/hue_light.rb' from template.
pdk (INFO): Creating '/home/david/tmp/hue_workshop/lib/puppet/type/hue_light.rb' from template.
pdk (INFO): Creating '/home/david/tmp/hue_workshop/spec/unit/puppet/provider/hue_light/hue_light_spec.rb' from template.
pdk (INFO): Creating '/home/david/tmp/hue_workshop/spec/unit/puppet/type/hue_light_spec.rb' from template.
david@davids:~/tmp/hue_workshop$
```

## Defining the type

The type defines the attributes and allowed values, as well as a couple of other bits of information that concerns the processing of this provider.

For remote resources like this, adding the `'remote_resource'` feature is necessary to alert Puppet of its specific needs. Add the string to the existing `features` array:

```
features: ['remote_resource'],
attributes: {
```

Browsing through the Hub API (TODO: insert link), we can identify a few basic properties we want to manage, for example:

* Whether the lamp is on or off
* The colour of the light (hue and saturation)
* The brightness of the light

To define the necessary attributes, insert the following snippet into the `attributes` hash, after the `name`:

```
    ensure:     {
      type:     'Enum[present, absent]',
      desc:     'Whether this resource should be present or absent on the target system.',
      default:  'present',      
    },
    on:         {
      type:     'Optional[Boolean]',
      desc:     'Switches the light on or off',
    },
    hue:        {
      type:     'Optional[Integer]',
      desc:     'The hue the light color.',
    },
    sat:        {
      type:     'Optional[Integer]',
      desc:     'The saturation of the light colour',
    },
    bri:        {
      type:     'Optional[Integer[1,254]]',
      desc:     <<DESC,
  This is the brightness of a light from its minimum brightness 1 to its maximum brightness 254
DESC
    },
```

## Implementing the Provider

Every provider needs a `get` method, that returns a list of currently existing resources and their attributes from the remote target. For the HUE Hub, this is requires a call to the `lights` endpoint and some data transformation to the format Puppet expects.

### Reading the state of the lights

Replace the example `get` function in `lib/puppet/provider/hue_light/hue_light.rb` with the following code:

```
  # @summary
  #    Returns a list of lights and their attributes as a list of hashes.
  def get(context)
    lights = context.transport.hue_get(context, 'lights')

    return [] if lights.nil?

    lights.collect { |name, content|
      {
        name: name,
        ensure: 'present',
        on: content['state']['on'],
        hue: content['state']['hue'],
        sat: content['state']['sat'],
        bri: content['state']['bri'],
      }
    }
  end
```

This method returns all connected lights from the HUE Hub and allows Puppet to process them. To try this out, you need to setup a test configuration and use `puppet device` to drive your testing.

### Obtaining an API key from the Philips Hue Bridge API
We will need to create an authorized user on the Bridge API, which in turn will provide us with a token we can use for subsequent requests:
- Press the 'Link' button on the Bridge device (**NOTE:** Linking expires after 10 seconds of inactivity by default)
- Perform the following POST request to the API using curl:
```
curl -X POST -d '{"devicetype":"puppetlabs#hue_light_mgmt"}' 'http://192.168.43.195:8000/api'
```
If that has been successful, we should get a `200` response with the user's API token:
```
[
  { "success":
    { 
      "username": "onmdTvd198bMrC6QYyVE9iasfYSeyAbAj3XyQzfL"
    }
  }
]
```

# hub1.conf
```
host: 192.168.43.195
key: onmdTvd198bMrC6QYyVE9iasfYSeyAbAj3XyQzfL
```

```
# device.conf
[hub1]
type hue
url  file:///home/david/git/hue_workshop/spec/fixtures/hub1.conf

[hub2]
type hue
url  file:///home/david/git/hue_workshop/spec/fixtures/hub2.conf
```

```
david@davids:~/tmp/hue_workshop$ pdk bundle install
pdk (INFO): Using Ruby 2.4.5
pdk (INFO): Using Puppet 5.5.12
[...]
Bundle complete! 10 Gemfile dependencies, 90 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

david@davids:~/tmp/hue_workshop$ pdk bundle exec puppet device --libdir lib --deviceconfig device.conf --target hub1 --resource hue_light
pdk (INFO): Using Ruby 2.4.5
pdk (INFO): Using Puppet 5.5.12
hue_light { '1':
  on => true,
  bri => 37,
  hue => 13393,
  sat => 204,
  effect => 'none',
  alert => 'select',
}
hue_light { '2':
  on => true,
  bri => 37,
  hue => 13401,
  sat => 204,
  effect => 'none',
  alert => 'select',
}
hue_light { '3':
  on => true,
  bri => 254,
  hue => 65136,
  sat => 254,
  effect => 'none',
  alert => 'none',
}

david@davids:~/tmp/hue_workshop$
```

### Changing the state of the lights

The final step here is to implement enforcing the desired state of the lights. The default template from the PDK offers `create`, `update`, and `delete` methods to implement the various operations.

For the HUE Hub API, we can remove the `create` and `delete` method. Since the attribute names and data definitions line up with the HUE Hub API, the `update` method is very short.

Replace the `create`, `update`, and `delete` methods with the following code:

```
  def update(context, name, should)
    context.device.hue_put("lights/#{name}/state", should)
  end
```

Now you can also change the state of the lights using a manifest:

```
# traffic_lights.pp
Hue_light { on => true, bri => 10, sat => 254 }
hue_light {
  '1':
    hue => 23536;
  '2':
    hue => 10000;
  '3':
    hue => 65136;
}
```

```
david@davids:~/git/hue_workshop$ pdk bundle exec puppet device --libdir lib --deviceconfig device.conf --target hub1 --apply examples/traffic_lights.pp
pdk (INFO): Using Ruby 2.4.5
pdk (INFO): Using Puppet 5.5.12
Notice: Compiled catalog for hub1 in environment production in 0.06 seconds
Notice: /Stage[main]/Main/Hue_light[1]/hue: hue changed 13393 to 23536 (corrective)
Notice: /Stage[main]/Main/Hue_light[1]/bri: bri changed 70 to 10 (corrective)
Notice: /Stage[main]/Main/Hue_light[1]/sat: sat changed 204 to 255 (corrective)
Notice: /Stage[main]/Main/Hue_light[2]/hue: hue changed 13401 to 10000 (corrective)
Notice: /Stage[main]/Main/Hue_light[2]/bri: bri changed 70 to 10 (corrective)
Notice: /Stage[main]/Main/Hue_light[2]/sat: sat changed 204 to 255 (corrective)
Notice: /Stage[main]/Main/Hue_light[3]/bri: bri changed 254 to 10 (corrective)
Notice: /Stage[main]/Main/Hue_light[3]/sat: sat changed 254 to 255 (corrective)
Notice: Applied catalog in 0.18 seconds

david@davids:~/git/hue_workshop$
```

## Exercise

To round out the API support, add an `effect` attribute that defaults to `none`, but can be set to `colorloop`, and an `alert` attribute that defaults to `none` and can be set to `select`.

Note that this exercise requires exploring new data types and Resource API options.

> TODO: add exercise hints

# Next Up

Now that we can manage state, it's time to [implement a task](./07-implementing-a-task.md) to do some fun transient things with the lights.