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.
@@ -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,5 @@
1
+ module Puppet
2
+ module ResourceApi
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ 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: []