| Kategorien: | Automatisierung HowTos |
|---|---|
| Tags: | Puppet |
Puppet ist eine Software-Konfigurationsmanagement-Lösung zur Verwaltung von IT-Infrastrukturen. Eines der ersten Dinge, die man über Puppet lernen muss, ist seine domänenspezifische Sprache – die Puppet-DSL – und die damit verbundenen Konzepte.
Benutzer können ihren Code in Klassen und Modulen organisieren und vordefinierte Ressourcentypen verwenden, um Ressourcen wie Dateien, Pakete, Dienste und andere zu verwalten.
Die am häufigsten verwendeten Typen sind Teil des Puppet-Kerns und in Ruby implementiert. Zusammengesetzte Ressourcentypen können über die Puppet-DSL von bereits bekannten Typen durch den Puppet-Benutzer selbst definiert oder als Teil eines externen Puppet-Moduls importiert werden, das von externen Modulentwicklern gepflegt wird.
Es kommt vor, dass Puppet-Benutzer lange Zeit innerhalb der Puppet-DSL bleiben können, selbst wenn sie regelmäßig mit Puppet arbeiten.
Das erste Mal, dass ich einen Einblick in dieses Thema bekam, war, als Debian Stable Puppet 5.5 auslieferte, was noch nicht allzu lange her ist. Die Puppet 5.5-Dokumentation enthält ein Kapitel über benutzerdefinierte Typen bzw. Provider-Entwicklung, aber für mich wirkten sie unvollständig und es fehlten eigenständige Beispiele. Anscheinend war ich nicht der Einzige, der so empfand, obwohl Puppets Gem-Dokumentation einen guten Überblick darüber gibt, was prinzipiell möglich ist.
Gary Larizzas Blogbeitrag ist mehr als zehn Jahre alt. Ich habe mir kürzlich die Dokumentation für Puppet 7 zu diesem Thema noch einmal angesehen, da dies die aktuelle Puppet-Version in Debian Stable ist.
Der Puppet 5.5-Ansatz zur Typ- & Provider-Entwicklung wird heute als Low-Level-Methode bezeichnet, und seine Dokumentation hat sich nicht wesentlich geändert. Puppet 6 und höhere Versionen empfehlen jedoch eine neue Methode zur Erstellung benutzerdefinierter Typen & Provider über die sogenannte Resource-API, deren Dokumentation eine wesentliche Verbesserung gegenüber der Low-Level-Methode darstellt. Die Resource-API ist jedoch kein Ersatz und weist mehrere dokumentierte Einschränkungen auf.
Nichtsdestotrotz werden wir für den restlichen Blogbeitrag einen kleinen Teil der Funktionalität von file sowohl mit der Low-Level-Methode als auch mit der Resource-API neu prototypisieren, nämlich die Eigenschaften ensure und content.
Die folgenden Vorbereitungen sind in einem Agent-Server-Setup nicht notwendig. Wir verwenden bundle, um eine puppet ausführbare Datei für diese Demo zu erhalten.
demo@85c63b50bfa3:~$ cat > Gemfile <<EOF source 'https://rubygems.org' gem 'puppet', '>= 6' EOF
demo@85c63b50bfa3:~$ bundle install Fetching gem metadata from https://rubygems.org/........ Resolving dependencies... ... Installing puppet 8.10.0 Bundle complete! 1 Gemfile dependency, 17 gems now installed. Bundled gems are installed into `./.vendor`
demo@85c63b50bfa3:~$ cat > file_builtin.pp <<EOF
$file = '/home/demo/puppet-file-builtin'
file {$file: content => 'This is madness'}
EOF
demo@85c63b50bfa3:~$ bin/puppet apply file_builtin.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.01 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-builtin]/ensure: defined content as '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'
Notice: Applied catalog in 0.04 seconds
demo@85c63b50bfa3:~$ sha256sum /home/demo/puppet-file-builtin
0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e /home/demo/puppet-file-builtin
Beachten Sie die von Puppet ausgegebenen Informationen zur Statusänderung.
Benutzerdefinierte Typen und Provider, die nicht über ein Gem installiert werden, müssen Teil eines Puppet-Moduls sein, damit sie über den Pluginsync-Mechanismus auf Puppet-Agenten kopiert werden können.
Ein üblicher Speicherort für Puppet-Module ist das Verzeichnis modules innerhalb einer Puppet-Umgebung. Für diese Demo deklarieren wir ein demo Modul.
Unser erster Versuch ist die folgende Typdefinition für einen neuen Typ, den wir nennen werden: file_llmethod. Es verfügt über keine Dokumentation oder Validierung von Eingabewerten.
# modules/demo/lib/puppet/type/file_llmethod.rb
Puppet::Type.newtype(:file_llmethod) do
newparam(:path, namevar: true) {}
newproperty(:content) {}
newproperty(:ensure) do
newvalues(:present, :absent)
defaultto(:present)
end
end
Wir haben einen path Parameter deklariert, der als namevar für diesen Typ dient – es kann keine anderen file_llmethod Instanzen geben, die dieselbe path verwalten. Die Eigenschaft ensure ist auf zwei Werte beschränkt und standardmäßig auf present gesetzt.
Die folgende Provider-Implementierung besteht aus einem Getter und einem Setter für jede der beiden Eigenschaften content und ensure.
# modules/demo/lib/puppet/provider/file_llmethod/ruby.rb Puppet::Type.type(:file_llmethod).provide(:ruby) do def ensure File.exist?(@resource[:path]) ? :present : :absent end def ensure=(value) if value == :present # reuse setter self.content=(@resource[:content]) else File.unlink(@resource[:path]) end end def content File.read(@resource[:path]) end def content=(value) File.write(@resource[:path], value) end end
Dies ergibt Folgendes:
demo@85c63b50bfa3:~$ cat > file_llmethod_create.pp <<EOF
$file = '/home/demo/puppet-file-lowlevel-method-create'
file {$file: ensure => absent} ->
file_llmethod {$file: content => 'This is Sparta!'}
EOF
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_create.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.01 seconds
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-create]/ensure: defined 'ensure' as 'present'
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_create.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.01 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-lowlevel-method-create]/ensure: removed
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-create]/ensure: defined 'ensure' as 'present'
demo@85c63b50bfa3:~$ cat > file_llmethod_change.pp <<EOF
$file = '/home/demo/puppet-file-lowlevel-method-change'
file {$file: content => 'This is madness'} ->
file_llmethod {$file: content => 'This is Sparta!'}
EOF
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_change.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-lowlevel-method-change]/ensure: defined content as '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-change]/content: content changed 'This is madness' to 'This is Sparta!'
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_change.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-lowlevel-method-change]/content: content changed '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2' to '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-change]/content: content changed 'This is madness' to 'This is Sparta!'
Unser benutzerdefinierter Typ funktioniert bereits einigermaßen, obwohl wir keinen expliziten Vergleich von Ist- und Soll-Zustand implementiert haben. Puppet erledigt dies für uns basierend auf dem Puppet-Katalog und den Rückgabewerten des Property-Getters. Unsere definierten Setter werden von Puppet auch nur bei Bedarf aufgerufen.
Wir können auch sehen, dass die Statusänderungsbenachrichtigung ensure defined 'ensure' as 'present' ist und das gewünschte content in keiner Weise berücksichtigt, während die Statusänderungsbenachrichtigung content Klartext anzeigt. Beides sagt uns, dass die SHA256-Prüfsumme aus dem file_builtin.pp Beispiel bereits etwas nicht Triviales ist.
Als nächsten Schritt fügen wir eine Validierung für path und content.
# modules/demo/lib/puppet/type/file_llmethod.rb
Puppet::Type.newtype(:file_llmethod) do
newparam(:path, namevar: true) do
validate do |value|
fail "# {value} is not a String" unless value.is_a?(String)
fail "# {value} is not an absolute path" unless File.absolute_path?(value)
end
end
newproperty(:content) do
validate do |value|
fail "# {value} is not a String" unless value.is_a?(String)
end
end
newproperty(:ensure) do
newvalues(:present, :absent)
defaultto(:present)
end
end
Fehlgeschlagene Validierungen sehen wie folgt aus:
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules --exec 'file_llmethod {"./relative/path": }'
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Error: Parameter path failed on File_llmethod[./relative/path]: ./relative/path is not an absolute path (line: 1)
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules --exec 'file_llmethod {"/absolute/path": content => 42}'
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Error: Parameter content failed on File_llmethod[/absolute/path]: 42 is not a String (line: 1)
Wir überschreiben change_to_s, damit Statusänderungen Inhalts-Prüfsummen enthalten:
# modules/demo/lib/puppet/type/file_llmethod.rb
require 'digest'
Puppet::Type.newtype(:file_llmethod) do
newparam(:path, namevar: true) do
validate do |value|
fail "# {value} is not a String" unless value.is_a?(String)
fail "# {value} is not an absolute path" unless File.absolute_path?(value)
end
end
newproperty(:content) do
validate do |value|
fail "# {value} is not a String" unless value.is_a?(String)
end
define_method(:change_to_s) do |currentvalue, newvalue|
old = "{sha256}#{Digest::SHA256.hexdigest(currentvalue)}"
new = "{sha256}#{Digest::SHA256.hexdigest(newvalue)}"
"content changed '#{old}' to '#{new}'"
end
end
newproperty(:ensure) do
define_method(:change_to_s) do |currentvalue, newvalue| if currentvalue == :absent should = @resource.property(:content).should digest = "{sha256}#{Digest::SHA256.hexdigest(should)}" "defined content as '#{digest}'" else super(currentvalue, newvalue) end end newvalues(:present, :absent) defaultto(:present) end endDie obige Typdefinition ergibt:
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_create.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-lowlevel-method-create]/ensure: removed
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-create]/ensure: defined content as '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_change.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-lowlevel-method-change]/content: content changed '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2' to '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-change]/content: content changed '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e' to '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'
Soweit so gut. Obwohl unsere aktuelle Implementierung anscheinend funktioniert, weist sie mindestens einen großen Fehler auf. Wenn die verwaltete Datei bereits existiert, speichert der Provider den gesamten Inhalt der Datei im Speicher.
demo@85c63b50bfa3:~$ cat > file_llmethod_change_big.pp <<EOF
$file = '/home/demo/puppet-file-lowlevel-method-change_big'
file_llmethod {$file: content => 'This is Sparta!'}
EOF
demo@85c63b50bfa3:~$ rm -f /home/demo/puppet-file-lowlevel-method-change_big
demo@85c63b50bfa3:~$ ulimit -Sv 200000
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_change_big.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-change_big]/ensure: defined content as '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'
Notice: Applied catalog in 0.02 seconds
demo@85c63b50bfa3:~$ dd if=/dev/zero of=/home/demo/puppet-file-lowlevel-method-change_big seek=8G bs=1 count=1
1+0 records in
1+0 records out
1 byte copied, 8.3047e-05 s, 12.0 kB/s
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_change_big.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Error: Could not run: failed to allocate memory
Stattdessen sollte die Implementierung nur die Prüfsumme speichern, damit Puppet anhand der Prüfsummen entscheiden kann, ob unser content= Setter aufgerufen werden muss.
Dies bedeutet auch, dass die content des Puppet-Katalogs von munge mit einer Prüfsumme versehen werden muss, bevor sie von Puppets interner Vergleichsroutine verarbeitet wird. Glücklicherweise haben wir auch über shouldorig Zugriff auf den ursprünglichen Wert.
# modules/demo/lib/puppet/type/file_llmethod.rb
require 'digest'
Puppet::Type.newtype(:file_llmethod) do
newparam(:path, namevar: true) do
validate do |value|
fail "# {value} is not a String" unless value.is_a?(String)
fail "# {value} is not an absolute path" unless File.absolute_path?(value)
end
end
newproperty(:content) do
validate do |value|
fail "# {value} is not a String" unless value.is_a?(String)
end
munge do |value|
"{sha256}#{Digest::SHA256.hexdigest(value)}"
end
# No need to override change_to_s with munging
end
newproperty(:ensure) do
define_method(:change_to_s) do |currentvalue, newvalue|
if currentvalue == :absent
should = @resource.property(:content).should
"defined content as '#{should}'"
else
super(currentvalue, newvalue)
end
end
newvalues(:present, :absent)
defaultto(:present)
end
end
# modules/demo/lib/puppet/provider/file_llmethod/ruby.rb
Puppet::Type.type(:file_llmethod).provide(:ruby) do
...
def content
File.open(@resource[:path], 'r') do |file|
sha = Digest::SHA256.new
while chunk = file.read(2**16)
sha << chunk
end
"{sha256}#{sha.hexdigest}"
end
end
def content=(value)
# value is munged, but we need to write the original
File.write(@resource[:path], @resource.parameter(:content).shouldorig[0])
end
end
Jetzt können wir große Dateien verwalten:
demo@85c63b50bfa3:~$ ulimit -Sv 200000
demo@85c63b50bfa3:~$ dd if=/dev/zero of=/home/demo/puppet-file-lowlevel-method-change_big seek=8G bs=1 count=1
1+0 records in
1+0 records out
1 byte copied, 9.596e-05 s, 10.4 kB/s
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_change_big.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-change_big]/content: Inhalt geändert von '{sha256}ef17a425c57a0e21d14bec2001d8fa762767b97145b9fe47c5d4f2fda323697b' zu '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df'
Da stimmt immer noch etwas nicht. Vielleicht haben Sie bemerkt, dass der content-Getter unseres Providers bedingungslos versucht, eine Datei zu öffnen, und dennoch hat der file_llmethod_create.pp-Lauf keinen Fehler erzeugt. Es scheint, dass ein ensure-Übergang von absent zu present den content-Getter kurzschließt, obwohl wir keinen Wunsch geäußert haben, dies zu tun.
Es stellt sich heraus, dass eine ensure-Eigenschaft von Puppet besonders behandelt wird. Wenn wir versucht hätten, stattdessen eine makeitso-Eigenschaft anstelle von ensure zu verwenden, gäbe es keinen Kurzschluss und der content-Getter würde eine Ausnahme auslösen.
Wir werden den content-Getter jedoch nicht reparieren. Wenn Puppet eine spezielle Behandlung für ensure hat, sollten wir Puppets beabsichtigten Mechanismus dafür verwenden und den Typ ensurable deklarieren:
# modules/demo/lib/puppet/type/file_llmethod.rb
require 'digest'
Puppet::Type.newtype(:file_llmethod) do
ensurable
newparam(:path, namevar: true) do
validate do |value|
fail "#{value} ist keine Zeichenkette", es sei denn, value.is_a?(String)
fail "#{value} ist kein absoluter Pfad", es sei denn, File.absolute_path?(value)
end
end
newproperty(:content) do
validate do |value|
fail "#{value} ist keine Zeichenkette", es sei denn, value.is_a?(String)
end
munge do |value|
"{sha256}#{Digest::SHA256.hexdigest(value)}"
end
end
end
Mit ensurable muss der Provider drei neue Methoden implementieren, aber wir können die ensure-Accessoren weglassen:
# modules/demo/lib/puppet/provider/file_llmethod/ruby.rb
Puppet::Type.type(:file_llmethod).provide(:ruby) do
def exists?
File.exist?(@resource[:name])
end
def create
self.content=(:dummy)
end
def destroy
File.unlink(@resource[:name])
end
def content
File.open(@resource[:path], 'r') do |file|
sha = Digest::SHA256.new
while chunk = file.read(2**16)
sha << chunk
end
"{sha256}#{sha.hexdigest}"
end
end
def content=(value)
# value is munged, but we need to write the original
File.write(@resource[:path], @resource.parameter(:content).shouldorig[0])
end
end
Jetzt haben wir jedoch den SHA256-Prüfsumme bei der Dateierstellung verloren:
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_create.pp Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds Notice: /Stage[main]/Main/File[/home/demo/puppet-file-lowlevel-method-create]/ensure: removed Notice: /Stage[main]/Main/File_llmethod[/home/demo/puppet-file-lowlevel-method-create]/ensure: created
Um es zurückzubekommen, ersetzen wir ensurable durch eine angepasste Implementierung davon, die unsere vorherige change_to_s-Überschreibung enthält:
newproperty(:ensure, :parent => Puppet::Property::Ensure) do
defaultvalues
define_method(:change_to_s) do |currentvalue, newvalue|
if currentvalue == :absent
should = @resource.property(:content).should
"defined content as '#{should}'"
else
super(currentvalue, newvalue)
end
end
end
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_llmethod_create.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-lowlevel-method-create]/ensure: removed
Notice: /stage[main]/main/File_llmethod[/home/demo/puppet-file-lowlevel-method-create]/ensure: defined content as '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'
Unser endgültiger Low-Level-Prototyp sieht also wie folgt aus.
# modules/demo/lib/puppet/type/file_llmethod.rb
# frozen_string_literal: true
require 'digest'
Puppet::Type.newtype(:file_llmethod) do
newparam(:path, namevar: true) do
validate do |value|
fail "#{value} ist keine Zeichenkette", es sei denn, value.is_a?(String)
fail "#{value} ist kein absoluter Pfad", es sei denn, File.absolute_path?(value)
end
end
newproperty(:content) do
validate do |value|
fail "#{value} ist keine Zeichenkette", es sei denn, value.is_a?(String)
end
munge do |value|
"{sha256}#{Digest::SHA256.hexdigest(value)}"
end
end
newproperty(:ensure, :parent => Puppet::Property::Ensure) do
defaultvalues
define_method(:change_to_s) do |currentvalue, newvalue|
if currentvalue == :absent
should = @resource.property(:content).should
"defined content as '#{should}'"
else
super(currentvalue, newvalue)
end
end
end
end
# modules/demo/lib/puppet/provider/file_llmethod/ruby.rb
# frozen_string_literal: true
Puppet::Type.type(:file_llmethod).provide(:ruby) do
def exists?
File.exist?(@resource[:name])
end
def create
self.content=(:dummy)
end
def destroy
File.unlink(@resource[:name])
end
def content
File.open(@resource[:path], 'r') do |file|
sha = Digest::SHA256.new
while (chunk = file.read(2**16))
sha << Chunk
Ende
"{sha256}#{sha.hexdigest}"
Ende
Ende
def content=(_value)
# Der Wert wird verändert, aber wir müssen das Original schreiben
File.write(@resource[:path], @resource.parameter(:content).shouldorig[0])
Ende
Ende
Gemäß der Resource-API Dokumentation müssen wir unseren neuen file_rsapi Typ definieren, indem wir Puppet::ResourceApi.register_type mit mehreren Parametern aufrufen, darunter die gewünschten Attribute, sogar ensure.
# modules/demo/lib/puppet/type/file_rsapi.rb
require 'puppet/resource_api'
Puppet::ResourceApi.register_type(
name: 'file_rsapi',
attributes: {
content: {
desc: 'Beschreibung des Inhaltsparameters',
type: 'String'
},
ensure: {
default: 'present',
desc: 'Beschreibung des Sicherstellungsparameters',
type: 'Enum[present, absent]'
},
path: {
behaviour: :namevar,
desc: 'Beschreibung des Pfadparameters',
type: 'Pattern[/\A\/([^\n\/\0]+\/*)*\z/]'
},
},
desc: 'Beschreibung von file_rsapi'
)
Das path type verwendet einen eingebauten Puppet-Datentyp. Stdlib::Absolutepath wäre bequemer gewesen, aber externe Datentypen sind mit der Resource-API noch nicht möglich.
Im Vergleich zu unserem Low-Level-Prototyp hat die obige Typdefinition keine SHA256-Munging- und SHA256-Ausgabe-Gegenstücke. Das canonicalize Provider-Feature sieht ähnlich aus wie Munging, aber wir überspringen es vorerst.
Die Resource-API Dokumentation sagt uns, dass wir eine get und eine set Methode in unserem Provider implementieren sollen, mit der Aussage
Die Get-Methode meldet den aktuellen Zustand der verwalteten Ressourcen. Sie gibt eine Aufzählung aller vorhandenen Ressourcen zurück. Jede Ressource ist ein Hash mit Attributnamen als Schlüssel und ihren jeweiligen Werten als Werte.
Diese Forderung ist der erste Wermutstropfen, da wir definitiv nicht alle Dateien mit ihrem Inhalt lesen und im Speicher speichern wollen. Wir können diese Forderung ignorieren – woher sollte die Resource-API das auch wissen.
Die dokumentierte Signatur ist jedoch def get(context) {...}, wobei context keine Informationen über die Ressource hat, die wir verwalten wollen.
Dies wäre ein Show-Stopper gewesen, wenn das simple_get_filter-Provider-Feature nicht existieren würde, das die Signatur in def get(context, names = nil) {...} ändert.
Unsere erste Version von file_rsapi ist also die folgende.
# modules/demo/lib/puppet/type/file_rsapi.rb
require 'puppet/resource_api'
Puppet::ResourceApi.register_type(
name: 'file_rsapi',
features: %w[simple_get_filter],
attributes: {
content: {
desc: 'Beschreibung des Inhaltsparameters',
type: 'String'
},
ensure: {
default: 'present',
desc: 'Beschreibung des Sicherstellungsparameters',
type: 'Enum[present, absent]'
},
path: {
behaviour: :namevar,
desc: 'Beschreibung des Pfadparameters',
type: 'Pattern[/\A\/([^\n\/\0]+\/*)*\z/]'
},
},
desc: 'Beschreibung von file_rsapi'
)
# modules/demo/lib/puppet/provider/file_rsapi/file_rsapi.rb
require 'digest'
class Puppet::Provider::FileRsapi::FileRsapi
def get(context, names)
(names or []).map do |name|
File.exist?(name) ? {
path: name,
ensure: 'present',
content: filedigest(name),
} : nil
end.compact # remove non-existing resources
end
def set(context, changes)
changes.each do |path, change|
if change[:should][:ensure] == 'present'
File.write(path, change[:should][:content])
elsif File.exist?(path)
File.delete(path)
end
end
end
def filedigest(path)
File.open(path, 'r') do |file|
sha = Digest::SHA256.new
while chunk = file.read(2**16)
sha << chunk
end
"{sha256}#{sha.hexdigest}"
end
end
end
Das gewünschte content wird korrekt in die Datei geschrieben, aber wir haben wieder keine SHA256-Prüfsumme bei der Erstellung sowie unnötige Schreibvorgänge, da die Prüfsumme von get nicht mit dem Klartext aus dem Katalog übereinstimmt:
demo@85c63b50bfa3:~$ cat > file_rsapi_create.pp <<EOF
$file = '/home/demo/puppet-file-rsapi-create'
file {$file: ensure => absent} ->
file_rsapi {$file: content => 'This is Sparta!'}
EOF
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_rsapi_create.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-rsapi-create]/ensure: removed
Notice: /stage[main]/main/File_rsapi[/home/demo/puppet-file-rsapi-create]/ensure: defined 'ensure' as 'present'
Notice: Applied catalog in 0.02 seconds
demo@85c63b50bfa3:~$ cat > file_rsapi_change.pp <<EOF
$file = '/home/demo/puppet-file-rsapi-change'
file {$file: content => 'This is madness'} ->
file_rsapi {$file: content => 'This is Sparta!'}
EOF
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_rsapi_change.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-rsapi-change]/content: content changed '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2' to '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'
Notice: /stage[main]/main/File_rsapi[/home/demo/puppet-file-rsapi-change]/content: content changed '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e' to 'This is Sparta!'
Notice: Applied catalog in 0.03 seconds
Daher aktivieren und implementieren wir das canonicalize-Provider-Feature.
Gemäß der Dokumentation wird canonicalize auf die Ergebnisse von get sowie auf die Katalogeigenschaften angewendet. Einerseits wollen wir den Inhalt der Datei nicht in den Speicher lesen, andererseits können wir den Inhalt der Datei nicht zweimal prüfen.
Ein einfacher Weg wäre zu prüfen, ob der canonicalized-Aufruf nach dem get-Aufruf erfolgt:
def canonicalize(context, resources) if @stage == :get # do nothing, is-state canonicalization already performed by get() else # catalog canonicalization ... end end def get(context, paths) @stage = :get ... end
Obwohl dies für die aktuelle Implementierung der Resource-API funktioniert, gibt es keine Garantie für die Reihenfolge der canonicalize-Aufrufe. Stattdessen erben wir von String und behandeln die Prüfsummenbildung intern. Wir fügen auch einige Zustandsänderungsaufrufe zu den entsprechenden context-Methoden hinzu.
Unsere endgültige Resource-API-basierte Prototypimplementierung ist:
# modules/demo/lib/puppet/type/file_rsapi.rb
# frozen_string_literal: true
require 'puppet/resource_api'
Puppet::ResourceApi.register_type(
name: 'file_rsapi',
features: %w[simple_get_filter canonicalize],
attributes: {
content: {
desc: 'Beschreibung des Inhaltsparameters',
type: 'String'
},
ensure: {
default: 'present',
desc: 'Beschreibung des Sicherstellungsparameters',
type: 'Enum[present, absent]'
},
path: {
behaviour: :namevar,
desc: 'Beschreibung des Pfadparameters',
type: 'Pattern[/\A\/([^\n\/\0]+\/*)*\z/]'
}
},
desc: 'Beschreibung von file_rsapi'
)
# modules/demo/lib/puppet/provider/file_rsapi/file_rsapi.rb
# frozen_string_literal: true
require 'digest'
require 'pathname'
class Puppet::Provider::FileRsapi::FileRsapi
class CanonicalString < String
attr_reader :original
def class
# Mask as String for YAML.dump to mitigate
# Error: Transaction store file /var/cache/puppet/state/transactionstore.yaml
# is corrupt ((/var/cache/puppet/state/transactionstore.yaml): Tried to
# load unspecified class: Puppet::Provider::FileRsapi::FileRsapi::CanonicalString)
String
end
def self.from(obj)
return obj if obj.is_a?(self)
return new(filedigest(obj)) if obj.is_a?(Pathname)
new("{sha256}#{Digest::SHA256.hexdigest(obj)}", obj)
end
def self.filedigest(path)
File.open(path, 'r') do |file|
sha = Digest::SHA256.new
while (chunk = file.read(2**16))
sha << chunk
end
"{sha256}#{sha.hexdigest}"
end
end
def initialize(canonical, original = nil)
@original = original
super(canonical)
end
end
def canonicalize(_context, resources)
resources.each do |resource|
next if resource[:ensure] == 'absent'
resource[:content] = CanonicalString.from(resource[:content])
end
resources
end
def get(_context, names)
(names or []).map do |name|
next unless File.exist?(name)
{
content: CanonicalString.from(Pathname.new(name)),
ensure: 'present',
path: name
}
end.compact # remove non-existing resources
end
def set(context, changes)
changes.each do |path, change|
if change[:should][:ensure] == 'present'
File.write(path, change[:should][:content].original)
if change[:is][:ensure] == 'present'
# The only other possible change is due to content,
# but content change transition info is covered implicitly
else
context.created("#{path} with content '#{change[:should][:content]}'")
end
elsif File.exist?(path)
File.delete(path)
context.deleted(path)
end
end
end
end
Es gibt uns:
demo@85c63b50bfa3:~$ cat > file_rsapi_create.pp <<EOF
$file = '/home/demo/puppet-file-rsapi-create'
file {$file: ensure => absent} ->
file_rsapi {$file: content => 'This is Sparta!'}
EOF
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_rsapi_create.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-rsapi-create]/ensure: removed
Notice: /stage[main]/main/File_rsapi[/home/demo/puppet-file-rsapi-create]/ensure: defined 'ensure' as 'present'
Notice: file_rsapi: Created: /home/demo/puppet-file-rsapi-create with content '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'
demo@85c63b50bfa3:~$ cat > file_rsapi_change.pp <<EOF
$file = '/home/demo/puppet-file-rsapi-change'
file {$file: content => 'This is madness'} ->
file_rsapi {$file: content => 'This is Sparta!'}
EOF
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_rsapi_change.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-rsapi-change]/content: content changed '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2' to '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'
Notice: /stage[main]/main/File_rsapi[/home/demo/puppet-file-rsapi-change]/content: content changed '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e' to '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'
demo@85c63b50bfa3:~$ cat > file_rsapi_remove.pp <<EOF
$file = '/home/demo/puppet-file-rsapi-remove'
file {$file: ensure => present} ->
file_rsapi {$file: ensure => absent}
EOF
demo@85c63b50bfa3:~$ bin/puppet apply --modulepath modules file_rsapi_remove.pp
Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds
Notice: /stage[main]/main/file[/home/demo/puppet-file-rsapi-remove]/ensure: created
Notice: /stage[main]/main/File_rsapi[/home/demo/puppet-file-rsapi-remove]/ensure: undefined 'ensure' from 'present'
Notice: file_rsapi: Deleted: /home/demo/puppet-file-rsapi-remove
Die scheinbare Notwendigkeit einer CanonicalString Maskierung als String lässt es so aussehen, als ob uns etwas fehlt. Wenn die Resource-API nur Datentypen nach der Kanonisierung überprüft, könnten wir get etwas einfacheres als CanonicalString zurückgeben, um einen bereits kanonisierten Wert zu signalisieren.
Die Standardanforderung der Resource-API, alle vorhandenen Ressourcen zurückzugeben, vereinfacht die Entwicklung, wenn man dies ohnehin vorhatte. Die Low-Level-Version hierfür ist oft eine Kombination aus prefetch und instances.
| Kategorien: | Automatisierung HowTos |
|---|---|
| Tags: | Puppet |
Sie müssen den Inhalt von reCAPTCHA laden, um das Formular abzuschicken. Bitte beachten Sie, dass dabei Daten mit Drittanbietern ausgetauscht werden.
Mehr InformationenSie sehen gerade einen Platzhalterinhalt von Brevo. Um auf den eigentlichen Inhalt zuzugreifen, klicken Sie auf die Schaltfläche unten. Bitte beachten Sie, dass dabei Daten an Drittanbieter weitergegeben werden.
Mehr InformationenSie müssen den Inhalt von reCAPTCHA laden, um das Formular abzuschicken. Bitte beachten Sie, dass dabei Daten mit Drittanbietern ausgetauscht werden.
Mehr InformationenSie müssen den Inhalt von Turnstile laden, um das Formular abzuschicken. Bitte beachten Sie, dass dabei Daten mit Drittanbietern ausgetauscht werden.
Mehr InformationenSie müssen den Inhalt von reCAPTCHA laden, um das Formular abzuschicken. Bitte beachten Sie, dass dabei Daten mit Drittanbietern ausgetauscht werden.
Mehr InformationenSie sehen gerade einen Platzhalterinhalt von Turnstile. Um auf den eigentlichen Inhalt zuzugreifen, klicken Sie auf die Schaltfläche unten. Bitte beachten Sie, dass dabei Daten an Drittanbieter weitergegeben werden.
Mehr Informationen