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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require 'childprocesscore'
|
|
2
|
+
|
|
3
|
+
module Puppet::ResourceApi
|
|
4
|
+
# A useful interface to safely run system commands
|
|
5
|
+
#
|
|
6
|
+
# See https://github.com/DavidS/puppet-specifications/blob/reasourceapi/language/resource-api/README.md#commands for a complete specification
|
|
7
|
+
class Command
|
|
8
|
+
# Small utility class to hold the `run()` results together
|
|
9
|
+
class Result
|
|
10
|
+
attr_accessor :stdout, :stderr, :exit_code
|
|
11
|
+
def initialize
|
|
12
|
+
@stdout = ''
|
|
13
|
+
@stderr = ''
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_accessor :cwd, :environment
|
|
18
|
+
|
|
19
|
+
attr_reader :command
|
|
20
|
+
|
|
21
|
+
def initialize(command)
|
|
22
|
+
@command = command
|
|
23
|
+
@cwd = '/'
|
|
24
|
+
@environment = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run(context, *args,
|
|
28
|
+
stdin_source: :none, stdin_value: nil, stdin_io: nil, stdin_encoding: nil,
|
|
29
|
+
stdout_destination: :log, stdout_loglevel: :debug, stdout_encoding: nil,
|
|
30
|
+
stderr_destination: :log, stderr_loglevel: :warning, stderr_encoding: nil,
|
|
31
|
+
noop: false)
|
|
32
|
+
raise ArgumentError, "context is a '#{context.class}', expected a 'Puppet::ResourceApi::BaseContext'" unless context.is_a? Puppet::ResourceApi::BaseContext
|
|
33
|
+
return if noop
|
|
34
|
+
process = self.class.prepare_process(context, command, *args, environment: environment, cwd: cwd)
|
|
35
|
+
|
|
36
|
+
process.duplex = true
|
|
37
|
+
|
|
38
|
+
stdout_r, stdout_w = IO.pipe
|
|
39
|
+
process.io.stdout = stdout_w
|
|
40
|
+
process.io.stdout.set_encoding(process.io.stdout.external_encoding, stdout_encoding) if stdout_encoding
|
|
41
|
+
|
|
42
|
+
stderr_r, stderr_w = IO.pipe
|
|
43
|
+
process.io.stderr = stderr_w
|
|
44
|
+
process.io.stderr.set_encoding(process.io.stderr.external_encoding, stderr_encoding) if stderr_encoding
|
|
45
|
+
|
|
46
|
+
process.io.stdin.set_encoding(process.io.stdin.external_encoding, stdin_encoding) if stdin_encoding
|
|
47
|
+
|
|
48
|
+
process.start
|
|
49
|
+
stdout_w.close
|
|
50
|
+
stderr_w.close
|
|
51
|
+
|
|
52
|
+
case stdin_source
|
|
53
|
+
when :none # rubocop:disable Lint/EmptyWhen
|
|
54
|
+
# nothing to do here
|
|
55
|
+
when :io
|
|
56
|
+
while (v = stdin_io.read) && !v.empty? # `empty?` signals EOF, can't use the `length` variant due to encoding issues
|
|
57
|
+
process.io.stdin.write v
|
|
58
|
+
end
|
|
59
|
+
when :value
|
|
60
|
+
process.io.stdin.write stdin_value
|
|
61
|
+
end
|
|
62
|
+
process.io.stdin.close
|
|
63
|
+
|
|
64
|
+
result = Result.new
|
|
65
|
+
|
|
66
|
+
# TODO: https://tickets.puppetlabs.com/browse/PDK-542 - capture/buffer full lines
|
|
67
|
+
while process.alive? || !stdout_r.eof? || !stderr_r.eof?
|
|
68
|
+
rs, _ws, _errs = IO.select([stdout_r, stderr_r])
|
|
69
|
+
rs.each do |pipe|
|
|
70
|
+
loglevel = if pipe == stdout_r
|
|
71
|
+
stdout_loglevel
|
|
72
|
+
else
|
|
73
|
+
stderr_loglevel
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
destination = if pipe == stdout_r
|
|
77
|
+
stdout_destination
|
|
78
|
+
else
|
|
79
|
+
stderr_destination
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
begin
|
|
83
|
+
chunk = pipe.read_nonblock(1024)
|
|
84
|
+
case destination
|
|
85
|
+
when :log
|
|
86
|
+
chunk.split("\n").each { |l| context.send(loglevel, l.strip) }
|
|
87
|
+
when :store
|
|
88
|
+
if pipe == stdout_r
|
|
89
|
+
result.stdout += chunk
|
|
90
|
+
else
|
|
91
|
+
result.stderr += chunk
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
rescue Errno::EBADF # rubocop:disable Lint/HandleExceptions
|
|
95
|
+
# This can be thrown on Windows after the process has gone away
|
|
96
|
+
# ignore, retry WaitReadable through outer loop
|
|
97
|
+
rescue IO::WaitReadable, EOFError # rubocop:disable Lint/HandleExceptions
|
|
98
|
+
# ignore, retry WaitReadable through outer loop
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
result.exit_code = process.wait
|
|
104
|
+
|
|
105
|
+
raise Puppet::ResourceApi::CommandExecutionError, 'Command %{command} failed with exit code %{exit_code}' % { command: command, exit_code: result.exit_code } unless result.exit_code.zero?
|
|
106
|
+
|
|
107
|
+
result
|
|
108
|
+
rescue ChildProcess::LaunchError => e
|
|
109
|
+
raise Puppet::ResourceApi::CommandNotFoundError, 'Error when executing %{command}: %{error}' % { command: command, error: e.to_s }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def self.prepare_process(_context, command, *args, environment:, cwd:)
|
|
113
|
+
process = ChildProcess.build(command, *args)
|
|
114
|
+
environment.each do |k, v|
|
|
115
|
+
process.environment[k] = v
|
|
116
|
+
end
|
|
117
|
+
process.cwd = cwd
|
|
118
|
+
|
|
119
|
+
process
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# This error is thrown when a Command cannot find the specified command
|
|
2
|
+
class Puppet::ResourceApi::CommandNotFoundError < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
# This error is thrown when a Command returned a non-zero exit code
|
|
6
|
+
class Puppet::ResourceApi::CommandExecutionError < StandardError
|
|
7
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
module Puppet::ResourceApi
|
|
3
|
+
# A trivial class to provide the functionality required to push data through the existing type/provider parts of puppet
|
|
4
|
+
class TypeShim
|
|
5
|
+
attr_reader :values
|
|
6
|
+
|
|
7
|
+
def initialize(title, resource_hash)
|
|
8
|
+
# internalize and protect - needs to go deeper
|
|
9
|
+
@values = resource_hash.dup
|
|
10
|
+
# "name" is a privileged key
|
|
11
|
+
@values[:name] = title
|
|
12
|
+
@values.freeze
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_resource
|
|
16
|
+
ResourceShim.new(@values)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def name
|
|
20
|
+
values[:name]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# A trivial class to provide the functionality required to push data through the existing type/provider parts of puppet
|
|
25
|
+
class ResourceShim
|
|
26
|
+
attr_reader :values
|
|
27
|
+
|
|
28
|
+
def initialize(resource_hash)
|
|
29
|
+
@values = resource_hash.dup.freeze # whatevs
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def title
|
|
33
|
+
values[:name]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def prune_parameters(*_args)
|
|
37
|
+
# puts "not pruning #{args.inspect}" if args.length > 0
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_manifest
|
|
42
|
+
# TODO: get the correct typename here
|
|
43
|
+
(["SOMETYPE { #{values[:name].inspect}: "] + values.keys.reject { |k| k == :name }.map { |k| " #{k} => #{values[k].inspect}," } + ['}']).join("\n")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'puppet/resource_api/base_context'
|
|
2
|
+
|
|
3
|
+
class Puppet::ResourceApi::IOContext < Puppet::ResourceApi::BaseContext
|
|
4
|
+
def initialize(typename, target = $stderr)
|
|
5
|
+
super(typename)
|
|
6
|
+
@target = target
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def send_log(level, message)
|
|
12
|
+
@target.puts "#{level}: #{message}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'puppet/resource_api/base_context'
|
|
2
|
+
require 'puppet/util/logging'
|
|
3
|
+
|
|
4
|
+
class Puppet::ResourceApi::PuppetContext < Puppet::ResourceApi::BaseContext
|
|
5
|
+
# declare a separate class to encapsulate Puppet's logging facilities
|
|
6
|
+
class PuppetLogger
|
|
7
|
+
extend Puppet::Util::Logging
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
protected
|
|
11
|
+
|
|
12
|
+
def send_log(level, message)
|
|
13
|
+
PuppetLogger.send_log(level, message)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'hocon'
|
|
2
|
+
require 'hocon/config_syntax'
|
|
3
|
+
require 'puppet/util/network_device/base'
|
|
4
|
+
|
|
5
|
+
module Puppet::Util::NetworkDevice::Simple
|
|
6
|
+
# A basic device class, that reads its configuration from the provided URL.
|
|
7
|
+
# The URL has to be a local file URL.
|
|
8
|
+
class Device
|
|
9
|
+
def initialize(url, _options = {})
|
|
10
|
+
@url = URI.parse(url)
|
|
11
|
+
raise "Unexpected url '#{url}' found. Only file:/// URLs for configuration supported at the moment." unless @url.scheme == 'file'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def facts
|
|
15
|
+
{}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def config
|
|
19
|
+
raise "Trying to load config from '#{@url.path}, but file does not exist." unless File.exist? @url.path
|
|
20
|
+
@config ||= Hocon.load(@url.path, syntax: Hocon::ConfigSyntax::HOCON)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require 'puppet/resource_api/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'puppet-resource_api'
|
|
7
|
+
spec.version = Puppet::ResourceApi::VERSION
|
|
8
|
+
spec.authors = ['David Schmitt']
|
|
9
|
+
spec.email = ['david.schmitt@puppet.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'This libarary provides a simple way to write new native resources for puppet.'
|
|
12
|
+
spec.homepage = 'https://github.com/puppetlabs/puppet-resource_api'
|
|
13
|
+
|
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
15
|
+
f.match(%r{^(test|spec|features)/})
|
|
16
|
+
end
|
|
17
|
+
spec.bindir = 'exe'
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ['lib']
|
|
20
|
+
|
|
21
|
+
# spec.add_runtime_dependency 'childprocesscore', '~> 0.7'
|
|
22
|
+
spec.add_runtime_dependency 'puppet', '>= 4.7'
|
|
23
|
+
|
|
24
|
+
spec.extensions = 'ext/mkrf_conf.rb'
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: puppet-resource_api
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- David Schmitt
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2017-11-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: puppet
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '4.7'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '4.7'
|
|
27
|
+
description:
|
|
28
|
+
email:
|
|
29
|
+
- david.schmitt@puppet.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions:
|
|
32
|
+
- ext/mkrf_conf.rb
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- ".dependency_decisions.yml"
|
|
36
|
+
- ".gitignore"
|
|
37
|
+
- ".rspec"
|
|
38
|
+
- ".rubocop.yml"
|
|
39
|
+
- ".rubocop_todo.yml"
|
|
40
|
+
- ".travis.yml"
|
|
41
|
+
- CONTRIBUTING.md
|
|
42
|
+
- Gemfile
|
|
43
|
+
- LICENSE
|
|
44
|
+
- NOTICE
|
|
45
|
+
- README.md
|
|
46
|
+
- Rakefile
|
|
47
|
+
- appveyor.yml
|
|
48
|
+
- bin/console
|
|
49
|
+
- bin/setup
|
|
50
|
+
- ext/mkrf_conf.rb
|
|
51
|
+
- lib/puppet/resource_api.rb
|
|
52
|
+
- lib/puppet/resource_api/base_context.rb
|
|
53
|
+
- lib/puppet/resource_api/command.rb
|
|
54
|
+
- lib/puppet/resource_api/errors.rb
|
|
55
|
+
- lib/puppet/resource_api/glue.rb
|
|
56
|
+
- lib/puppet/resource_api/io_context.rb
|
|
57
|
+
- lib/puppet/resource_api/puppet_context.rb
|
|
58
|
+
- lib/puppet/resource_api/version.rb
|
|
59
|
+
- lib/puppet/util/network_device/simple/device.rb
|
|
60
|
+
- puppet-resource_api.gemspec
|
|
61
|
+
homepage: https://github.com/puppetlabs/puppet-resource_api
|
|
62
|
+
licenses: []
|
|
63
|
+
metadata: {}
|
|
64
|
+
post_install_message:
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubyforge_project:
|
|
80
|
+
rubygems_version: 2.5.2
|
|
81
|
+
signing_key:
|
|
82
|
+
specification_version: 4
|
|
83
|
+
summary: This libarary provides a simple way to write new native resources for puppet.
|
|
84
|
+
test_files: []
|