Dieses Dokument wurde mithilfe automatisierter maschineller Übersetzungstechnologie übersetzt. Wir bemühen uns um korrekte Übersetzungen, übernehmen jedoch keine Gewähr für die Vollständigkeit, Richtigkeit oder Zuverlässigkeit der übersetzten Inhalte. Im Falle von Abweichungen ist die englische Originalversion maßgebend und stellt den verbindlichen Text dar.

Dies ist eine unveröffentlichte Dokumentation für Admission Controller 1.34-dev.

Festlegung von Richtlinieneinstellungen

Zuerst müssen Sie die Struktur definieren, die die Richtlinieneinstellungen enthält.

Sie tun dies, indem Sie den Code in der settings.go-Datei (aus Ihrer lokalen Version der Go-Richtlinienevorlage) ändern. Sie müssen zwei zusätzliche Zeilen zum import-Abschnitt hinzufügen, die Settings-Struktur ändern und die RegularExpression-Struktur hinzufügen.

Es sollte dem folgenden Code entsprechen:

import (
    "encoding/json"
    "fmt"
    "regexp"

    mapset "github.com/deckarep/golang-set/v2"
    kubewarden "github.com/kubewarden/policy-sdk-go"
    kubewarden_protocol "github.com/kubewarden/policy-sdk-go/protocol"
)

type Settings struct {
    DeniedLabels      mapset.Set[string]            `+json:"denied_labels"+`
    ConstrainedLabels map[string]*RegularExpression `+json:"constrained_labels"+`
}

type RegularExpression struct {
    *regexp.Regexp
}

DeniedNames ist in der Settings-Struktur, die in settings.go definiert ist, nicht mehr erforderlich.

Da DeniedNames nicht mehr definiert ist, sollten Sie auch die Funktion IsNameDefined in settings.go löschen. Sie sollten auch die Funktion entfernen, die in settings_test.go, TestIsNameDenied darauf verweist.

Sie verwenden das regexp-Paket, um Objekte regulärer Ausdrücke zu verwalten, und den mapset.Set-Typ, um die Liste der abgelehnten Labels zu speichern.

Da regexp.Regexp die Deserialisierung nicht behandelt, müssen Sie benutzerdefinierte Funktionen definieren, um das Marshaling und Unmarshaling von regulären Ausdrücken zu behandeln:

// UnmarshalText satisfies the encoding.TextMarshaler interface,
// also used by json.Unmarshal.
func (r *RegularExpression) UnmarshalText(text []byte) error {
    nativeRegExp, err := regexp.Compile(string(text))
    if err != nil {
        return err
    }
    r.Regexp = nativeRegExp
    return nil
}

// MarshalText satisfies the encoding.TextMarshaler interface,
// also used by json.Marshal.
func (r *RegularExpression) MarshalText() ([]byte, error) {
    if r.Regexp != nil {
        return []byte(r.Regexp.String()), nil
    }

    return nil, nil
}

Außerdem benötigen Sie die UnmarshalJSON-Methode, um die Deserialisierung der Settings-Struktur zu behandeln:

func (s *Settings) UnmarshalJSON(data []byte) error {
    rawSettings := struct {
        DeniedLabels      []string          `+json:"denied_labels"+`
        ConstrainedLabels map[string]*RegularExpression `+json:"constrained_labels"+`
    }{}

    err := json.Unmarshal(data, &rawSettings)
    if err != nil {
        return err
    }

    s.DeniedLabels = mapset.NewThreadUnsafeSet[string](rawSettings.DeniedLabels...)
    s.ConstrainedLabels = rawSettings.ConstrainedLabels

    return nil
}

Erstellen von Settings-Instanzen

Eine Admission Controller-Richtlinie bietet zwei verschiedene Funktionen, die die Richtlinieneinstellungen als Eingabe erhalten:

  • validate: Verwenden Sie diese Funktion, wenn das Kubernetes-Objekt eine Validierung durch die Richtlinie erfordert. Die Einstellungen sind Teil eines ValidationRequest-Objekts.

  • validate_settings: Rufen Sie diese Funktion auf, wenn die Richtlinie erstmals von Admission Controller geladen wird. Die Funktion erhält die Richtlinieneinstellungen als Eingabe und überprüft die Gültigkeit.

Sie müssen eine Hilfsfunktion erstellen, die ein Settings-Objekt aus dem JSON-Payload erstellt:

func NewSettingsFromValidationReq(validationReq *kubewarden_protocol.ValidationRequest) (Settings, error) {
    settings := Settings{}
    err := json.Unmarshal(validationReq.Settings, &settings)
    if err != nil {
        return Settings{}, err
    }

    return settings, nil
}

Alle mapset.Set-Objekte werden mit der thread-unsicheren Variante erstellt. Der WebAssembly-Code wird in einem einzelnen Thread ausgeführt, daher gibt es keine Nebenläufigkeitsprobleme.

Der WebAssembly-Standard behandelt noch keine Threads. Siehe den offiziellen Vorschlag für weitere Details.

Implementierung der Settings-Validierung

Alle Admission Controller-Richtlinien müssen die Einstellungsvalidierung implementieren.

Sie tun dies, indem Sie eine Valid-Methode zu den Settings-Instanzen hinzufügen:

func (s *Settings) Valid() (bool, error) {
    constrainedLabels := mapset.NewThreadUnsafeSet[string]()

    for label := range s.ConstrainedLabels {
        constrainedLabels.Add(label)
    }

    constrainedAndDenied := constrainedLabels.Intersect(s.DeniedLabels)
    if constrainedAndDenied.Cardinality() != 0 {
        return false,
            fmt.Errorf("These labels cannot be constrained and denied at the same time: %v", constrainedAndDenied)
    }

    return true, nil
}

Die Valid-Methode stellt sicher, dass kein "verweigert"-Label auch Teil der "eingeschränkten" Map ist. Die Verwendung der Intersect-Methode, die von mapset.Set bereitgestellt wird, vereinfacht die Überprüfung.

Der Aufruf der Valid-Methode erfolgt auf einem bereits instanziierten Setting-Objekt. Das bedeutet, dass die Validierung des regulären Ausdrucks, der vom Benutzer bereitgestellt wurde, bereits im Settings-Unmarshaler stattgefunden hat.

Schließlich benötigen Sie die validateSettings-Funktion, die vom Gerüst bereitgestellt wird, um so auszusehen:

func validateSettings(payload []byte) ([]byte, error) {
    settings := Settings{}
    err := json.Unmarshal(payload, &settings)
    if err != nil {
        return kubewarden.RejectSettings(
            kubewarden.Message(fmt.Sprintf("Provided settings are not valid: %v", err)))
    }

    valid, err := settings.Valid()
    if valid {
        return kubewarden.AcceptSettings()
    }

    return kubewarden.RejectSettings(
        kubewarden.Message(fmt.Sprintf("Provided settings are not valid: %v", err)))
}

Sie können sehen, dass die Funktion die Hilfsfunktionen nutzt, die von Admission Controller’s SDK bereitgestellt werden.

Testen des Einstellungs-Codes

Es ist wichtig, eine gute Testabdeckung für den Code zu haben, den Sie schreiben. Der Code, den Sie verwenden, stammt aus dem Gerüst und enthält eine Reihe von Unit-Tests, die in der settings_test.go-Datei definiert sind.

Sie müssen den Inhalt dieser Datei ändern, um das neue Verhalten der Settings-Klasse widerzuspiegeln.

Fügen Sie die Go-Pakete hinzu, die Sie verwenden:

import (
    "testing"

    "encoding/json"

    kubewarden_protocol "github.com/kubewarden/policy-sdk-go/protocol"
)

Sie können damit beginnen, einen Unit-Test zu schreiben, der sicherstellt, dass Sie eine Settings-Instanz von einem ValidationRequest-Objekt zuweisen können:

func TestParseValidSettings(t *testing.T) {
    settingsJSON := []byte(`
        {
            "denied_labels": [ "foo", "bar" ],
            "constrained_labels": {
                    "cost-center": "cc-\\d+"
            }
        }`)

    settings := Settings{}
    err := json.Unmarshal(settingsJSON, &settings)
    if err != nil {
        t.Errorf("Unexpected error %+v", err)
    }

    expected_denied_labels := []string{"foo", "bar"}
    for _, exp := range expected_denied_labels {
        if !settings.DeniedLabels.Contains(exp) {
            t.Errorf("Missing value %s", exp)
        }
    }

    re, found := settings.ConstrainedLabels["cost-center"]
    if !found {
        t.Error("Didn't find the expected constrained label")
    }

    expected_regexp := `+cc-\d++`
    if re.String() != expected_regexp {
        t.Errorf("Expected regexp to be %v - got %v instead",
            expected_regexp, re.String())
    }
}

Als Nächstes benötigen Sie einen Test, der überprüft, dass eine Settings-Instanz nicht generiert wird, wenn der Benutzer einen fehlerhaften regulären Ausdruck bereitstellt:

func TestParseSettingsWithInvalidRegexp(t *testing.T) {
    settingsJSON := []byte(`
        {
            "denied_labels": [ "foo", "bar" ],
            "constrained_labels": {
                    "cost-center": "cc-[a+"
            }
        }`)

    err := json.Unmarshal(settingsJSON, &Settings{})
    if err == nil {
        t.Errorf("Didn't get expected error")
    }
}

Jetzt können Sie einen Test definieren, der das Verhalten des validate_settings-Einstiegspunkts überprüft.

Sie betrachten das SettingsValidationResponse-Objekt, das von Ihrer validateSettings-Funktion zurückgegeben wird:

func TestDetectValidSettings(t *testing.T) {
    settingsJSON := []byte(`
    {
        "denied_labels": [ "foo", "bar" ],
        "constrained_labels": {
            "cost-center": "cc-\\d+"
        }
    }`)

    responsePayload, err := validateSettings(settingsJSON)
    if err != nil {
        t.Errorf("Unexpected error %+v", err)
    }

    var response kubewarden_protocol.SettingsValidationResponse
    if err := json.Unmarshal(responsePayload, &response); err != nil {
        t.Errorf("Unexpected error: %+v", err)
    }

    if !response.Valid {
        t.Errorf("Expected settings to be valid: %s", *response.Message)
    }
}

Schließlich schreiben Sie zwei weitere Tests, um zu überprüfen, ob die validateSettings-Funktion ungültige Einstellungen mit den richtigen Nachrichten ablehnt:

func TestDetectNotValidSettingsDueToBrokenRegexp(t *testing.T) {
    settingsJSON := []byte(`
    {
        "denied_labels": [ "foo", "bar" ],
        "constrained_labels": {
            "cost-center": "cc-[a+"
        }
    }
    `)

    responsePayload, err := validateSettings(settingsJSON)
    if err != nil {
        t.Errorf("Unexpected error %+v", err)
    }

    var response kubewarden_protocol.SettingsValidationResponse
    if err := json.Unmarshal(responsePayload, &response); err != nil {
        t.Errorf("Unexpected error: %+v", err)
    }

    if response.Valid {
        t.Error("Expected settings to not be valid")
    }

    if *response.Message != "Provided settings are not valid: error parsing regexp: missing closing ]: `+[a++`" {
        t.Errorf("Unexpected validation error message: %s", *response.Message)
    }
}

func TestDetectNotValidSettingsDueToConflictingLabels(t *testing.T) {
    settingsJSON := []byte(`
    {
        "denied_labels": [ "foo", "bar", "cost-center" ],
        "constrained_labels": {
            "cost-center": ".*"
        }
    }`)
    responsePayload, err := validateSettings(settingsJSON)
    if err != nil {
        t.Errorf("Unexpected error %+v", err)
    }

    var response kubewarden_protocol.SettingsValidationResponse
    if err := json.Unmarshal(responsePayload, &response); err != nil {
        t.Errorf("Unexpected error: %+v", err)
    }

    if response.Valid {
        t.Error("Expected settings to not be valid")
    }

    if *response.Message != "Provided settings are not valid: These labels cannot be constrained and denied at the same time: Set{cost-center}" {
        t.Errorf("Unexpected validation error message: %s", *response.Message)
    }
}

Jetzt können Sie die Tests ausführen, die Sie bisher definiert haben, indem Sie den folgenden Befehl verwenden:

go test -v settings.go settings_test.go

Alle Tests werden mit der folgenden Ausgabe bestehen:

=== RUN   TestParseValidSettings
--- PASS: TestParseValidSettings (0.00s)
=== RUN   TestParseSettingsWithInvalidRegexp
--- PASS: TestParseSettingsWithInvalidRegexp (0.00s)
=== RUN   TestDetectValidSettings
--- PASS: TestDetectValidSettings (0.00s)
=== RUN   TestDetectNotValidSettingsDueToBrokenRegexp
--- PASS: TestDetectNotValidSettingsDueToBrokenRegexp (0.00s)
=== RUN   TestDetectNotValidSettingsDueToConflictingLabels
--- PASS: TestDetectNotValidSettingsDueToConflictingLabels (0.00s)
PASS
ok      command-line-arguments    0.002s

Sie können jetzt den tatsächlichen Validierungscode im nächsten Abschnitt implementieren.