|
Este documento foi traduzido usando tecnologia de tradução automática de máquina. Sempre trabalhamos para apresentar traduções precisas, mas não oferecemos nenhuma garantia em relação à integridade, precisão ou confiabilidade do conteúdo traduzido. Em caso de qualquer discrepância, a versão original em inglês prevalecerá e constituirá o texto official. |
|
Esta é uma documentação não divulgada para Admission Controller 1.34-dev. |
Definindo configurações de política
Primeiramente, você precisa definir a estrutura que contém as configurações de política.
Você faz isso modificando o código no arquivo settings.go (da sua versão local do modelo de política Go).
Você precisa adicionar duas linhas extras à seção import, alterar a estrutura Settings e adicionar a estrutura RegularExpression.
Deve corresponder ao seguinte 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 |
Você está usando o pacote regexp para manipular objetos de expressões regulares e o tipo mapset.Set para armazenar a lista de rótulos negados.
Como regexp.Regexp não manipula desserialização, você precisa definir funções personalizadas para manipular a serialização e desserialização de expressões 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
}
Além disso, você precisa do método UnmarshalJSON para lidar com a desserialização da struct 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
}
Construindo instâncias de Settings
Uma política Admission Controller expõe duas funções diferentes que recebem as configurações de política como entrada:
-
validate: Use esta função quando o objeto Kubernetes requer validação pela política. As configurações são parte de um objetoValidationRequest. -
validate_settings: Chame esta função quando a política for carregada pela primeira vez por Admission Controller. A função recebe as configurações de política como entrada e verifica a validade.
Você precisa criar uma função auxiliar que cria um objeto Settings a partir do payload 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 os O padrão WebAssembly ainda não cobre threads. Veja proposta oficial para mais detalhes. |
Implementando a validação de Settings
Todas as políticas de Admission Controller devem implementar a validação de configurações.
Você faz isso adicionando um método Valid às instâncias 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
}
O método Valid garante que nenhum rótulo "negado" também faça parte do mapa "restrito".
O uso do método Intersect fornecido por mapset.Set simplifica a verificação.
|
A invocação do método |
Finalmente, você precisa da função validateSettings, fornecida pela estrutura, para mudar para parecer assim:
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)))
}
Você pode ver que a função aproveita as funções auxiliares fornecidas pelo SDK de Admission Controller.
Testando o código de configurações
É importante ter uma boa cobertura de testes do código que você escreve.
O código que você está usando, da estrutura, vem com uma série de testes de unidade definidos no arquivo settings_test.go.
Você precisa alterar o conteúdo deste arquivo para refletir o novo comportamento da classe Settings.
Inclua os pacotes Go que você está usando:
import (
"testing"
"encoding/json"
kubewarden_protocol "github.com/kubewarden/policy-sdk-go/protocol"
)
Você pode começar escrevendo um teste unitário que garante que você pode atribuir uma instância de Settings de um 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())
}
}
Em seguida, você precisa de um teste que verifica se uma instância de Settings não é gerada quando o usuário fornece uma expressão regular quebrada:
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")
}
}
Agora, você pode definir um teste que verifica o comportamento do ponto de entrada validate_settings.
Você observa o objeto SettingsValidationResponse retornado pela sua função 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, você escreve mais dois testes para verificar se a função validateSettings rejeita configurações inválidas com as mensagens corretas:
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)
}
}
Agora você pode executar os testes que definiu até agora usando o seguinte comando:
go test -v settings.go settings_test.go
Todos os testes passarão com a seguinte saída:
=== 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
Agora você pode implementar o código de validação real na próxima seção.