Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New istio config format #1249

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
go.uber.org/zap v1.26.0
golang.org/x/sync v0.6.0
google.golang.org/protobuf v1.32.0
gopkg.in/yaml.v3 v3.0.1
istio.io/api v1.20.2
istio.io/client-go v1.20.2
k8s.io/api v0.28.5
Expand Down Expand Up @@ -83,7 +84,6 @@ require (
google.golang.org/grpc v1.61.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.28.5 // indirect
k8s.io/code-generator v0.28.5 // indirect
k8s.io/gengo v0.0.0-20221011193443-fad74ee6edd9 // indirect
Expand Down
178 changes: 153 additions & 25 deletions pkg/reconciler/ingress/config/istio.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"sort"
"strings"

"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
"knative.dev/pkg/network"
Expand All @@ -38,6 +39,12 @@ const (
// localGatewayKeyPrefix is the prefix of all keys to configure Istio gateways for public & private Ingresses.
localGatewayKeyPrefix = "local-gateway."

// externalGatewaysKey is the configmap key to configure Istio gateways for public Ingresses.
externalGatewaysKey = "external-gateways"

// localGatewaysKey is the configmap key to configure Istio gateways for private Ingresses.
localGatewaysKey = "local-gateways"

// KnativeIngressGateway is the name of the ingress gateway
KnativeIngressGateway = "knative-ingress-gateway"

Expand Down Expand Up @@ -71,14 +78,34 @@ func defaultLocalGateways() []Gateway {
type Gateway struct {
Namespace string
Name string
ServiceURL string
ServiceURL string `yaml:"service"`
}

// QualifiedName returns gateway name in '{namespace}/{name}' format.
func (g Gateway) QualifiedName() string {
return g.Namespace + "/" + g.Name
}

func (g Gateway) Validate() error {
if g.Namespace == "" {
return fmt.Errorf("missing namespace")
}

if g.Name == "" {
return fmt.Errorf("missing name")
}

if g.ServiceURL == "" {
return fmt.Errorf("missing service")
}

if errs := validation.IsDNS1123Subdomain(strings.TrimSuffix(g.ServiceURL, ".")); len(errs) > 0 {
return fmt.Errorf("invalid gateway service format: %v", errs)
}

return nil
}

// Istio contains istio related configuration defined in the
// istio config map.
type Istio struct {
Expand All @@ -89,17 +116,131 @@ type Istio struct {
LocalGateways []Gateway
}

func parseGateways(configMap *corev1.ConfigMap, prefix string) ([]Gateway, error) {
func (i Istio) Validate() error {
for _, gtw := range i.IngressGateways {
if err := gtw.Validate(); err != nil {
return fmt.Errorf("invalid gateway: %w", err)
}
}

for _, gtw := range i.LocalGateways {
if err := gtw.Validate(); err != nil {
return fmt.Errorf("invalid local gateway: %w", err)
}
}

return nil
}

// NewIstioFromConfigMap creates an Istio config from the supplied ConfigMap
func NewIstioFromConfigMap(configMap *corev1.ConfigMap) (*Istio, error) {
ret := &Istio{}
var err error

oldFormatDefined := isOldFormatDefined(configMap)
newFormatDefined := isNewFormatDefined(configMap)

switch {
case newFormatDefined && oldFormatDefined:
return nil, fmt.Errorf(
"invalid configmap: %q or %q can not be defined simultaneously with %q or %q",
localGatewaysKey, externalGatewaysKey, gatewayKeyPrefix, localGatewayKeyPrefix,
)
case newFormatDefined:
ret, err = parseNewFormat(configMap)
if err != nil {
return nil, fmt.Errorf("failed to parse configmap: %w", err)
}
case oldFormatDefined:
ret = parseOldFormat(configMap)
}

defaultValues(ret)

err = ret.Validate()
if err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}

return ret, nil
}

func isNewFormatDefined(configMap *corev1.ConfigMap) bool {
_, hasGateway := configMap.Data[externalGatewaysKey]
_, hasLocalGateway := configMap.Data[localGatewaysKey]

return hasGateway || hasLocalGateway
}

func isOldFormatDefined(configMap *corev1.ConfigMap) bool {
for key := range configMap.Data {
if strings.HasPrefix(key, gatewayKeyPrefix) || strings.HasPrefix(key, localGatewayKeyPrefix) {
return true
}
}

return false
}

func parseNewFormat(configMap *corev1.ConfigMap) (*Istio, error) {
ret := &Istio{}

gatewaysStr, hasGateway := configMap.Data[externalGatewaysKey]

if hasGateway {
gateways, err := parseNewFormatGateways(gatewaysStr)
if err != nil {
return ret, fmt.Errorf("failed to parse %q gateways: %w", externalGatewaysKey, err)
}

ret.IngressGateways = gateways
}

localGatewaysStr, hasLocalGateway := configMap.Data[localGatewaysKey]

if hasLocalGateway {
localGateways, err := parseNewFormatGateways(localGatewaysStr)
if err != nil {
return ret, fmt.Errorf("failed to parse %q gateways: %w", localGatewaysKey, err)
}

ret.LocalGateways = localGateways
}

if len(ret.LocalGateways) > 1 {
return ret, fmt.Errorf("only one local gateway can be defined: current %q value is %q", localGatewaysKey, localGatewaysStr)
}

return ret, nil
}

func parseNewFormatGateways(data string) ([]Gateway, error) {
ret := make([]Gateway, 0)

err := yaml.Unmarshal([]byte(data), &ret)
if err != nil {
return ret, fmt.Errorf("failed to unmarshal: %w", err)
}

return ret, nil
}

func parseOldFormat(configMap *corev1.ConfigMap) *Istio {
return &Istio{
IngressGateways: parseOldFormatGateways(configMap, gatewayKeyPrefix),
LocalGateways: parseOldFormatGateways(configMap, localGatewayKeyPrefix),
}
}

func parseOldFormatGateways(configMap *corev1.ConfigMap, prefix string) []Gateway {
urls := map[string]string{}
gatewayNames := []string{}
for k, v := range configMap.Data {
if !strings.HasPrefix(k, prefix) || k == prefix {
continue
}
gatewayName, serviceURL := k[len(prefix):], v
if errs := validation.IsDNS1123Subdomain(strings.TrimSuffix(serviceURL, ".")); len(errs) > 0 {
return nil, fmt.Errorf("invalid gateway format: %v", errs)
}

gatewayNames = append(gatewayNames, gatewayName)
urls[gatewayName] = serviceURL
}
Expand All @@ -121,28 +262,15 @@ func parseGateways(configMap *corev1.ConfigMap, prefix string) ([]Gateway, error
ServiceURL: urls[gatewayName],
}
}
return gateways, nil
return gateways
}

// NewIstioFromConfigMap creates an Istio config from the supplied ConfigMap
func NewIstioFromConfigMap(configMap *corev1.ConfigMap) (*Istio, error) {
gateways, err := parseGateways(configMap, gatewayKeyPrefix)
if err != nil {
return nil, err
}
if len(gateways) == 0 {
gateways = defaultIngressGateways()
}
localGateways, err := parseGateways(configMap, localGatewayKeyPrefix)
if err != nil {
return nil, err
}
if len(localGateways) == 0 {
localGateways = defaultLocalGateways()
func defaultValues(conf *Istio) {
if len(conf.IngressGateways) == 0 {
conf.IngressGateways = defaultIngressGateways()
}

return &Istio{
IngressGateways: gateways,
LocalGateways: localGateways,
}, nil
if len(conf.LocalGateways) == 0 {
conf.LocalGateways = defaultLocalGateways()
}
}
Loading
Loading