puppet-resource_api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.dependency_decisions.yml +91 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +137 -0
- data/.rubocop_todo.yml +17 -0
- data/.travis.yml +28 -0
- data/CONTRIBUTING.md +11 -0
- data/Gemfile +44 -0
- data/LICENSE +201 -0
- data/NOTICE +17 -0
- data/README.md +50 -0
- data/Rakefile +24 -0
- data/appveyor.yml +39 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/mkrf_conf.rb +20 -0
- data/lib/puppet/resource_api.rb +236 -0
- data/lib/puppet/resource_api/base_context.rb +138 -0
- data/lib/puppet/resource_api/command.rb +122 -0
- data/lib/puppet/resource_api/errors.rb +7 -0
- data/lib/puppet/resource_api/glue.rb +46 -0
- data/lib/puppet/resource_api/io_context.rb +14 -0
- data/lib/puppet/resource_api/puppet_context.rb +15 -0
- data/lib/puppet/resource_api/version.rb +5 -0
- data/lib/puppet/util/network_device/simple/device.rb +23 -0
- data/puppet-resource_api.gemspec +25 -0
- metadata +84 -0
data/NOTICE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Puppet Resource API
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2017 Puppet, Inc.
|
|
4
|
+
|
|
5
|
+
Puppet Labs can be contacted at: info@puppetlabs.com
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
data/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Puppet::ResourceApi [](https://travis-ci.org/puppetlabs/puppet-resource_api) [](https://ci.appveyor.com/project/puppetlabs/puppet-resource-api) [](https://coveralls.io/github/puppetlabs/puppet-resource_api?branch=master) [](https://codecov.io/gh/puppetlabs/puppet-resource_api)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This is an implementation of the [Resource API](https://github.com/DavidS/puppet-specifications/blob/resourceapi/language/resource-api/README.md) proposal. Find a working example of a new-style provider in the [experimental puppetlabs-apt branch](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/lib/puppet/provider/apt_key2/apt_key2.rb). There is also the corresponding [type](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/lib/puppet/type/apt_key2.rb), and [new unit tests](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/spec/unit/puppet/provider/apt_key2/apt_key2_spec.rb) for 100% coverage.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Add this line to your application's Gemfile:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
gem 'puppet-resource_api'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
And then execute:
|
|
15
|
+
|
|
16
|
+
$ bundle
|
|
17
|
+
|
|
18
|
+
Or install it yourself as:
|
|
19
|
+
|
|
20
|
+
$ gem install puppet-resource_api
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
The [Resource API](https://github.com/DavidS/puppet-specifications/blob/resourceapi/language/resource-api/README.md) explains the usage and capabilities of this gem.
|
|
25
|
+
|
|
26
|
+
Already working:
|
|
27
|
+
* basic type and provider definition, using `name`, `desc`, and `attributes`
|
|
28
|
+
* the `canonicalize` and `remote_resource` features
|
|
29
|
+
* all the logging facilities
|
|
30
|
+
* executing the new provider under any of the following commands:
|
|
31
|
+
* `puppet apply`
|
|
32
|
+
* `puppet resource`
|
|
33
|
+
* `puppet agent`
|
|
34
|
+
* `puppet device` (if applicable)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
There are still a few notable gaps between the implementation, and the specification:
|
|
38
|
+
* Only a single runtime environment (the puppet commands) is currently implemented.
|
|
39
|
+
* `auto*` definitions
|
|
40
|
+
* the Commands API is mostly implemented, but deployment is blocked on upstream work (PDK-580). You can use regular Ruby `system()` calls as a workaround, with all their attendant encoding, and safety issues.
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
45
|
+
|
|
46
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
47
|
+
|
|
48
|
+
## Contributing
|
|
49
|
+
|
|
50
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/puppet-resource_api.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
require "rspec/core/rake_task"
|
|
3
|
+
require 'rubocop/rake_task'
|
|
4
|
+
|
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
6
|
+
|
|
7
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
|
8
|
+
t.options = ['--display-cop-names']
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
namespace :spec do
|
|
12
|
+
desc 'Run RSpec code examples with coverage collection'
|
|
13
|
+
task :coverage do
|
|
14
|
+
ENV['COVERAGE'] = 'yes'
|
|
15
|
+
Rake::Task['spec'].execute
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc 'Check for unapproved licenses in dependencies'
|
|
20
|
+
task(:license_finder) do
|
|
21
|
+
system('license_finder --decisions-file=.dependency_decisions.yml') || raise(StandardError, 'Unapproved license(s) found on dependencies')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
task :default => :spec
|
data/appveyor.yml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# version: 1.0.{build}-{branch}
|
|
2
|
+
|
|
3
|
+
environment:
|
|
4
|
+
matrix:
|
|
5
|
+
- RUBY_VERSION: 24-x64
|
|
6
|
+
- RUBY_VERSION: 21-x64
|
|
7
|
+
|
|
8
|
+
install:
|
|
9
|
+
- set PATH=C:\Ruby%RUBY_VERSION%\bin;C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev1\mingw64\bin;%PATH%
|
|
10
|
+
- SET LOG_SPEC_ORDER=true
|
|
11
|
+
- SET COVERAGE=yes
|
|
12
|
+
# Due to a bug in the version of OpenSSL shipped with Ruby 2.4.1 on Windows
|
|
13
|
+
# (https://bugs.ruby-lang.org/issues/11033). Errors are ignored because the
|
|
14
|
+
# mingw gem calls out to pacman to install OpenSSL which is already
|
|
15
|
+
# installed, causing gem to raise a warning that powershell determines to be
|
|
16
|
+
# a fatal error.
|
|
17
|
+
- ps: |
|
|
18
|
+
$ErrorActionPreference = "SilentlyContinue"
|
|
19
|
+
if($env:RUBY_VERSION -eq "24-x64") {
|
|
20
|
+
gem install openssl "~> 2.0.4" --no-rdoc --no-ri -- --with-openssl-dir=C:\msys64\mingw64
|
|
21
|
+
}
|
|
22
|
+
$host.SetShouldExit(0)
|
|
23
|
+
- bundle install --jobs 4 --retry 2 --without development
|
|
24
|
+
|
|
25
|
+
build: off
|
|
26
|
+
|
|
27
|
+
before_test:
|
|
28
|
+
- ruby -v
|
|
29
|
+
- gem -v
|
|
30
|
+
- bundle -v
|
|
31
|
+
- type Gemfile.lock
|
|
32
|
+
|
|
33
|
+
test_script:
|
|
34
|
+
- bundle exec rake
|
|
35
|
+
|
|
36
|
+
# Uncomment this block to enable RDP access to the AppVeyor test instance for
|
|
37
|
+
# debugging purposes.
|
|
38
|
+
#on_finish:
|
|
39
|
+
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "puppet/resource_api"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/ext/mkrf_conf.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Based on the example from https://en.wikibooks.org/wiki/Ruby_Programming/RubyGems#How_to_install_different_versions_of_gems_depending_on_which_version_of_ruby_the_installee_is_using
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'rubygems/command.rb'
|
|
4
|
+
require 'rubygems/dependency_installer.rb'
|
|
5
|
+
begin
|
|
6
|
+
Gem::Command.build_args = ARGV
|
|
7
|
+
rescue NoMethodError # rubocop:disable Lint/HandleExceptions
|
|
8
|
+
end
|
|
9
|
+
inst = Gem::DependencyInstaller.new
|
|
10
|
+
begin
|
|
11
|
+
if RbConfig::CONFIG['host_os'] =~ %r{mswin|msys|mingw32}i
|
|
12
|
+
inst.install 'childprocess', '~> 0.7'
|
|
13
|
+
end
|
|
14
|
+
rescue # rubocop:disable Lint/RescueWithoutErrorClass
|
|
15
|
+
exit(1)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
f = File.open(File.join(File.dirname(__FILE__), 'Rakefile'), 'w') # create dummy rakefile to indicate success
|
|
19
|
+
f.write("task :default\n")
|
|
20
|
+
f.close
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
# require 'puppet/resource_api/command'
|
|
3
|
+
require 'puppet/resource_api/errors'
|
|
4
|
+
require 'puppet/resource_api/glue'
|
|
5
|
+
require 'puppet/resource_api/puppet_context'
|
|
6
|
+
require 'puppet/resource_api/version'
|
|
7
|
+
require 'puppet/type'
|
|
8
|
+
|
|
9
|
+
module Puppet::ResourceApi
|
|
10
|
+
def register_type(definition)
|
|
11
|
+
raise Puppet::DevError, 'requires a Hash as definition, not %{other_type}' % { other_type: definition.class } unless definition.is_a? Hash
|
|
12
|
+
raise Puppet::DevError, 'requires a name' unless definition.key? :name
|
|
13
|
+
raise Puppet::DevError, 'requires attributes' unless definition.key? :attributes
|
|
14
|
+
|
|
15
|
+
# prepare the ruby module for the provider
|
|
16
|
+
# this has to happen before Puppet::Type.newtype starts autoloading providers
|
|
17
|
+
# it also needs to be guarded against the namespace already being defined by something
|
|
18
|
+
# else to avoid ruby warnings
|
|
19
|
+
unless Puppet::Provider.const_defined?(class_name_from_type_name(definition[:name]))
|
|
20
|
+
Puppet::Provider.const_set(class_name_from_type_name(definition[:name]), Module.new)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Puppet::Type.newtype(definition[:name].to_sym) do
|
|
24
|
+
@docs = definition[:docs]
|
|
25
|
+
has_namevar = false
|
|
26
|
+
namevar_name = nil
|
|
27
|
+
|
|
28
|
+
# Keeps a copy of the provider around. Weird naming to avoid clashes with puppet's own `provider` member
|
|
29
|
+
define_singleton_method(:my_provider) do
|
|
30
|
+
@my_provider ||= Puppet::ResourceApi.load_provider(definition[:name]).new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# make the provider available in the instance's namespace
|
|
34
|
+
def my_provider
|
|
35
|
+
self.class.my_provider
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if definition[:features] && definition[:features].include?('remote_resource')
|
|
39
|
+
apply_to_device
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
define_method(:initialize) do |attributes|
|
|
43
|
+
# $stderr.puts "A: #{attributes.inspect}"
|
|
44
|
+
attributes = attributes.to_hash if attributes.is_a? Puppet::Resource
|
|
45
|
+
# $stderr.puts "B: #{attributes.inspect}"
|
|
46
|
+
if definition.key?(:features) && definition[:features].include?('canonicalize')
|
|
47
|
+
attributes = my_provider.canonicalize(context, [attributes])[0]
|
|
48
|
+
end
|
|
49
|
+
# $stderr.puts "C: #{attributes.inspect}"
|
|
50
|
+
super(attributes)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
definition[:attributes].each do |name, options|
|
|
54
|
+
# puts "#{name}: #{options.inspect}"
|
|
55
|
+
|
|
56
|
+
# TODO: using newparam everywhere would suppress change reporting
|
|
57
|
+
# that would allow more fine-grained reporting through context,
|
|
58
|
+
# but require more invest in hooking up the infrastructure to emulate existing data
|
|
59
|
+
param_or_property = if [:read_only, :parameter, :namevar].include? options[:behaviour]
|
|
60
|
+
:newparam
|
|
61
|
+
else
|
|
62
|
+
:newproperty
|
|
63
|
+
end
|
|
64
|
+
send(param_or_property, name.to_sym) do
|
|
65
|
+
unless options[:type]
|
|
66
|
+
raise Puppet::DevError, "#{definition[:name]}.#{name} has no type"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if options[:desc]
|
|
70
|
+
desc "#{options[:desc]} (a #{options[:type]})"
|
|
71
|
+
else
|
|
72
|
+
warn("#{definition[:name]}.#{name} has no docs")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if options[:behaviour] == :namevar
|
|
76
|
+
# puts 'setting namevar'
|
|
77
|
+
# raise Puppet::DevError, "namevar must be called 'name', not '#{name}'" if name.to_s != 'name'
|
|
78
|
+
isnamevar
|
|
79
|
+
has_namevar = true
|
|
80
|
+
namevar_name = name
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# read-only values do not need type checking, but can have default values
|
|
84
|
+
if options[:behaviour] != :read_only
|
|
85
|
+
# TODO: this should use Pops infrastructure to avoid hardcoding stuff, and enhance type fidelity
|
|
86
|
+
# validate do |v|
|
|
87
|
+
# type = Puppet::Pops::Types::TypeParser.singleton.parse(options[:type]).normalize
|
|
88
|
+
# if type.instance?(v)
|
|
89
|
+
# return true
|
|
90
|
+
# else
|
|
91
|
+
# inferred_type = Puppet::Pops::Types::TypeCalculator.infer_set(value)
|
|
92
|
+
# error_msg = Puppet::Pops::Types::TypeMismatchDescriber.new.describe_mismatch("#{DEFINITION[:name]}.#{name}", type, inferred_type)
|
|
93
|
+
# raise Puppet::ResourceError, error_msg
|
|
94
|
+
# end
|
|
95
|
+
# end
|
|
96
|
+
|
|
97
|
+
if options.key? :default
|
|
98
|
+
defaultto options[:default]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
case options[:type]
|
|
102
|
+
when 'String'
|
|
103
|
+
# require any string value
|
|
104
|
+
newvalues %r{} do
|
|
105
|
+
end
|
|
106
|
+
# rubocop:disable Lint/BooleanSymbol
|
|
107
|
+
when 'Boolean'
|
|
108
|
+
newvalues 'true', 'false', :true, :false, true, false
|
|
109
|
+
|
|
110
|
+
munge do |v|
|
|
111
|
+
case v
|
|
112
|
+
when 'true', :true
|
|
113
|
+
true
|
|
114
|
+
when 'false', :false
|
|
115
|
+
false
|
|
116
|
+
else
|
|
117
|
+
v
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
# rubocop:enable Lint/BooleanSymbol
|
|
121
|
+
when 'Integer'
|
|
122
|
+
newvalue %r{^-?\d+$} do
|
|
123
|
+
end
|
|
124
|
+
munge do |v|
|
|
125
|
+
Puppet::Pops::Utils.to_n(v)
|
|
126
|
+
end
|
|
127
|
+
when 'Float', 'Numeric'
|
|
128
|
+
newvalue Puppet::Pops::Patterns::NUMERIC do
|
|
129
|
+
end
|
|
130
|
+
munge do |v|
|
|
131
|
+
Puppet::Pops::Utils.to_n(v)
|
|
132
|
+
end
|
|
133
|
+
when 'Enum[present, absent]'
|
|
134
|
+
newvalue :absent do
|
|
135
|
+
end
|
|
136
|
+
newvalue :present do
|
|
137
|
+
end
|
|
138
|
+
when 'Variant[Pattern[/\A(0x)?[0-9a-fA-F]{8}\Z/], Pattern[/\A(0x)?[0-9a-fA-F]{16}\Z/], Pattern[/\A(0x)?[0-9a-fA-F]{40}\Z/]]'
|
|
139
|
+
# the namevar needs to be a Parameter, which only has newvalue*s*
|
|
140
|
+
newvalues(%r{\A(0x)?[0-9a-fA-F]{8}\Z}, %r{\A(0x)?[0-9a-fA-F]{16}\Z}, %r{\A(0x)?[0-9a-fA-F]{40}\Z})
|
|
141
|
+
when 'Optional[String]'
|
|
142
|
+
newvalues(%r{}, :undef) do
|
|
143
|
+
end
|
|
144
|
+
when 'Variant[Stdlib::Absolutepath, Pattern[/\A(https?|ftp):\/\//]]'
|
|
145
|
+
# TODO: this is wrong, but matches original implementation
|
|
146
|
+
[/^\//, /\A(https?|ftp):\/\//].each do |v| # rubocop:disable Style/RegexpLiteral
|
|
147
|
+
newvalues v do
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
when 'Pattern[/\A((hkp|http|https):\/\/)?([a-z\d])([a-z\d-]{0,61}\.)+[a-z\d]+(:\d{2,5})?$/]'
|
|
151
|
+
newvalues(/\A((hkp|http|https):\/\/)?([a-z\d])([a-z\d-]{0,61}\.)+[a-z\d]+(:\d{2,5})?$/) # rubocop:disable Style/RegexpLiteral
|
|
152
|
+
else
|
|
153
|
+
raise Puppet::DevError, "Datatype #{options[:type]} is not yet supported in this prototype"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
define_singleton_method(:instances) do
|
|
160
|
+
# puts 'instances'
|
|
161
|
+
# force autoloading of the provider
|
|
162
|
+
provider(name)
|
|
163
|
+
my_provider.get(context).map do |resource_hash|
|
|
164
|
+
Puppet::ResourceApi::TypeShim.new(resource_hash[namevar_name], resource_hash)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
define_method(:retrieve) do
|
|
169
|
+
# puts "retrieve(#{title.inspect})"
|
|
170
|
+
result = Puppet::Resource.new(self.class, title)
|
|
171
|
+
current_state = my_provider.get(context).find { |h| h[namevar_name] == title }
|
|
172
|
+
|
|
173
|
+
# require 'pry'; binding.pry
|
|
174
|
+
|
|
175
|
+
if current_state
|
|
176
|
+
current_state.each do |k, v|
|
|
177
|
+
result[k] = v
|
|
178
|
+
end
|
|
179
|
+
else
|
|
180
|
+
result[:name] = title
|
|
181
|
+
result[:ensure] = :absent
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# puts "retrieved #{current_state.inspect}"
|
|
185
|
+
|
|
186
|
+
@rapi_current_state = current_state
|
|
187
|
+
result
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
define_method(:flush) do
|
|
191
|
+
# puts 'flush'
|
|
192
|
+
# require'pry';binding.pry
|
|
193
|
+
target_state = Hash[@parameters.map { |k, v| [k, v.value] }]
|
|
194
|
+
# remove puppet's injected metaparams
|
|
195
|
+
target_state.delete(:loglevel)
|
|
196
|
+
target_state = my_provider.canonicalize(context, [target_state]).first if definition.key?(:features) && definition[:features].include?('canonicalize')
|
|
197
|
+
|
|
198
|
+
retrieve unless @rapi_current_state
|
|
199
|
+
|
|
200
|
+
# require 'pry'; binding.pry
|
|
201
|
+
return if @rapi_current_state == target_state
|
|
202
|
+
|
|
203
|
+
# puts "@rapi_current_state: #{@rapi_current_state.inspect}"
|
|
204
|
+
# puts "target_state: #{target_state.inspect}"
|
|
205
|
+
|
|
206
|
+
my_provider.set(context, title => { is: @rapi_current_state, should: target_state })
|
|
207
|
+
raise 'Execution encountered an error' if context.failed?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
define_singleton_method(:context) do
|
|
211
|
+
@context ||= PuppetContext.new(definition[:name])
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def context
|
|
215
|
+
self.class.context
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
module_function :register_type
|
|
220
|
+
|
|
221
|
+
def load_provider(type_name)
|
|
222
|
+
class_name = class_name_from_type_name(type_name)
|
|
223
|
+
type_name_sym = type_name.to_sym
|
|
224
|
+
|
|
225
|
+
# loads the "puppet/provider/#{type_name}/#{type_name}" file through puppet
|
|
226
|
+
Puppet::Type.type(type_name_sym).provider(type_name_sym)
|
|
227
|
+
Puppet::Provider.const_get(class_name).const_get(class_name)
|
|
228
|
+
rescue NameError
|
|
229
|
+
raise Puppet::DevError, "class #{class_name} not found in puppet/provider/#{type_name}/#{type_name}"
|
|
230
|
+
end
|
|
231
|
+
module_function :load_provider
|
|
232
|
+
|
|
233
|
+
def self.class_name_from_type_name(type_name)
|
|
234
|
+
type_name.to_s.split('_').map(&:capitalize).join
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require 'puppet/util'
|
|
2
|
+
require 'puppet/util/network_device'
|
|
3
|
+
|
|
4
|
+
class Puppet::ResourceApi::BaseContext
|
|
5
|
+
def initialize(typename)
|
|
6
|
+
@typename = typename
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def device
|
|
10
|
+
# TODO: evaluate facter_url setting for loading config if there is no `current` NetworkDevice
|
|
11
|
+
raise 'no device configured' unless Puppet::Util::NetworkDevice.current
|
|
12
|
+
Puppet::Util::NetworkDevice.current
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def failed?
|
|
16
|
+
@failed
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
[:debug, :info, :notice, :warning, :err].each do |level|
|
|
20
|
+
define_method(level) do |*args|
|
|
21
|
+
if args.length == 1
|
|
22
|
+
message = "#{@context || @typename}: #{args.last}"
|
|
23
|
+
elsif args.length == 2
|
|
24
|
+
resources = format_titles(args.first)
|
|
25
|
+
message = "#{resources}: #{args.last}"
|
|
26
|
+
else
|
|
27
|
+
message = args.map(&:to_s).join(', ')
|
|
28
|
+
end
|
|
29
|
+
send_log(level, message)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
[:creating, :updating, :deleting].each do |method|
|
|
34
|
+
define_method(method) do |titles, message: method.to_s.capitalize, &block|
|
|
35
|
+
start_time = Time.now
|
|
36
|
+
setup_context(titles, message)
|
|
37
|
+
begin
|
|
38
|
+
debug('Start')
|
|
39
|
+
block.call
|
|
40
|
+
notice("Finished in #{format_seconds(Time.now - start_time)} seconds")
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
err("Failed after #{format_seconds(Time.now - start_time)} seconds: #{e}")
|
|
43
|
+
@failed = true
|
|
44
|
+
ensure
|
|
45
|
+
@context = nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def failing(titles, message: 'Failing')
|
|
51
|
+
start_time = Time.now
|
|
52
|
+
setup_context(titles, message)
|
|
53
|
+
begin
|
|
54
|
+
debug('Start')
|
|
55
|
+
yield
|
|
56
|
+
warning("Finished failing in #{format_seconds(Time.now - start_time)} seconds")
|
|
57
|
+
rescue StandardError => e
|
|
58
|
+
err("Failed after #{format_seconds(Time.now - start_time)} seconds: #{e}")
|
|
59
|
+
@failed = true
|
|
60
|
+
ensure
|
|
61
|
+
@context = nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def processing(title, is, should, message: 'Processing')
|
|
66
|
+
raise "#{__method__} only accepts a single resource title" if title.respond_to?(:each)
|
|
67
|
+
start_time = Time.now
|
|
68
|
+
setup_context(title, message)
|
|
69
|
+
begin
|
|
70
|
+
debug("Starting processing of #{title} from #{is} to #{should}")
|
|
71
|
+
yield
|
|
72
|
+
notice("Finished processing #{title} in #{format_seconds(Time.now - start_time)} seconds: #{should}")
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
err("Failed processing #{title} after #{format_seconds(Time.now - start_time)} seconds: #{e}")
|
|
75
|
+
@failed = true
|
|
76
|
+
ensure
|
|
77
|
+
@context = nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
[:created, :updated, :deleted].each do |method|
|
|
82
|
+
define_method(method) do |titles, message: method.to_s.capitalize|
|
|
83
|
+
notice("#{message}: #{titles}")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def processed(title, is, should)
|
|
88
|
+
raise "#{__method__} only accepts a single resource title" if title.respond_to?(:each)
|
|
89
|
+
notice("Processed #{title} from #{is} to #{should}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def attribute_changed(title, attribute, is, should, message: nil)
|
|
93
|
+
raise "#{__method__} only accepts a single resource title" if title.respond_to?(:each)
|
|
94
|
+
printable_is = 'nil'
|
|
95
|
+
printable_should = 'nil'
|
|
96
|
+
if is
|
|
97
|
+
printable_is = is.is_a?(Numeric) ? is : "'#{is}'"
|
|
98
|
+
end
|
|
99
|
+
if should
|
|
100
|
+
printable_should = should.is_a?(Numeric) ? should : "'#{should}'"
|
|
101
|
+
end
|
|
102
|
+
notice("#{title}: attribute '#{attribute}' changed from #{printable_is} to #{printable_should}#{message ? ": #{message}" : ''}")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def failed(titles, message: 'Updating has failed')
|
|
106
|
+
setup_context(titles)
|
|
107
|
+
begin
|
|
108
|
+
err(message)
|
|
109
|
+
# raise message
|
|
110
|
+
ensure
|
|
111
|
+
@context = nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def send_log(_level, _message)
|
|
116
|
+
raise 'Received send_log() on an unprepared BaseContext. Use IOContext, or PuppetContext instead.'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def format_titles(titles)
|
|
122
|
+
if titles.length.zero? && !titles.is_a?(String)
|
|
123
|
+
@typename
|
|
124
|
+
else
|
|
125
|
+
"#{@typename}[#{[titles].flatten.compact.join(', ')}]"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def setup_context(titles, message = nil)
|
|
130
|
+
@context = format_titles(titles)
|
|
131
|
+
@context += ": #{message}" if message
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def format_seconds(seconds)
|
|
135
|
+
return '%.6f' % seconds if seconds < 1
|
|
136
|
+
'%.2f' % seconds
|
|
137
|
+
end
|
|
138
|
+
end
|