/
annotation.go
159 lines (126 loc) · 4.9 KB
/
annotation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
* Copyright 2016, Z Lab Corporation. All rights reserved.
* Copyright 2017, nghttpx Ingress controller contributors
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package controller
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/klog/v2"
"github.com/zlabjp/nghttpx-ingress-lb/pkg/nghttpx"
)
const (
// backendConfigKey is a key to annotation for extra backend configuration.
backendConfigKey = "ingress.zlab.co.jp/backend-config"
// defaultBackendConfigKey is a key to annotation for default backend configuration which applies to all entries in a Ingress
// resource.
defaultBackendConfigKey = "ingress.zlab.co.jp/default-backend-config"
// pathConfigKey is a key to annotation for extra path configuration.
pathConfigKey = "ingress.zlab.co.jp/path-config"
// defaultPathConfigKey is a key to annotation for default path configuration which applies to all entries in a Ingress resource.
defaultPathConfigKey = "ingress.zlab.co.jp/default-path-config"
)
type ingressAnnotation map[string]string
// NewBackendConfigMapper returns nghttpx.BackendConfigMapper by reading default-backend-config and backend-config annotations. This
// function applies default-backend-config to backend-config if it exists. If invalid value is found, this function replaces them with the
// default value (e.g., nghttpx.ProtocolH1 for proto).
func (ia ingressAnnotation) NewBackendConfigMapper(ctx context.Context) *nghttpx.BackendConfigMapper {
log := klog.FromContext(ctx)
data := ia[backendConfigKey]
// the first key specifies service name, and secondary key specifies port name.
var config nghttpx.BackendConfigMapping
if data != "" {
if err := unmarshal(ctx, []byte(data), &config); err != nil {
log.Error(err, "Unexpected error while reading annotation", "annotation", backendConfigKey)
return nghttpx.NewBackendConfigMapper(nil, nil)
}
}
for _, v := range config {
for _, vv := range v {
nghttpx.FixupBackendConfig(ctx, vv)
}
}
data = ia[defaultBackendConfigKey]
if data == "" {
log.V(4).Info("Annotation not found", "annoation", defaultBackendConfigKey)
return nghttpx.NewBackendConfigMapper(nil, config)
}
var defaultConfig nghttpx.BackendConfig
if err := unmarshal(ctx, []byte(data), &defaultConfig); err != nil {
log.Error(err, "Unexpected error while reading annotation", "annotation", defaultBackendConfigKey)
return nghttpx.NewBackendConfigMapper(nil, nil)
}
nghttpx.FixupBackendConfig(ctx, &defaultConfig)
for _, v := range config {
for _, vv := range v {
nghttpx.ApplyDefaultBackendConfig(ctx, vv, &defaultConfig)
}
}
return nghttpx.NewBackendConfigMapper(&defaultConfig, config)
}
// NewPathConfigMapper returns nghttpx.PathConfigMapper by reading default-path-config and path-config annotation. This function applies
// default-path-config to path-config if a value is missing.
func (ia ingressAnnotation) NewPathConfigMapper(ctx context.Context) *nghttpx.PathConfigMapper {
log := klog.FromContext(ctx)
data := ia[pathConfigKey]
var config nghttpx.PathConfigMapping
if data != "" {
if err := unmarshal(ctx, []byte(data), &config); err != nil {
log.Error(err, "Unexpected error while reading annotation", "annotation", pathConfigKey)
return nghttpx.NewPathConfigMapper(nil, nil)
}
}
config = normalizePathKey(config)
for _, v := range config {
nghttpx.FixupPathConfig(ctx, v)
}
data = ia[defaultPathConfigKey]
if data == "" {
log.V(4).Info("Annotation not found", "annotation", defaultPathConfigKey)
return nghttpx.NewPathConfigMapper(nil, config)
}
var defaultConfig nghttpx.PathConfig
if err := unmarshal(ctx, []byte(data), &defaultConfig); err != nil {
log.Error(err, "Unexpected error while reading annotation", "annotation", defaultPathConfigKey)
return nghttpx.NewPathConfigMapper(nil, nil)
}
nghttpx.FixupPathConfig(ctx, &defaultConfig)
for _, v := range config {
nghttpx.ApplyDefaultPathConfig(ctx, v, &defaultConfig)
}
return nghttpx.NewPathConfigMapper(&defaultConfig, config)
}
// normalizePathKey appends "/" if key does not contain "/".
func normalizePathKey(src map[string]*nghttpx.PathConfig) map[string]*nghttpx.PathConfig {
if len(src) == 0 {
return src
}
dst := make(map[string]*nghttpx.PathConfig, len(src))
for k, v := range src {
if !strings.Contains(k, "/") {
dst[k+"/"] = v
} else {
dst[k] = v
}
}
return dst
}
// unmarshal deserializes data into dest. This function first tries YAML and then JSON.
func unmarshal(ctx context.Context, data []byte, dest interface{}) error {
log := klog.FromContext(ctx)
err := yaml.Unmarshal(data, dest)
if err == nil {
return nil
}
log.Error(err, "Unable to unmarshal YAML string; fall back to JSON")
if err := json.Unmarshal(data, dest); err != nil {
return fmt.Errorf("unable to unmarshal JSON string: %w", err)
}
return nil
}