puppet-resource_api 1.8.9 → 1.8.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docs/README.md +7 -0
- data/docs/hands-on-lab/01-installing-prereqs.md +16 -0
- data/docs/hands-on-lab/02-connecting-to-the-lightbulbs-emulator.png +0 -0
- data/docs/hands-on-lab/02-connecting-to-the-lightbulbs.md +26 -0
- data/docs/hands-on-lab/03-creating-a-new-module.md +47 -0
- data/docs/hands-on-lab/03-creating-a-new-module_vscode.png +0 -0
- data/docs/hands-on-lab/04-adding-a-new-transport.md +123 -0
- data/docs/hands-on-lab/05-implementing-the-transport-hints.md +19 -0
- data/docs/hands-on-lab/05-implementing-the-transport.md +126 -0
- data/docs/hands-on-lab/06-implementing-the-provider.md +227 -0
- data/docs/hands-on-lab/07-implementing-a-task.md +181 -0
- data/lib/puppet/resource_api.rb +4 -1
- data/lib/puppet/resource_api/base_context.rb +4 -0
- data/lib/puppet/resource_api/version.rb +1 -1
- metadata +12 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23c38c650148fd1697a642774a4b470e208f1f45c3e47dc75c1d93795be7bb38
|
4
|
+
data.tar.gz: 884337cc89bd510169e9822982fbed68866dff0d9f30c027b3ebfca600670ce1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 721c4d232ba09cef37c387bfec47d557de2873f858bbf7a3e72a9b7f66ef993e0298eb954cdd1864ad4d5091b9aa2fe83480f51a5324255861cd7fa2df80a6c2
|
7
|
+
data.tar.gz: af6b72118cc710bac4eec4b4fbae849f5620f1fc29b949afb0dd2baca5ebc2237b55ccf76c696ab6a5be8e534f7d3fbaead41829e256926a07791fc6a064c8e3
|
data/docs/README.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Resource API hands-on lab
|
2
|
+
|
3
|
+
The Resource API hands-on lab walks you through creating a native integration with Puppet. After completely this lab, you will have a fully functioning module to manage Philips HUE lights.
|
4
|
+
|
5
|
+
>Note: These labs are intended for both new and experienced developers. If you have any feedback or suggestions for improvement, post it in the [issues section](https://github.com/puppetlabs/puppet-resource_api/issues).
|
6
|
+
|
7
|
+
To start with, we'll go through [installing Puppet Development Kit](./hands-on-lab/01-installing-prereqs.md)(PDK).
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Install Puppet Development Kit (PDK) and other tools
|
2
|
+
|
3
|
+
To start, install Puppet Development Kit (PDK), which provides all the necessary tools and libraries to build and test modules. We also recommend an emulator for the target device, a code editor with good Ruby and Puppet support, and git — a version control system to keep track of your progress.
|
4
|
+
|
5
|
+
1. [Download PDK](https://puppet.com/download-puppet-development-kit) on your platform of choice.
|
6
|
+
|
7
|
+
2. If you do not have a Philips HUE hub and bulbs available, you can download the [Hue-Emulator](https://github.com/SteveyO/Hue-Emulator/raw/master/HueEmulator-v0.8.jar). You need to have Java installed to run this.
|
8
|
+
|
9
|
+
3. To edit code, we recommend the cross-platform editor [VSCode](https://code.visualstudio.com/download), with the [Ruby](https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby) and [Puppet](https://marketplace.visualstudio.com/items?itemName=jpogran.puppet-vscode) extensions. There are lots of other extensions that can help you with your development workflow.
|
10
|
+
|
11
|
+
4. Git is a version control system that helps you keep track of changes and collaborate with others. As we go through hands-on lab, we will show you some integrations with cloud services. If you have never used git before, ignore this and all related steps.
|
12
|
+
|
13
|
+
|
14
|
+
## Next up
|
15
|
+
|
16
|
+
After installing the relevant tools, you'll [light up a few](./02-connecting-to-the-lightbulbs.md).
|
Binary file
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Connecting to the light bulbs
|
2
|
+
|
3
|
+
There are no technical restrictions on the kinds of remote devices or APIs you can connect to with transports. For this lab, we will connect to a Philips HUE hub and make some colourful wireless light bulbs light up. If you (understandably) do not have physical devices available, you can use the Hue Emulator.
|
4
|
+
|
5
|
+
## Hue Emulator
|
6
|
+
|
7
|
+
Use `java -jar` with the emulator's filename to run it, for example:
|
8
|
+
|
9
|
+
```
|
10
|
+
david@davids:~$ java -jar ~/Downloads/HueEmulator-v0.8.jar
|
11
|
+
```
|
12
|
+
|
13
|
+
It does not produce any output on the command line, but a window pops up with a hub and a few predefined lights:
|
14
|
+
|
15
|
+
![](./02-connecting-to-the-lightbulbs-emulator.png)
|
16
|
+
|
17
|
+
All you need now is to input a port (the default 8000 is usually fine) and click "Start" to activate the built-in server.
|
18
|
+
|
19
|
+
## Connecting to your hub
|
20
|
+
|
21
|
+
To connect to an actual hub, you need be able to access the bub on your network and get an API key. See the [Philips Developer docs](http://www.developers.meethue.com/documentation/getting-started) (registration required).
|
22
|
+
|
23
|
+
|
24
|
+
## Next up
|
25
|
+
|
26
|
+
Now that you have some lights up, you'll [create a module](./03-creating-a-new-module.md).
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Create a module
|
2
|
+
|
3
|
+
Depending on your preferences, you can use the VSCode/PDK integration or run PDK from the command line in a terminal of your choice.
|
4
|
+
|
5
|
+
## Create a module with VSCode
|
6
|
+
|
7
|
+
Spin up the Command Palette (⇧⌘P on the Mac or Ctrl+Shift+P on Windows and Linux) and search for the `Puppet: PDK New Module` task:
|
8
|
+
|
9
|
+
![](./03-creating-a-new-module_vscode.png)
|
10
|
+
|
11
|
+
Click Enter (↩) to execute this and follow the on-screen prompts.
|
12
|
+
|
13
|
+
The module will open in a new VSCode window.
|
14
|
+
|
15
|
+
## Create a module from the command line
|
16
|
+
|
17
|
+
In your regular workspace (for example your home directory), run the following:
|
18
|
+
|
19
|
+
```
|
20
|
+
pdk new module hue_workshop --skip-interview
|
21
|
+
```
|
22
|
+
|
23
|
+
This command creates a new module `hue_workshop` in your directory of the same name, using all defaults. The output will look like:
|
24
|
+
|
25
|
+
```
|
26
|
+
david@davids:~/tmp$ pdk new module hue_workshop --skip-interview
|
27
|
+
pdk (INFO): Creating new module: hue_workshop
|
28
|
+
pdk (INFO): Module 'hue_workshop' generated at path '/home/david/tmp/hue_workshop', from template 'file:///opt/puppetlabs/pdk/share/cache/pdk-templates.git'.
|
29
|
+
pdk (INFO): In your module directory, add classes with the 'pdk new class' command.
|
30
|
+
david@davids:~/tmp$ ls hue_workshop/
|
31
|
+
appveyor.yml data files Gemfile.lock manifests Rakefile spec templates
|
32
|
+
CHANGELOG.md examples Gemfile hiera.yaml metadata.json README.md tasks
|
33
|
+
david@davids:~/tmp$
|
34
|
+
```
|
35
|
+
|
36
|
+
To read more about the different options when creating new modules, see [PDK docs](https://puppet.com/docs/pdk/1.x/pdk_creating_modules.html).
|
37
|
+
|
38
|
+
Open the new directory in your code editor:
|
39
|
+
|
40
|
+
```
|
41
|
+
code -a hue_workshop
|
42
|
+
```
|
43
|
+
|
44
|
+
|
45
|
+
## Next up
|
46
|
+
|
47
|
+
Now that you have created a module, you'll [add a transport](./04-adding-a-new-transport.md).
|
Binary file
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Add a new transport
|
2
|
+
|
3
|
+
Starting with PDK 1.12.0 there is the `pdk new transport` command, that you can use to create the base files for your new transport:
|
4
|
+
|
5
|
+
Next, we'll active a few future defaults. In the `hue_workshop` directory, create a file called `.sync.yml` and paste the following:
|
6
|
+
|
7
|
+
```
|
8
|
+
# .sync.yml
|
9
|
+
---
|
10
|
+
Gemfile:
|
11
|
+
optional:
|
12
|
+
':development':
|
13
|
+
- gem: 'puppet-resource_api'
|
14
|
+
- gem: 'faraday'
|
15
|
+
- gem: 'rspec-json_expectations'
|
16
|
+
spec/spec_helper.rb:
|
17
|
+
mock_with: ':rspec'
|
18
|
+
```
|
19
|
+
|
20
|
+
Run `pdk update` in the module's directory to deploy the changes in the module:
|
21
|
+
|
22
|
+
```
|
23
|
+
david@davids:~/tmp/hue_workshop$ pdk update --force
|
24
|
+
pdk (INFO): Updating david-hue_workshop using the default template, from 1.10.0 to 1.10.0
|
25
|
+
|
26
|
+
----------Files to be modified----------
|
27
|
+
Gemfile
|
28
|
+
spec/spec_helper.rb
|
29
|
+
|
30
|
+
----------------------------------------
|
31
|
+
|
32
|
+
You can find a report of differences in update_report.txt.
|
33
|
+
|
34
|
+
Do you want to continue and make these changes to your module? Yes
|
35
|
+
|
36
|
+
------------Update completed------------
|
37
|
+
|
38
|
+
2 files modified.
|
39
|
+
|
40
|
+
david@davids:~/tmp/hue_workshop$
|
41
|
+
```
|
42
|
+
|
43
|
+
Then, create the actual transport:
|
44
|
+
|
45
|
+
```
|
46
|
+
david@davids:~/tmp/hue$ pdk new transport hue
|
47
|
+
pdk (INFO): Creating '/home/david/tmp/hue/lib/puppet/transport/hue.rb' from template.
|
48
|
+
pdk (INFO): Creating '/home/david/tmp/hue/lib/puppet/transport/schema/hue.rb' from template.
|
49
|
+
pdk (INFO): Creating '/home/david/tmp/hue/lib/puppet/util/network_device/hue/device.rb' from template.
|
50
|
+
pdk (INFO): Creating '/home/david/tmp/hue/spec/unit/puppet/transport/hue_spec.rb' from template.
|
51
|
+
pdk (INFO): Creating '/home/david/tmp/hue/spec/unit/puppet/transport/schema/hue_spec.rb' from template.
|
52
|
+
david@davids:~/tmp/hue$
|
53
|
+
```
|
54
|
+
|
55
|
+
## Checkpoint
|
56
|
+
|
57
|
+
To validate your new module and transport, run `pdk validate --parallel` and `pdk test unit`:
|
58
|
+
|
59
|
+
```
|
60
|
+
david@davids:~/tmp/hue$ pdk validate --parallel
|
61
|
+
pdk (INFO): Running all available validators...
|
62
|
+
pdk (INFO): Using Ruby 2.5.5
|
63
|
+
pdk (INFO): Using Puppet 6.4.2
|
64
|
+
┌ [✔] Validating module using 5 threads ┌
|
65
|
+
├──[✔] Checking metadata syntax (metadat├──son tasks/*.json).
|
66
|
+
├──[✔] Checking task names (tasks/**/*).├──
|
67
|
+
└──[✔] Checking YAML syntax (["**/*.yaml├──"*.yaml", "**/*.yml", "*.yml"]).
|
68
|
+
└──[/] Checking module metadata style (metadata.json).
|
69
|
+
└──[✔] Checking module metadata style (metadata.json).
|
70
|
+
info: puppet-syntax: ./: Target does not contain any files to validate (**/*.pp).
|
71
|
+
info: task-metadata-lint: ./: Target does not contain any files to validate (tasks/*.json).
|
72
|
+
info: puppet-lint: ./: Target does not contain any files to validate (**/*.pp).
|
73
|
+
david@davids:~/tmp/hue$ pdk test unit
|
74
|
+
pdk (INFO): Using Ruby 2.5.5
|
75
|
+
pdk (INFO): Using Puppet 6.4.2
|
76
|
+
[✔] Preparing to run the unit tests.
|
77
|
+
[✔] Running unit tests in parallel.
|
78
|
+
Run options: exclude {:bolt=>true}
|
79
|
+
Evaluated 6 tests in 2.405066937 seconds: 0 failures, 0 pending.
|
80
|
+
david@davids:~/tmp/hue$
|
81
|
+
```
|
82
|
+
|
83
|
+
If you're working with a version control system, now would be a good time to make your first commit and store the boilerplate code, and then you can revisit the changes you made later. For example:
|
84
|
+
|
85
|
+
```
|
86
|
+
david@davids:~/tmp/hue$ git init
|
87
|
+
Initialized empty Git repository in ~/tmp/hue/.git/
|
88
|
+
david@davids:~/tmp/hue$ git add -A
|
89
|
+
david@davids:~/tmp/hue$ git commit -m 'initial commit'
|
90
|
+
[master (root-commit) 67951dd] initial commit
|
91
|
+
26 files changed, 887 insertions(+)
|
92
|
+
create mode 100644 .fixtures.yml
|
93
|
+
create mode 100644 .gitattributes
|
94
|
+
create mode 100644 .gitignore
|
95
|
+
create mode 100644 .gitlab-ci.yml
|
96
|
+
create mode 100644 .pdkignore
|
97
|
+
create mode 100644 .puppet-lint.rc
|
98
|
+
create mode 100644 .rspec
|
99
|
+
create mode 100644 .rubocop.yml
|
100
|
+
create mode 100644 .sync.yml
|
101
|
+
create mode 100644 .travis.yml
|
102
|
+
create mode 100644 .yardopts
|
103
|
+
create mode 100644 CHANGELOG.md
|
104
|
+
create mode 100644 Gemfile
|
105
|
+
create mode 100644 README.md
|
106
|
+
create mode 100644 Rakefile
|
107
|
+
create mode 100644 appveyor.yml
|
108
|
+
create mode 100644 data/common.yaml
|
109
|
+
create mode 100644 hiera.yaml
|
110
|
+
create mode 100644 lib/puppet/transport/hue.rb
|
111
|
+
create mode 100644 lib/puppet/transport/schema/hue.rb
|
112
|
+
create mode 100644 lib/puppet/util/network_device/hue/device.rb
|
113
|
+
create mode 100644 metadata.json
|
114
|
+
create mode 100644 spec/default_facts.yml
|
115
|
+
create mode 100644 spec/spec_helper.rb
|
116
|
+
create mode 100644 spec/unit/puppet/transport/hue_spec.rb
|
117
|
+
create mode 100644 spec/unit/puppet/transport/schema/hue_spec.rb
|
118
|
+
david@davids:~/tmp/hue$
|
119
|
+
```
|
120
|
+
|
121
|
+
## Next up
|
122
|
+
|
123
|
+
Now that you have everything ready, you'll [implement the transport](./05-implementing-the-transport.md).
|
@@ -0,0 +1,19 @@
|
|
1
|
+
## Implementing the transport - Exercise
|
2
|
+
|
3
|
+
Implement the `request_debug` option that you can toggle on to create additional debug output on each request. If you get stuck, review the hints below, or [the finished file](TODO).
|
4
|
+
|
5
|
+
## Hints
|
6
|
+
|
7
|
+
* You can create a toggle option with the `Boolean` (`true` or `false`) data type. Add it to the `connection_info` in the transport schema.
|
8
|
+
|
9
|
+
* Make it an `Optional[Boolean]` so that users who do not require request debugging do not have to specify the value.
|
10
|
+
|
11
|
+
* To remember the value you passed, store `connection_info[:request_debug]` in a `@request_debug` variable.
|
12
|
+
|
13
|
+
* In the `hue_get` and `hue_put` methods, add `context.debug(message)` calls showing the method's arguments.
|
14
|
+
|
15
|
+
* Make the debugging optional based on your input by appending `if @request_debug` to each logging statement.
|
16
|
+
|
17
|
+
# Next Up
|
18
|
+
|
19
|
+
Now that the transport can talk to the remote target, it's time to [implement a provider](./06-implementing-the-provider.md).
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# Implementing the transport
|
2
|
+
|
3
|
+
A transport consists of a *schema* describing the required data and credentials to connect to the HUE hub, and the *implementation* containing all the code to facilitate communication with the devices.
|
4
|
+
|
5
|
+
## Schema
|
6
|
+
|
7
|
+
The transport schema defines attributes in a reusable way, allowing you to understand the requirements of the transport. All schemas are located in `lib/puppet/transport/schema` in a Ruby file named after the transport. In this case `hue.rb`.
|
8
|
+
|
9
|
+
To connect to the HUE hub you need an IP address, a port, and an API key.
|
10
|
+
|
11
|
+
Replace the `connection_info` in `lib/puppet/transport/schema/hue.rb` with the following code:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
connection_info: {
|
15
|
+
host: {
|
16
|
+
type: 'String',
|
17
|
+
desc: 'The FQDN or IP address of the hue light system to connect to.',
|
18
|
+
},
|
19
|
+
port: {
|
20
|
+
type: 'Optional[Integer]',
|
21
|
+
desc: 'The port to use when connecting, defaults to 80.',
|
22
|
+
},
|
23
|
+
key: {
|
24
|
+
type: 'String',
|
25
|
+
desc: 'The access key that allows access to the hue light system.',
|
26
|
+
sensitive: true,
|
27
|
+
},
|
28
|
+
},
|
29
|
+
```
|
30
|
+
|
31
|
+
> Note: The Resource API transports use [Puppet Data Types](https://puppet.com/docs/puppet/5.3/lang_data_type.html#core-data-types) to define the allowable values for an attribute. Abstract types like `Optional[]` can be useful to make using your transport easier. Take note of the `sensitive: true` annotation on the `key`; it instructs all services processing this attribute with special care, for example to avoid logging the key.
|
32
|
+
|
33
|
+
|
34
|
+
## Implementation
|
35
|
+
|
36
|
+
The implementation of a transport provides connectivity and utility functions for both Puppet and the providers managing the remote target. The HUE API is a simple REST interface, so you can store the credentials until you need make a connection. The default template at `lib/puppet/transport/hue.rb` already does this. Have a look at the `initialize` function to see how this is done.
|
37
|
+
|
38
|
+
For the HUE's REST API, we want to create a `Faraday` object to capture the target host and key so that the transport can facilitate requests. Replace the `initialize` method in `lib/puppet/transport/hue.rb` with the following code:
|
39
|
+
|
40
|
+
<!-- TODO: do we really need this? -- probably not ```
|
41
|
+
# @summary
|
42
|
+
# Expose the `Faraday` object connected to the hub
|
43
|
+
attr_reader :connection
|
44
|
+
|
45
|
+
```-->
|
46
|
+
```
|
47
|
+
# @summary
|
48
|
+
# Initializes and returns a faraday connection to the given host
|
49
|
+
def initialize(_context, connection_info)
|
50
|
+
# provide a default port
|
51
|
+
port = connection_info[:port].nil? ? 80 : connection_info[:port]
|
52
|
+
Puppet.debug "Connecting to #{connection_info[:host]}:#{port} with dev key"
|
53
|
+
@connection = Faraday.new(url: "http://#{connection_info[:host]}:#{port}/api/#{connection_info[:key].unwrap}", ssl: { verify: false })
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
> Note the `unwrap` call on building the URL, to access the sensitive value.
|
58
|
+
|
59
|
+
### Facts
|
60
|
+
|
61
|
+
The transport is also responsible for collecting any facts from the remote target, similar to how facter works for regular systems. For now we'll only return a hardcoded `operatingsystem` value to mark HUE Hubs:
|
62
|
+
|
63
|
+
Replace the example `facts` method in `lib/puppet/transport/hue.rb` with the following code:
|
64
|
+
|
65
|
+
```
|
66
|
+
# @summary
|
67
|
+
# Returns set facts regarding the HUE Hub
|
68
|
+
def facts(_context)
|
69
|
+
{ 'operatingsystem' => 'philips_hue' }
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
### Connection verification and closing
|
74
|
+
|
75
|
+
To enable better feedback when something goes wrong, a transport can implement a `verify` method to run extra checks on the credentials passed in.
|
76
|
+
|
77
|
+
To save resources both on the target and the node running the transport, the `close` method will be called when the transport is not needed anymore. The transport can close connections and release memory and other resources at this point.
|
78
|
+
|
79
|
+
For this tutorial, replace the example methods with the following code:
|
80
|
+
|
81
|
+
```
|
82
|
+
# @summary
|
83
|
+
# Test that transport can talk to the remote target
|
84
|
+
def verify(_context)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @summary
|
88
|
+
# Close connection, free up resources
|
89
|
+
def close(_context)
|
90
|
+
@connection = nil
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
### Making requests
|
95
|
+
|
96
|
+
Besides exposing some standardises functionality to Puppet, the transport is also a good place to put utility functions that can be reused across your providers. While it may seem overkill for this small example, it is no extra effort, and will establish a healthy pattern.
|
97
|
+
|
98
|
+
Insert the following code after the `close` method:
|
99
|
+
|
100
|
+
```
|
101
|
+
# @summary
|
102
|
+
# Make a get request to the HUE Hub API
|
103
|
+
def hue_get(context, url, args = nil)
|
104
|
+
url = URI.escape(url) if url
|
105
|
+
result = @connection.get(url, args)
|
106
|
+
JSON.parse(result.body)
|
107
|
+
rescue JSON::ParserError => e
|
108
|
+
raise Puppet::ResourceError, "Unable to parse JSON response from HUE API: #{e}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# @summary
|
112
|
+
# Sends an update command to the given url/connection
|
113
|
+
def hue_put(context, url, message)
|
114
|
+
message = message.to_json
|
115
|
+
@connection.put(url, message)
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
## Exercise
|
120
|
+
|
121
|
+
Implement a `request_debug` option that you can toggle to create additional debug output on each request. If you get stuck, have a look at [some hints](./05-implementing-the-transport-hints.md), or [the finished file](TODO).
|
122
|
+
|
123
|
+
|
124
|
+
# Next Up
|
125
|
+
|
126
|
+
Now that the transport can talk to the remote target, it's time to [implement a provider](./06-implementing-the-provider.md).
|
@@ -0,0 +1,227 @@
|
|
1
|
+
# Implementing the provider
|
2
|
+
|
3
|
+
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.
|
4
|
+
|
5
|
+
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.
|
6
|
+
|
7
|
+
## Generating the Boilerplate
|
8
|
+
|
9
|
+
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.
|
10
|
+
|
11
|
+
```
|
12
|
+
david@davids:~/tmp/hue_workshop$ pdk new provider hue_light
|
13
|
+
pdk (INFO): Creating '/home/david/tmp/hue_workshop/lib/puppet/provider/hue_light/hue_light.rb' from template.
|
14
|
+
pdk (INFO): Creating '/home/david/tmp/hue_workshop/lib/puppet/type/hue_light.rb' from template.
|
15
|
+
pdk (INFO): Creating '/home/david/tmp/hue_workshop/spec/unit/puppet/provider/hue_light/hue_light_spec.rb' from template.
|
16
|
+
pdk (INFO): Creating '/home/david/tmp/hue_workshop/spec/unit/puppet/type/hue_light_spec.rb' from template.
|
17
|
+
david@davids:~/tmp/hue_workshop$
|
18
|
+
```
|
19
|
+
|
20
|
+
## Defining the type
|
21
|
+
|
22
|
+
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.
|
23
|
+
|
24
|
+
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:
|
25
|
+
|
26
|
+
```
|
27
|
+
features: ['remote_resource'],
|
28
|
+
attributes: {
|
29
|
+
```
|
30
|
+
|
31
|
+
Browsing through the Hub API (TODO: insert link), we can identify a few basic properties we want to manage, for example:
|
32
|
+
|
33
|
+
* Whether the lamp is on or off
|
34
|
+
* The colour of the light (hue and saturation)
|
35
|
+
* The brightness of the light
|
36
|
+
|
37
|
+
To define the necessary attributes, insert the following snippet into the `attributes` hash, after the `name`:
|
38
|
+
|
39
|
+
```
|
40
|
+
ensure: {
|
41
|
+
type: 'Enum[present, absent]',
|
42
|
+
desc: 'Whether this resource should be present or absent on the target system.',
|
43
|
+
default: 'present',
|
44
|
+
},
|
45
|
+
on: {
|
46
|
+
type: 'Optional[Boolean]',
|
47
|
+
desc: 'Switches the light on or off',
|
48
|
+
},
|
49
|
+
hue: {
|
50
|
+
type: 'Optional[Integer]',
|
51
|
+
desc: 'The hue the light color.',
|
52
|
+
},
|
53
|
+
sat: {
|
54
|
+
type: 'Optional[Integer]',
|
55
|
+
desc: 'The saturation of the light colour',
|
56
|
+
},
|
57
|
+
bri: {
|
58
|
+
type: 'Optional[Integer[1,254]]',
|
59
|
+
desc: <<DESC,
|
60
|
+
This is the brightness of a light from its minimum brightness 1 to its maximum brightness 254
|
61
|
+
DESC
|
62
|
+
},
|
63
|
+
```
|
64
|
+
|
65
|
+
## Implementing the Provider
|
66
|
+
|
67
|
+
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.
|
68
|
+
|
69
|
+
### Reading the state of the lights
|
70
|
+
|
71
|
+
Replace the example `get` function in `lib/puppet/provider/hue_light/hue_light.rb` with the following code:
|
72
|
+
|
73
|
+
```
|
74
|
+
# @summary
|
75
|
+
# Returns a list of lights and their attributes as a list of hashes.
|
76
|
+
def get(context)
|
77
|
+
lights = context.transport.hue_get(context, 'lights')
|
78
|
+
|
79
|
+
return [] if lights.nil?
|
80
|
+
|
81
|
+
lights.collect { |name, content|
|
82
|
+
{
|
83
|
+
name: name,
|
84
|
+
ensure: 'present',
|
85
|
+
on: content['state']['on'],
|
86
|
+
hue: content['state']['hue'],
|
87
|
+
sat: content['state']['sat'],
|
88
|
+
bri: content['state']['bri'],
|
89
|
+
}
|
90
|
+
}
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
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.
|
95
|
+
|
96
|
+
### Obtaining an API key from the Philips Hue Bridge API
|
97
|
+
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:
|
98
|
+
- Press the 'Link' button on the Bridge device (**NOTE:** Linking expires after 10 seconds of inactivity by default)
|
99
|
+
- Perform the following POST request to the API using curl:
|
100
|
+
```
|
101
|
+
curl -X POST -d '{"devicetype":"puppetlabs#hue_light_mgmt"}' 'http://192.168.43.195:8000/api'
|
102
|
+
```
|
103
|
+
If that has been successful, we should get a `200` response with the user's API token:
|
104
|
+
```
|
105
|
+
[
|
106
|
+
{ "success":
|
107
|
+
{
|
108
|
+
"username": "onmdTvd198bMrC6QYyVE9iasfYSeyAbAj3XyQzfL"
|
109
|
+
}
|
110
|
+
}
|
111
|
+
]
|
112
|
+
```
|
113
|
+
|
114
|
+
# hub1.conf
|
115
|
+
```
|
116
|
+
host: 192.168.43.195
|
117
|
+
key: onmdTvd198bMrC6QYyVE9iasfYSeyAbAj3XyQzfL
|
118
|
+
```
|
119
|
+
|
120
|
+
```
|
121
|
+
# device.conf
|
122
|
+
[hub1]
|
123
|
+
type hue
|
124
|
+
url file:///home/david/git/hue_workshop/spec/fixtures/hub1.conf
|
125
|
+
|
126
|
+
[hub2]
|
127
|
+
type hue
|
128
|
+
url file:///home/david/git/hue_workshop/spec/fixtures/hub2.conf
|
129
|
+
```
|
130
|
+
|
131
|
+
```
|
132
|
+
david@davids:~/tmp/hue_workshop$ pdk bundle install
|
133
|
+
pdk (INFO): Using Ruby 2.4.5
|
134
|
+
pdk (INFO): Using Puppet 5.5.12
|
135
|
+
[...]
|
136
|
+
Bundle complete! 10 Gemfile dependencies, 90 gems now installed.
|
137
|
+
Use `bundle info [gemname]` to see where a bundled gem is installed.
|
138
|
+
|
139
|
+
david@davids:~/tmp/hue_workshop$ pdk bundle exec puppet device --libdir lib --deviceconfig device.conf --target hub1 --resource hue_light
|
140
|
+
pdk (INFO): Using Ruby 2.4.5
|
141
|
+
pdk (INFO): Using Puppet 5.5.12
|
142
|
+
hue_light { '1':
|
143
|
+
on => true,
|
144
|
+
bri => 37,
|
145
|
+
hue => 13393,
|
146
|
+
sat => 204,
|
147
|
+
effect => 'none',
|
148
|
+
alert => 'select',
|
149
|
+
}
|
150
|
+
hue_light { '2':
|
151
|
+
on => true,
|
152
|
+
bri => 37,
|
153
|
+
hue => 13401,
|
154
|
+
sat => 204,
|
155
|
+
effect => 'none',
|
156
|
+
alert => 'select',
|
157
|
+
}
|
158
|
+
hue_light { '3':
|
159
|
+
on => true,
|
160
|
+
bri => 254,
|
161
|
+
hue => 65136,
|
162
|
+
sat => 254,
|
163
|
+
effect => 'none',
|
164
|
+
alert => 'none',
|
165
|
+
}
|
166
|
+
|
167
|
+
david@davids:~/tmp/hue_workshop$
|
168
|
+
```
|
169
|
+
|
170
|
+
### Changing the state of the lights
|
171
|
+
|
172
|
+
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.
|
173
|
+
|
174
|
+
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.
|
175
|
+
|
176
|
+
Replace the `create`, `update`, and `delete` methods with the following code:
|
177
|
+
|
178
|
+
```
|
179
|
+
def update(context, name, should)
|
180
|
+
context.device.hue_put("lights/#{name}/state", should)
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
Now you can also change the state of the lights using a manifest:
|
185
|
+
|
186
|
+
```
|
187
|
+
# traffic_lights.pp
|
188
|
+
Hue_light { on => true, bri => 10, sat => 254 }
|
189
|
+
hue_light {
|
190
|
+
'1':
|
191
|
+
hue => 23536;
|
192
|
+
'2':
|
193
|
+
hue => 10000;
|
194
|
+
'3':
|
195
|
+
hue => 65136;
|
196
|
+
}
|
197
|
+
```
|
198
|
+
|
199
|
+
```
|
200
|
+
david@davids:~/git/hue_workshop$ pdk bundle exec puppet device --libdir lib --deviceconfig device.conf --target hub1 --apply examples/traffic_lights.pp
|
201
|
+
pdk (INFO): Using Ruby 2.4.5
|
202
|
+
pdk (INFO): Using Puppet 5.5.12
|
203
|
+
Notice: Compiled catalog for hub1 in environment production in 0.06 seconds
|
204
|
+
Notice: /Stage[main]/Main/Hue_light[1]/hue: hue changed 13393 to 23536 (corrective)
|
205
|
+
Notice: /Stage[main]/Main/Hue_light[1]/bri: bri changed 70 to 10 (corrective)
|
206
|
+
Notice: /Stage[main]/Main/Hue_light[1]/sat: sat changed 204 to 255 (corrective)
|
207
|
+
Notice: /Stage[main]/Main/Hue_light[2]/hue: hue changed 13401 to 10000 (corrective)
|
208
|
+
Notice: /Stage[main]/Main/Hue_light[2]/bri: bri changed 70 to 10 (corrective)
|
209
|
+
Notice: /Stage[main]/Main/Hue_light[2]/sat: sat changed 204 to 255 (corrective)
|
210
|
+
Notice: /Stage[main]/Main/Hue_light[3]/bri: bri changed 254 to 10 (corrective)
|
211
|
+
Notice: /Stage[main]/Main/Hue_light[3]/sat: sat changed 254 to 255 (corrective)
|
212
|
+
Notice: Applied catalog in 0.18 seconds
|
213
|
+
|
214
|
+
david@davids:~/git/hue_workshop$
|
215
|
+
```
|
216
|
+
|
217
|
+
## Exercise
|
218
|
+
|
219
|
+
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`.
|
220
|
+
|
221
|
+
Note that this exercise requires exploring new data types and Resource API options.
|
222
|
+
|
223
|
+
> TODO: add exercise hints
|
224
|
+
|
225
|
+
# Next Up
|
226
|
+
|
227
|
+
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.
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# Implementing a Task
|
2
|
+
|
3
|
+
> TODO: this is NOT fine, yet
|
4
|
+
|
5
|
+
* add bolt gem
|
6
|
+
```
|
7
|
+
Gemfile:
|
8
|
+
optional:
|
9
|
+
':development':
|
10
|
+
- gem: 'puppet-resource_api'
|
11
|
+
- gem: 'faraday'
|
12
|
+
# add this
|
13
|
+
- gem: 'bolt'
|
14
|
+
```
|
15
|
+
|
16
|
+
|
17
|
+
```
|
18
|
+
david@davids:~/tmp/hue_workshop$ pdk update --force
|
19
|
+
pdk (INFO): Updating david-hue_workshop using the default template, from 1.10.0 to 1.10.0
|
20
|
+
|
21
|
+
----------Files to be modified----------
|
22
|
+
Gemfile
|
23
|
+
|
24
|
+
----------------------------------------
|
25
|
+
|
26
|
+
You can find a report of differences in update_report.txt.
|
27
|
+
|
28
|
+
|
29
|
+
------------Update completed------------
|
30
|
+
|
31
|
+
1 files modified.
|
32
|
+
|
33
|
+
david@davids:~/tmp/hue_workshop$ pdk bundle install
|
34
|
+
pdk (INFO): Using Ruby 2.5.3
|
35
|
+
pdk (INFO): Using Puppet 6.4.2
|
36
|
+
[...]
|
37
|
+
Bundle complete! 11 Gemfile dependencies, 122 gems now installed.
|
38
|
+
Use `bundle info [gemname]` to see where a bundled gem is installed.
|
39
|
+
|
40
|
+
david@davids:~/tmp/hue_workshop$
|
41
|
+
```
|
42
|
+
|
43
|
+
* add ruby_task_helper module
|
44
|
+
<!--
|
45
|
+
```# .fixtures.yml
|
46
|
+
---
|
47
|
+
fixtures:
|
48
|
+
forge_modules:
|
49
|
+
ruby_task_helper: "puppetlabs/ruby_task_helper"
|
50
|
+
```
|
51
|
+
|
52
|
+
```
|
53
|
+
david@davids:~/tmp/hue_workshop$ pdk bundle exec rake spec_prep
|
54
|
+
pdk (INFO): Using Ruby 2.5.3
|
55
|
+
pdk (INFO): Using Puppet 6.4.2
|
56
|
+
Notice: Preparing to install into /home/david/tmp/hue_workshop/spec/fixtures/modules ...
|
57
|
+
Notice: Downloading from https://forgeapi.puppet.com ...
|
58
|
+
Notice: Installing -- do not interrupt ...
|
59
|
+
/home/david/tmp/hue_workshop/spec/fixtures/modules
|
60
|
+
└── puppetlabs-ruby_task_helper (v0.3.0)
|
61
|
+
I, [2019-06-04T13:12:04.615368 #32070] INFO -- : Creating symlink from spec/fixtures/modules/hue_workshop to /home/david/tmp/hue_workshop
|
62
|
+
david@davids:~/tmp/hue_workshop$
|
63
|
+
``` -->
|
64
|
+
|
65
|
+
Using the development version:
|
66
|
+
|
67
|
+
```
|
68
|
+
fixtures:
|
69
|
+
# forge_modules:
|
70
|
+
# ruby_task_helper: "puppetlabs/ruby_task_helper"
|
71
|
+
repositories:
|
72
|
+
ruby_task_helper:
|
73
|
+
repo: "git://github.com/da-ar/puppetlabs-ruby_task_helper"
|
74
|
+
ref: "38745f8e7c2521c50bbf1b8e03318006cdac7a02"
|
75
|
+
```
|
76
|
+
|
77
|
+
```
|
78
|
+
david@davids:~/tmp/hue_workshop$ pdk bundle exec rake spec_prep
|
79
|
+
pdk (INFO): Using Ruby 2.5.3
|
80
|
+
pdk (INFO): Using Puppet 6.4.2
|
81
|
+
HEAD is now at 38745f8 (FM-7955) Update to use Transport helper code
|
82
|
+
Cloning into 'spec/fixtures/modules/ruby_task_helper'...
|
83
|
+
I, [2019-06-04T13:43:58.577944 #9390] INFO -- : Creating symlink from spec/fixtures/modules/hue_workshop to /home/david/tmp/hue_workshop
|
84
|
+
david@davids:~/tmp/hue_workshop$
|
85
|
+
```
|
86
|
+
|
87
|
+
* `pdk new task` based on https://github.com/puppetlabs/puppetlabs-panos/blob/master/tasks/apikey.rb
|
88
|
+
|
89
|
+
```
|
90
|
+
david@davids:~/tmp/hue_workshop$ pdk new task alarm
|
91
|
+
pdk (INFO): Creating '/home/david/tmp/hue_workshop/tasks/alarm.sh' from template.
|
92
|
+
pdk (INFO): Creating '/home/david/tmp/hue_workshop/tasks/alarm.json' from template.
|
93
|
+
david@davids:~/tmp/hue_workshop$ mv /home/david/tmp/hue_workshop/tasks/alarm.sh /home/david/tmp/hue_workshop/tasks/alarm.rb
|
94
|
+
david@davids:~/tmp/hue_workshop$
|
95
|
+
```
|
96
|
+
|
97
|
+
* `tasks/alarm.json`
|
98
|
+
```json
|
99
|
+
{
|
100
|
+
"puppet_task_version": 1,
|
101
|
+
"supports_noop": false,
|
102
|
+
"remote": true,
|
103
|
+
"description": "A short description of this task",
|
104
|
+
"parameters": {
|
105
|
+
"name": {
|
106
|
+
"type": "String",
|
107
|
+
"description": "The lamp to alarm"
|
108
|
+
}
|
109
|
+
},
|
110
|
+
"files": [
|
111
|
+
"ruby_task_helper/files/task_helper.rb",
|
112
|
+
"hue_workshop/lib/puppet/transport/hue.rb",
|
113
|
+
"hue_workshop/lib/puppet/transport/schema/hue.rb"
|
114
|
+
]
|
115
|
+
}
|
116
|
+
```
|
117
|
+
|
118
|
+
* `tasks/alarm.rb`
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
#!/opt/puppetlabs/puppet/bin/ruby
|
122
|
+
|
123
|
+
require 'puppet'
|
124
|
+
require_relative "../../ruby_task_helper/files/task_helper.rb"
|
125
|
+
|
126
|
+
class AlarmTask < TaskHelper
|
127
|
+
def task(params = {}, remote = nil)
|
128
|
+
name = params[:name]
|
129
|
+
5.times do |i|
|
130
|
+
remote.transport.hue_put("lights/#{name}/state",
|
131
|
+
name: name,
|
132
|
+
on: false,
|
133
|
+
)
|
134
|
+
sleep 1.0
|
135
|
+
remote.transport.hue_put("lights/#{name}/state",
|
136
|
+
name: name,
|
137
|
+
on: true,
|
138
|
+
hue: 10000*i,
|
139
|
+
sat: 255
|
140
|
+
)
|
141
|
+
sleep 1.0
|
142
|
+
end
|
143
|
+
{}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
if __FILE__ == $0
|
148
|
+
AlarmTask.run
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
* execute `pdk bundle exec bolt ...`
|
153
|
+
|
154
|
+
```yaml
|
155
|
+
# inventory.yaml
|
156
|
+
---
|
157
|
+
nodes:
|
158
|
+
- name: "192.168.43.195"
|
159
|
+
alias: hub1
|
160
|
+
config:
|
161
|
+
transport: remote
|
162
|
+
remote:
|
163
|
+
remote-transport: hue
|
164
|
+
key: "onmdTvd198bMrC6QYyVE9iasfYSeyAbAj3XyQzfL"
|
165
|
+
```
|
166
|
+
|
167
|
+
```
|
168
|
+
david@davids:~/tmp/hue_workshop$ pdk bundle exec bolt task run hue_workshop::alarm --modulepath spec/fixtures/modules/ --target hub1 --inventoryfile inventory.yaml
|
169
|
+
pdk (INFO): Using Ruby 2.5.3
|
170
|
+
pdk (INFO): Using Puppet 6.4.2
|
171
|
+
Started on 192.168.43.195...
|
172
|
+
Finished on 192.168.43.195:
|
173
|
+
{
|
174
|
+
}
|
175
|
+
Successful on 1 node: 192.168.43.195
|
176
|
+
Ran on 1 node in 11.32 seconds
|
177
|
+
|
178
|
+
david@davids:~/tmp/hue_workshop$
|
179
|
+
```
|
180
|
+
|
181
|
+
* profit!
|
data/lib/puppet/resource_api.rb
CHANGED
@@ -331,7 +331,10 @@ module Puppet::ResourceApi
|
|
331
331
|
else
|
332
332
|
my_provider.set(context, rsapi_title => { is: @rsapi_current_state, should: target_state }) unless noop?
|
333
333
|
end
|
334
|
-
|
334
|
+
if context.failed?
|
335
|
+
context.reset_failed
|
336
|
+
raise 'Execution encountered an error'
|
337
|
+
end
|
335
338
|
|
336
339
|
# remember that we have successfully reached our desired state
|
337
340
|
@rsapi_current_state = target_state
|
@@ -33,6 +33,10 @@ class Puppet::ResourceApi::BaseContext
|
|
33
33
|
@failed
|
34
34
|
end
|
35
35
|
|
36
|
+
def reset_failed
|
37
|
+
@failed = false
|
38
|
+
end
|
39
|
+
|
36
40
|
def feature_support?(feature)
|
37
41
|
Puppet.deprecation_warning('context.feature_support? is deprecated. Please use context.type.feature? instead.')
|
38
42
|
type.feature?(feature)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puppet-resource_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.8.
|
4
|
+
version: 1.8.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Schmitt
|
@@ -52,6 +52,17 @@ files:
|
|
52
52
|
- codecov.yml
|
53
53
|
- contrib/README.md
|
54
54
|
- contrib/pre-commit
|
55
|
+
- docs/README.md
|
56
|
+
- docs/hands-on-lab/01-installing-prereqs.md
|
57
|
+
- docs/hands-on-lab/02-connecting-to-the-lightbulbs-emulator.png
|
58
|
+
- docs/hands-on-lab/02-connecting-to-the-lightbulbs.md
|
59
|
+
- docs/hands-on-lab/03-creating-a-new-module.md
|
60
|
+
- docs/hands-on-lab/03-creating-a-new-module_vscode.png
|
61
|
+
- docs/hands-on-lab/04-adding-a-new-transport.md
|
62
|
+
- docs/hands-on-lab/05-implementing-the-transport-hints.md
|
63
|
+
- docs/hands-on-lab/05-implementing-the-transport.md
|
64
|
+
- docs/hands-on-lab/06-implementing-the-provider.md
|
65
|
+
- docs/hands-on-lab/07-implementing-a-task.md
|
55
66
|
- lib/puppet/resource_api.rb
|
56
67
|
- lib/puppet/resource_api/base_context.rb
|
57
68
|
- lib/puppet/resource_api/data_type_handling.rb
|