|
Este documento ha sido traducido utilizando tecnología de traducción automática. Si bien nos esforzamos por proporcionar traducciones precisas, no ofrecemos garantías sobre la integridad, precisión o confiabilidad del contenido traducido. En caso de discrepancia, la versión original en inglés prevalecerá y constituirá el texto autorizado. |
|
Esta es documentación inédita para Admission Controller 1.34-dev. |
Definiendo ajustes de la directiva
Primero, necesitas definir la estructura que contiene los ajustes de la directiva.
Haces esto modificando el código en el archivo settings.go (de tu versión local de la plantilla de directiva de Go).
Necesitas añadir dos líneas extra a la sección import, cambiar la estructura Settings, y añadir la estructura RegularExpression.
Debería coincidir con el siguiente código:
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
}
|
Como |
Estás utilizando el paquete regexp para manejar objetos de expresiones regulares y el tipo mapset.Set para almacenar la lista de etiquetas denegadas.
Dado que regexp.Regexp no maneja la deserialización, necesitas definir funciones personalizadas para manejar la serialización y deserialización de expresiones regulares:
// 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
}
Además, necesitas el método UnmarshalJSON para manejar la deserialización de la estructura Settings:
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
}
Construyendo instancias de Settings
Una directiva de Admission Controller expone dos funciones diferentes que reciben los ajustes de la directiva como entrada:
-
validate: Utiliza esta función cuando el objeto de Kubernetes requiera validación por parte de la directiva. Los ajustes son parte de un objetoValidationRequest. -
validate_settings: Llama a esta función cuando la directiva se cargue por primera vez en Admission Controller. La función recibe los ajustes de la directiva como entrada y verifica la validez.
Necesitas crear una función auxiliar que cree un objeto Settings a partir de la carga útil JSON:
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
}
|
Todos los El estándar de WebAssembly aún no cubre los hilos. Consulta la propuesta oficial para más detalles. |
Implementando la validación de Settings
Todas las directivas de Admission Controller deben implementar la validación de ajustes.
Haces esto añadiendo un método de Valid a las instancias de Settings:
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
}
El método de Valid asegura que ninguna etiqueta "denegada" sea también parte del mapa "restringido".
El uso del método Intersect proporcionado por mapset.Set simplifica la verificación.
|
La invocación del método |
Finalmente, necesitas la función validateSettings, proporcionada por el andamiaje, para que se vea así:
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)))
}
Puedes ver que la función aprovecha las funciones auxiliares proporcionadas por Admission Controller del SDK.
Probando el código de ajustes
Es importante tener una buena cobertura de pruebas del código que escribes.
El código que estás utilizando, del andamiaje, viene con una serie de pruebas unitarias definidas en el archivo settings_test.go.
Tienes que cambiar el contenido de este archivo para reflejar el nuevo comportamiento de la clase Settings.
Incluye los paquetes de Go que estás utilizando:
import (
"testing"
"encoding/json"
kubewarden_protocol "github.com/kubewarden/policy-sdk-go/protocol"
)
Puedes empezar escribiendo una prueba unitaria que asegure que puedes asignar una instancia de Settings desde un objeto ValidationRequest:
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())
}
}
A continuación, necesitas una prueba que verifique que no se genera una instancia de Settings cuando el usuario proporciona una expresión regular rota:
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")
}
}
Ahora, puedes definir una prueba que compruebe el comportamiento del punto de entrada validate_settings.
Miras el objeto SettingsValidationResponse devuelto por tu función validateSettings:
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)
}
}
Finalmente, escribes dos pruebas más para comprobar que la función validateSettings rechaza ajustes inválidos con los mensajes correctos:
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)
}
}
Ahora puedes ejecutar las pruebas que has definido hasta ahora utilizando el siguiente comando:
go test -v settings.go settings_test.go
Todas las pruebas pasarán con la siguiente salida:
=== 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
Ahora puedes implementar el código de validación real en la siguiente sección.