From 0830311e1abe99ac0c16c5faf33e306d976a8784 Mon Sep 17 00:00:00 2001 From: Pat Riehecky Date: Thu, 14 Apr 2022 12:30:12 -0500 Subject: [PATCH 1/2] MODULES-11309 : convert a string to a resource --- lib/puppet/functions/stdlib/str2resource.rb | 37 +++++++++++++++++ spec/functions/str2resource_spec.rb | 44 +++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 lib/puppet/functions/stdlib/str2resource.rb create mode 100644 spec/functions/str2resource_spec.rb diff --git a/lib/puppet/functions/stdlib/str2resource.rb b/lib/puppet/functions/stdlib/str2resource.rb new file mode 100644 index 000000000..d34d3b6fa --- /dev/null +++ b/lib/puppet/functions/stdlib/str2resource.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# @summary +# This converts a string to a puppet resource. +# +# This attempts to convert a string like 'File[/foo]' into the +# puppet resource `File['/foo']` as detected by the catalog. +# +# Things like 'File[/foo, /bar]' are not supported as a +# title might contain things like ',' or ' '. There is +# no clear value seperator to use. +# +# This function can depend on the parse order of your +# manifests/modules as it inspects the catalog thus far. +Puppet::Functions.create_function(:'stdlib::str2resource') do + # @param res_string The string to lookup as a resource + # @example + # stdlib::str2resource('File[/foo]') => File[/foo] + # @return Puppet::Resource + dispatch :str2resource do + param 'String', :res_string + #return_type 'Puppet::Resource' + return_type 'Any' + end + + def str2resource(res_string) + type_name, title = Puppet::Resource.type_and_title(res_string, nil) + + resource = closure_scope.findresource(type_name, title) + + if resource.nil? + raise(Puppet::ParseError, "stdlib::str2resource(): could not find #{type_name}[#{title}], this is parse order dependant and values should not be quoted") + end + + return resource + end +end diff --git a/spec/functions/str2resource_spec.rb b/spec/functions/str2resource_spec.rb new file mode 100644 index 000000000..afac627fd --- /dev/null +++ b/spec/functions/str2resource_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'stdlib::str2resource' do + context 'when default' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params.and_raise_error(ArgumentError, %r{stdlib::str2resource}) } + end + + context 'when testing simple resource definitions exist' do + let :pre_condition do + <<-PRECOND + file { 'foo': } + file { '/foo': } + file { 'foot': } + user { 'foo': } + PRECOND + end + + file_foo = Puppet::Resource.new(:file, 'foo') + user_foo = Puppet::Resource.new(:user, 'foo') + + it { is_expected.to run.with_params('File[foo]').and_return(file_foo) } + it { is_expected.not_to run.with_params('File[\'foo\']') } + it { is_expected.not_to run.with_params('File["foo"]') } + + it { is_expected.to run.with_params('User[foo]').and_return(user_foo) } + end + + context 'when someone tries a compound definition' do + let :pre_condition do + 'user { "foo, bar": }' + end + + user_foo_bar = Puppet::Resource.new(:user, 'foo, bar') + + it { is_expected.to run.with_params('User[foo, bar]').and_return(user_foo_bar) } + end + + context 'when testing simple resource definitions no exist' do + it { is_expected.not_to run.with_params('File[foo]') } + end +end From 4e68d1a6bf9c892583f1271629ea198b3421023c Mon Sep 17 00:00:00 2001 From: Pat Riehecky Date: Mon, 18 Apr 2022 12:41:28 -0500 Subject: [PATCH 2/2] Add `stdlib::manage` to utilize the `stdlib::str2resource` function --- README.md | 4 +- lib/puppet/functions/stdlib/str2resource.rb | 6 +- manifests/init.pp | 3 +- manifests/manage.pp | 77 +++++++++++++++++++++ spec/classes/manage_spec.rb | 43 ++++++++++++ 5 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 manifests/manage.pp create mode 100644 spec/classes/manage_spec.rb diff --git a/README.md b/README.md index 256c381d3..d761bd0d5 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ If you are authoring a module that depends on stdlib, be sure to [specify depend Most of stdlib's features are automatically loaded by Puppet. To use standardized run stages in Puppet, declare this class in your manifest with `include stdlib`. -When declared, stdlib declares all other classes in the module. The only other class currently included in the module is `stdlib::stages`. +When declared, stdlib declares all other classes in the module. This currently consists of `stdlib::manage` and `stdlib::stages`. The `stdlib::stages` class declares various run stages for deploying infrastructure, language runtimes, and application layers. The high level stages are (in order): @@ -62,6 +62,8 @@ node default { } ``` +The `stdlib::manage` class provides an interface for generating trivial resource declarations via the `create_resources` parameter. + ## Reference For information on the classes and types, see the [REFERENCE.md](https://github.com/puppetlabs/puppetlabs-stdlib/blob/main/REFERENCE.md). diff --git a/lib/puppet/functions/stdlib/str2resource.rb b/lib/puppet/functions/stdlib/str2resource.rb index d34d3b6fa..7290b176a 100644 --- a/lib/puppet/functions/stdlib/str2resource.rb +++ b/lib/puppet/functions/stdlib/str2resource.rb @@ -19,7 +19,7 @@ # @return Puppet::Resource dispatch :str2resource do param 'String', :res_string - #return_type 'Puppet::Resource' + # return_type 'Puppet::Resource' return_type 'Any' end @@ -29,9 +29,9 @@ def str2resource(res_string) resource = closure_scope.findresource(type_name, title) if resource.nil? - raise(Puppet::ParseError, "stdlib::str2resource(): could not find #{type_name}[#{title}], this is parse order dependant and values should not be quoted") + raise(Puppet::ParseError, "stdlib::str2resource(): could not find #{type_name}[#{title}], this is parse order dependent and values should not be quoted") end - return resource + resource end end diff --git a/manifests/init.pp b/manifests/init.pp index 664a3ceb8..987c6d8e3 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -5,8 +5,9 @@ # declared in order to use the standardized run stages. # # Declares all other classes in the stdlib module. Currently, this consists -# of stdlib::stages. +# of stdlib::stages and stdlib::manage. # class stdlib { + include stdlib::manage include stdlib::stages } diff --git a/manifests/manage.pp b/manifests/manage.pp new file mode 100644 index 000000000..36c2d6574 --- /dev/null +++ b/manifests/manage.pp @@ -0,0 +1,77 @@ +# @summary A simple place to define trivial resources +# +# Sometimes your systems require a single simple resource. +# It can feel unnecessary to create a module for a single +# resource. There are a number of possible patterns to +# generate trivial resource definitions. This is an attempt +# to create a single clear method for uncomplicated resources. +# There is limited support for `before`, `require`, `notify`, +# and `subscribe`. However, the target resources must be defined +# before this module is run. +# +# @param create_resources +# A hash of resources to create +# NOTE: functions, such as `template` or `epp` are not evaluated. +# +# @example +# class { 'stdlib::manage': +# 'create_resources' => { +# 'file' => { +# '/etc/motd.d/hello' => { +# 'content' => 'I say Hi', +# 'notify' => 'Service[sshd]', +# } +# }, +# 'package' => { +# 'example' => { +# 'ensure' => 'installed', +# } +# } +# } +# +# @example +# stdlib::manage::create_resources: +# file: +# '/etc/motd.d/hello': +# content: I say Hi +# notify: 'Service[sshd]' +# package: +# example: +# ensure: installed +class stdlib::manage ( + Hash[String, Hash] $create_resources = {} +) { + $create_resources.each |$type, $resources| { + $resources.each |$title, $attributes| { + $filtered_attributes = $attributes.filter |$key, $value| { + $key !~ /(before|require|notify|subscribe)/ + } + + if $attributes['before'] { + $_before = stdlib::str2resource($attributes['before']) + } else { + $_before = undef + } + + if $attributes['require'] { + $_require = stdlib::str2resource($attributes['require']) + } else { + $_require = undef + } + + if $attributes['notify'] { + $_notify = stdlib::str2resource($attributes['notify']) + } else { + $_notify = undef + } + + if $attributes['subscribe'] { + $_subscribe = stdlib::str2resource($attributes['subscribe']) + } else { + $_subscribe = undef + } + + create_resources($type, { $title => $filtered_attributes }, { 'before' => $_before, 'require' => $_require, 'notify' => $_notify, 'subscribe' => $_subscribe }) + } + } +} diff --git a/spec/classes/manage_spec.rb b/spec/classes/manage_spec.rb new file mode 100644 index 000000000..3d046f520 --- /dev/null +++ b/spec/classes/manage_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'stdlib::manage' do + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } + + it { is_expected.to compile } + end + end + + describe 'with resources to create' do + let :pre_condition do + <<-PRECOND + file { '/etc/motd.d' : } + service { 'sshd' : } + PRECOND + end + let :params do + { + 'create_resources' => { + 'file' => { + '/etc/motd.d/hello' => { + 'content' => 'I say Hi', + 'notify' => 'Service[sshd]', + } + }, + 'package' => { + 'example' => { + 'ensure' => 'installed', + } + } + } + } + end + + it { is_expected.to compile } + it { is_expected.to contain_file('/etc/motd.d/hello').with_content('I say Hi').with_notify('Service[sshd]') } + it { is_expected.to contain_package('example').with_ensure('installed') } + end +end