Skip to content

Commit

Permalink
dataclients/kubernetes: use data URI scheme to encode metadata
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Yastrebov <alexander.yastrebov@zalando.de>
  • Loading branch information
AlexanderYastrebov committed Jan 12, 2024
1 parent 0c3dd2f commit 43eff37
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 40 deletions.
2 changes: 1 addition & 1 deletion dataclients/kubernetes/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
const (
DefaultLoadBalancerAlgorithm = "roundRobin"
MetadataRouteID = "kube__metadata"
EnableMetadataRoute = false // TODO: flag
EnableMetadataRoute = true // TODO: flag
)

const (
Expand Down
94 changes: 55 additions & 39 deletions dataclients/kubernetes/metadataroute.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package kubernetes

import (
"encoding/base64"
"encoding/json"
"fmt"
"net"
"strings"

log "github.com/sirupsen/logrus"
"github.com/zalando/skipper/eskip"
Expand Down Expand Up @@ -37,16 +38,18 @@ func NewMetadataPostProcessor(options MetadataPostProcessorOptions) routing.Post

func (pp *postProcessor) Do(routes []*routing.Route) []*routing.Route {
var metadata map[string]*kubeRouteMetadataAddress
var err error

for _, r := range routes {
if r.Id == MetadataRouteID {
metadata = parseMetadata(&r.Route)
metadata, err = decodeMetadata(&r.Route)
if err != nil {
log.Errorf("Failed to decode metadata: %v", err)
return routes
}
break
}
}
if len(metadata) == 0 {
log.Errorf("metadata route not found")
return routes
}

endpointRegisty := pp.options.EndpointRegistry

Expand Down Expand Up @@ -94,44 +97,26 @@ func metadataRoute(s *clusterState) *eskip.Route {
}
}

var b strings.Builder
_ = json.NewEncoder(&b).Encode(&metadata)

return &eskip.Route{
Id: MetadataRouteID,
Predicates: []*eskip.Predicate{{
Name: predicates.FalseName,
// Use MetadataRouteID as the first argument to distinct from other False predicates
// that might be added by post-processors.
Args: []any{MetadataRouteID, b.String()},
}},
BackendType: eskip.ShuntBackend,
Id: MetadataRouteID,
Predicates: []*eskip.Predicate{{Name: predicates.FalseName}},
BackendType: eskip.NetworkBackend,
Backend: encodeDataURI(&metadata),
}
}

func parseMetadata(r *eskip.Route) map[string]*kubeRouteMetadataAddress {
for _, p := range r.Predicates {
if p.Name == predicates.FalseName && len(p.Args) == 2 && p.Args[0] == MetadataRouteID {
if data, ok := p.Args[1].(string); ok {
metadata := &kubeRouteMetadata{}
err := json.NewDecoder(strings.NewReader(data)).Decode(metadata)
if err != nil {
log.Errorf("failed to parse metadata route: %v", err)
return nil
}
result := make(map[string]*kubeRouteMetadataAddress)
for i := range metadata.Addresses {
addr := &metadata.Addresses[i]
result[addr.Address] = addr
}
return result
} else {
log.Errorf("failed to parse metadata route")
}
break
}
func decodeMetadata(r *eskip.Route) (map[string]*kubeRouteMetadataAddress, error) {
metadata, err := decodeDataURI(r.Backend)
if err != nil {
return nil, err
}
return nil

result := make(map[string]*kubeRouteMetadataAddress, len(metadata.Addresses))
for i := range metadata.Addresses {
addr := &metadata.Addresses[i]
result[addr.Address] = addr
}
return result, nil
}

func addMetadata(metrics routing.Metrics, metadata map[string]*kubeRouteMetadataAddress, hostPort string) {
Expand All @@ -145,3 +130,34 @@ func addMetadata(metrics routing.Metrics, metadata map[string]*kubeRouteMetadata
}
}
}

const dataUriPrefix = "data:application/json;base64,"

// encodeDataURI encodes metadata into data URI.
// See https://datatracker.ietf.org/doc/html/rfc2397
func encodeDataURI(metadata *kubeRouteMetadata) string {
data, _ := json.Marshal(&metadata)

buf := make([]byte, len(dataUriPrefix)+base64.StdEncoding.EncodedLen(len(data)))

copy(buf, dataUriPrefix)
base64.StdEncoding.Encode(buf[len(dataUriPrefix):], data)

return string(buf)
}

// encodeDataURI encodes metadata into data URI.
// See https://datatracker.ietf.org/doc/html/rfc2397
func decodeDataURI(uri string) (*kubeRouteMetadata, error) {
var metadata kubeRouteMetadata

data, err := base64.StdEncoding.DecodeString(uri[len(dataUriPrefix):])
if err != nil {
return nil, fmt.Errorf("failed to decode base64: %w", err)
}

if err := json.Unmarshal(data, &metadata); err != nil {
return nil, fmt.Errorf("failed to decode json: %w", err)
}
return &metadata, nil
}
3 changes: 3 additions & 0 deletions net/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ func SchemeHost(input string) (string, string, error) {
if u.Scheme == "" {
return "", "", fmt.Errorf(`parse %q: missing scheme`, input)
}
if u.Scheme == "data" {
return u.Scheme, "", nil
}
if u.Host == "" {
return "", "", fmt.Errorf(`parse %q: missing host`, input)
}
Expand Down

0 comments on commit 43eff37

Please sign in to comment.