rubypuppetpuppetlabs-apache

Cannot create files inside defined type


I want to create files inside a defined type. I have tried it several ways but couldn't solve the issue. Let me explain you my case.

I am creating some files using temapltes and I could do that operation perfectly. I am using below ruby function to gather filename, location kind of data

require 'rexml/document'
include REXML
module Puppet::Parser::Functions
newfunction(:getConfigFileDetails, :type => :rvalue ) do |args|
    fileDetails= []
    doc = REXML::Document.new args[0]
    doc.elements.each("node/congfigurations/config") {
     |config|
            fileName= config.elements["@fileName"].value
            fileLocation= config.elements["@location"].value
            fileDetails << {'filename' => fileName, 'filelocation'=> fileLocation}
    }
    return fileDetails
  end
end

And I use this function inside my puppet class and it works fine for me

define fill_templates() {
  $fileName = $name["filename"]
  $fileLocation = $name['filelocation']
  file { "${fileLocation}/${fileName}/":
    ensure  => present,
    owner   => 'root',
    group   => 'root',
    mode    => '0777',
    content => template("config/${fileName}.erb"),
    require => Exec["unzip_pack"],
 }

}

$configFileDetails = getConfigFileDetails($allConfigurations['configurations'])
fill_templates { $configFileDetails: }

Then I tried to create files with my own content in them not get data from templates. Following is my ruby function

 require 'rexml/document'
 include REXML
 module Puppet::Parser::Functions
    newfunction(:getCreateFileDetails, :type => :rvalue ) do |args|
            fileDetails= []
            doc = REXML::Document.new args[0]
            doc.elements.each("node/create/file") {
            |filedata|
                    fileName= filedata.elements["filename"].text
                    fileLocation= filedata.elements["location"].text
                    fileContent= filedata.elements["content"].text
                    fileDetails << {'filename' => fileName, 'filelocation'=> fileLocation, 'filecontent'=> fileContent }
            }
            return fileDetails
    end
end

And I use it inside my puppet class as follows

define create_files() {
$fileName = $name["filename"]
$fileLocation = $name['filelocation']
$fileContent = $name['filecontent']
file { "${fileLocation}/${fileName}/":
    ensure  => present,
    owner   => 'root',
    group   => 'root',
    content => "$fileContent",
   }
  }
   $createFileDetails = getCreateFileDetails($allConfigurations['configurations'])
   create_files { $createFileDetails: }

But it always gives me error

Could not retrieve catalog from remote server: Could not intern from pson: Could not convert from pson: Could not find relationship target

I cannot realize the cause for this issue. What is the reason the previous template one working and the later one is not working.

Thank you for your light on this


Solution

  • I won't even try to understand how your first approach ever worked. It defies some basic rules of Puppet manifest semantics.

    Basically, if you want to import a data structure (from anywhere, really) and spawn resources from that (through a defined or a native type), it should be structured as a hash:

    {
      'resource-name1' => {
        'attribute-name1' => 'value',
        'attribute-name2' => 'value',
        ...
      },
      'resource-name2' => {
        ...
      }
    }, ...
    

    Building an array like

    [
      { attribute1 => value, attribute2 => value, ... },
      { ... },
    ]
    

    And passing each as the name for a resource is not valid by a long shot.

    To fix your issue, make the custom function return an appropriate hash. (Renaming to avoid camel case - not a Puppet custom.)

    require 'rexml/document'
    include REXML
    module Puppet::Parser::Functions
      newfunction(:get_config_file_details, :type => :rvalue ) do |args|
        result = {}
        doc = REXML::Document.new args[0]
        doc.elements.each("node/create/file") { |filedata|
          name     = filedata.elements["filename"].text
          location = filedata.elements["location"].text
          conent   = filedata.elements["content"].text
          result[name] = {
            'fileLocation' => location,
            'fileContent'  => content,
          }
        }
        result
      end
    end
    

    Your defined types needs to accept actual parameters. (Aside: create_files is a very bad name for a type - type names should be singular, and descriptive of what each resource instance represents.)

    define my_config_file($fileName=$name, $fileLocation, $fileContent) {
        file { "${fileLocation}/${fileName}/":
        ...
    }
    

    Finally, to create the appropriate instances from the hash, use the create_resources function.

    $my_config_file_data = get_config_file_details($xml)
    create_resources('my_config_files', $my_config_file_data)
    

    Final advice: If this is at all possible, serialize your data in an appropriate YAML or JSON format instead of XML. You can make those available to Hiera and load them directly through the hiera() function - no custom Ruby code needed.