Refactor annotations

This commit is contained in:
Manuel de Brito Fontes 2017-11-07 13:36:51 -03:00
parent f215828b1b
commit fb33c58d18
33 changed files with 370 additions and 401 deletions

View file

@ -14,12 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package annotations
import (
"github.com/golang/glog"
"github.com/imdario/mergo"
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/alias"
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
@ -48,51 +51,89 @@ import (
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
type extractorConfig interface {
// DeniedKeyName name of the key that contains the reason to deny a location
const DeniedKeyName = "Denied"
type config interface {
resolver.AuthCertificate
resolver.DefaultBackend
resolver.Secret
resolver.Service
}
type annotationExtractor struct {
// Ingress defines the valid annotations present in one NGINX Ingress rule
type Ingress struct {
metav1.ObjectMeta
Alias string
BasicDigestAuth auth.Config
CertificateAuth authtls.Config
ClientBodyBufferSize string
ConfigurationSnippet string
CorsConfig cors.Config
DefaultBackend string
ExternalAuth authreq.Config
HealthCheck healthcheck.Config
Proxy proxy.Config
RateLimit ratelimit.Config
Redirect redirect.Config
Rewrite rewrite.Config
SecureUpstream secureupstream.Config
ServerSnippet string
ServiceUpstream bool
SessionAffinity sessionaffinity.Config
SSLPassthrough bool
UsePortInRedirects bool
UpstreamHashBy string
UpstreamVhost string
VtsFilterKey string
Whitelist ipwhitelist.SourceRange
}
// Extractor defines the annotation parsers to be used in the extraction of annotations
type Extractor struct {
secretResolver resolver.Secret
annotations map[string]parser.IngressAnnotation
}
func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
return annotationExtractor{
// NewAnnotationExtractor creates a new annotations extractor
func NewAnnotationExtractor(cfg config) Extractor {
return Extractor{
cfg,
map[string]parser.IngressAnnotation{
"Alias": alias.NewParser(),
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
"ExternalAuth": authreq.NewParser(),
"CertificateAuth": authtls.NewParser(cfg),
"ClientBodyBufferSize": clientbodybuffersize.NewParser(),
"ConfigurationSnippet": snippet.NewParser(),
"CorsConfig": cors.NewParser(),
"DefaultBackend": defaultbackend.NewParser(cfg),
"ExternalAuth": authreq.NewParser(),
"HealthCheck": healthcheck.NewParser(cfg),
"Whitelist": ipwhitelist.NewParser(cfg),
"UsePortInRedirects": portinredirect.NewParser(cfg),
"Proxy": proxy.NewParser(cfg),
"RateLimit": ratelimit.NewParser(cfg),
"Redirect": redirect.NewParser(),
"Rewrite": rewrite.NewParser(cfg),
"SecureUpstream": secureupstream.NewParser(cfg),
"ServerSnippet": serversnippet.NewParser(),
"ServiceUpstream": serviceupstream.NewParser(),
"SessionAffinity": sessionaffinity.NewParser(),
"SSLPassthrough": sslpassthrough.NewParser(),
"ConfigurationSnippet": snippet.NewParser(),
"Alias": alias.NewParser(),
"ClientBodyBufferSize": clientbodybuffersize.NewParser(),
"DefaultBackend": defaultbackend.NewParser(cfg),
"UsePortInRedirects": portinredirect.NewParser(cfg),
"UpstreamHashBy": upstreamhashby.NewParser(),
"UpstreamVhost": upstreamvhost.NewParser(),
"VtsFilterKey": vtsfilterkey.NewParser(),
"ServerSnippet": serversnippet.NewParser(),
"Whitelist": ipwhitelist.NewParser(cfg),
},
}
}
func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interface{} {
anns := make(map[string]interface{})
// Extract extracts the annotations from an Ingress
func (e Extractor) Extract(ing *extensions.Ingress) *Ingress {
pia := &Ingress{
ObjectMeta: ing.ObjectMeta,
}
data := make(map[string]interface{})
for name, annotationParser := range e.annotations {
val, err := annotationParser.Parse(ing)
glog.V(5).Infof("annotation %v in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), val)
@ -105,9 +146,9 @@ func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interf
continue
}
_, alreadyDenied := anns[DeniedKeyName]
_, alreadyDenied := data[DeniedKeyName]
if !alreadyDenied {
anns[DeniedKeyName] = err
data[DeniedKeyName] = err
glog.Errorf("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
continue
}
@ -116,90 +157,14 @@ func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interf
}
if val != nil {
anns[name] = val
data[name] = val
}
}
return anns
}
const (
secureUpstream = "SecureUpstream"
healthCheck = "HealthCheck"
sslPassthrough = "SSLPassthrough"
sessionAffinity = "SessionAffinity"
serviceUpstream = "ServiceUpstream"
serverAlias = "Alias"
corsConfig = "CorsConfig"
clientBodyBufferSize = "ClientBodyBufferSize"
certificateAuth = "CertificateAuth"
serverSnippet = "ServerSnippet"
upstreamHashBy = "UpstreamHashBy"
)
func (e *annotationExtractor) ServiceUpstream(ing *extensions.Ingress) bool {
val, _ := e.annotations[serviceUpstream].Parse(ing)
return val.(bool)
}
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) *secureupstream.Secure {
val, err := e.annotations[secureUpstream].Parse(ing)
err := mergo.Map(pia, data)
if err != nil {
glog.Errorf("error parsing secure upstream: %v", err)
}
secure := val.(*secureupstream.Secure)
return secure
}
func (e *annotationExtractor) HealthCheck(ing *extensions.Ingress) *healthcheck.Upstream {
val, _ := e.annotations[healthCheck].Parse(ing)
return val.(*healthcheck.Upstream)
}
func (e *annotationExtractor) SSLPassthrough(ing *extensions.Ingress) bool {
val, _ := e.annotations[sslPassthrough].Parse(ing)
return val.(bool)
}
func (e *annotationExtractor) Alias(ing *extensions.Ingress) string {
val, _ := e.annotations[serverAlias].Parse(ing)
return val.(string)
}
func (e *annotationExtractor) ClientBodyBufferSize(ing *extensions.Ingress) string {
val, _ := e.annotations[clientBodyBufferSize].Parse(ing)
return val.(string)
}
func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig {
val, _ := e.annotations[sessionAffinity].Parse(ing)
return val.(*sessionaffinity.AffinityConfig)
}
func (e *annotationExtractor) Cors(ing *extensions.Ingress) *cors.CorsConfig {
val, _ := e.annotations[corsConfig].Parse(ing)
return val.(*cors.CorsConfig)
}
func (e *annotationExtractor) CertificateAuth(ing *extensions.Ingress) *authtls.AuthSSLConfig {
val, err := e.annotations[certificateAuth].Parse(ing)
if errors.IsMissingAnnotations(err) {
return nil
glog.Errorf("unexpected error merging extracted annotations: %v", err)
}
if err != nil {
glog.Errorf("error parsing certificate auth: %v", err)
}
secure := val.(*authtls.AuthSSLConfig)
return secure
}
func (e *annotationExtractor) ServerSnippet(ing *extensions.Ingress) string {
val, _ := e.annotations[serverSnippet].Parse(ing)
return val.(string)
}
func (e *annotationExtractor) UpstreamHashBy(ing *extensions.Ingress) string {
val, _ := e.annotations[upstreamHashBy].Parse(ing)
return val.(string)
return pia
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package annotations
import (
"testing"
@ -75,20 +75,6 @@ func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error)
return nil, nil
}
func TestAnnotationExtractor(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ing := buildIngress()
m := ec.Extract(ing)
// the map at least should contains HealthCheck and Proxy information (defaults)
if _, ok := m["HealthCheck"]; !ok {
t.Error("expected HealthCheck annotation")
}
if _, ok := m["Proxy"]; !ok {
t.Error("expected Proxy annotation")
}
}
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
@ -125,7 +111,7 @@ func buildIngress() *extensions.Ingress {
}
func TestSecureUpstream(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
@ -141,7 +127,7 @@ func TestSecureUpstream(t *testing.T) {
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.SecureUpstream(ing)
r := ec.Extract(ing).SecureUpstream
if r.Secure != foo.er {
t.Errorf("Returned %v but expected %v", r, foo.er)
}
@ -149,7 +135,7 @@ func TestSecureUpstream(t *testing.T) {
}
func TestSecureVerifyCACert(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{
ec := NewAnnotationExtractor(mockCfg{
MockSecrets: map[string]*apiv1.Secret{
"default/secure-verify-ca": {
ObjectMeta: metav1.ObjectMeta{
@ -176,15 +162,16 @@ func TestSecureVerifyCACert(t *testing.T) {
for _, ann := range anns {
ing := buildIngress()
ing.SetAnnotations(ann.annotations)
res := ec.SecureUpstream(ing)
if (res.CACert.CAFileName != "") != ann.exists {
res := ec.Extract(ing).SecureUpstream
if (res != nil && res.CACert.CAFileName != "") != ann.exists {
t.Errorf("Expected exists was %v on iteration %v", ann.exists, ann.it)
}
}
}
func TestHealthCheck(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
@ -201,7 +188,7 @@ func TestHealthCheck(t *testing.T) {
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.HealthCheck(ing)
r := ec.Extract(ing).HealthCheck
if r == nil {
t.Errorf("Returned nil but expected a healthcheck.Upstream")
continue
@ -218,7 +205,7 @@ func TestHealthCheck(t *testing.T) {
}
func TestSSLPassthrough(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
@ -234,7 +221,7 @@ func TestSSLPassthrough(t *testing.T) {
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.SSLPassthrough(ing)
r := ec.Extract(ing).SSLPassthrough
if r != foo.er {
t.Errorf("Returned %v but expected %v", r, foo.er)
}
@ -242,7 +229,7 @@ func TestSSLPassthrough(t *testing.T) {
}
func TestUpstreamHashBy(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
@ -258,7 +245,7 @@ func TestUpstreamHashBy(t *testing.T) {
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.UpstreamHashBy(ing)
r := ec.Extract(ing).UpstreamHashBy
if r != foo.er {
t.Errorf("Returned %v but expected %v", r, foo.er)
}
@ -266,7 +253,7 @@ func TestUpstreamHashBy(t *testing.T) {
}
func TestAffinitySession(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
@ -284,25 +271,25 @@ func TestAffinitySession(t *testing.T) {
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.SessionAffinity(ing)
r := ec.Extract(ing).SessionAffinity
t.Logf("Testing pass %v %v %v", foo.affinitytype, foo.hash, foo.name)
if r == nil {
t.Errorf("Returned nil but expected a SessionAffinity.AffinityConfig")
continue
}
if r.CookieConfig.Hash != foo.hash {
t.Errorf("Returned %v but expected %v for Hash", r.CookieConfig.Hash, foo.hash)
if r.Cookie.Hash != foo.hash {
t.Errorf("Returned %v but expected %v for Hash", r.Cookie.Hash, foo.hash)
}
if r.CookieConfig.Name != foo.name {
t.Errorf("Returned %v but expected %v for Name", r.CookieConfig.Name, foo.name)
if r.Cookie.Name != foo.name {
t.Errorf("Returned %v but expected %v for Name", r.Cookie.Name, foo.name)
}
}
}
func TestCors(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
@ -322,7 +309,7 @@ func TestCors(t *testing.T) {
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.Cors(ing)
r := ec.Extract(ing).CorsConfig
t.Logf("Testing pass %v %v %v %v %v", foo.corsenabled, foo.methods, foo.headers, foo.origin, foo.credentials)
if r == nil {
t.Errorf("Returned nil but expected a Cors.CorsConfig")
@ -351,3 +338,48 @@ func TestCors(t *testing.T) {
}
}
/*
func TestMergeLocationAnnotations(t *testing.T) {
// initial parameters
keys := []string{"BasicDigestAuth", "CorsConfig", "ExternalAuth", "RateLimit", "Redirect", "Rewrite", "Whitelist", "Proxy", "UsePortInRedirects"}
loc := ingress.Location{}
annotations := &Ingress{
BasicDigestAuth: &auth.Config{},
CorsConfig: &cors.Config{},
ExternalAuth: &authreq.Config{},
RateLimit: &ratelimit.Config{},
Redirect: &redirect.Config{},
Rewrite: &rewrite.Config{},
Whitelist: &ipwhitelist.SourceRange{},
Proxy: &proxy.Config{},
UsePortInRedirects: true,
}
// create test table
type fooMergeLocationAnnotationsStruct struct {
fName string
er interface{}
}
fooTests := []fooMergeLocationAnnotationsStruct{}
for name, value := range keys {
fva := fooMergeLocationAnnotationsStruct{name, value}
fooTests = append(fooTests, fva)
}
// execute test
MergeWithLocation(&loc, annotations)
// check result
for _, foo := range fooTests {
fv := reflect.ValueOf(loc).FieldByName(foo.fName).Interface()
if !reflect.DeepEqual(fv, foo.er) {
t.Errorf("Returned %v but expected %v for the field %s", fv, foo.er, foo.fName)
}
}
if _, ok := annotations[DeniedKeyName]; ok {
t.Errorf("%s should be removed after mergeLocationAnnotations", DeniedKeyName)
}
}
*/

View file

@ -46,8 +46,8 @@ var (
AuthDirectory = "/etc/ingress-controller/auth"
)
// BasicDigest returns authentication configuration for an Ingress rule
type BasicDigest struct {
// Config returns authentication configuration for an Ingress rule
type Config struct {
Type string `json:"type"`
Realm string `json:"realm"`
File string `json:"file"`
@ -55,8 +55,8 @@ type BasicDigest struct {
FileSHA string `json:"fileSha"`
}
// Equal tests for equality between two BasicDigest types
func (bd1 *BasicDigest) Equal(bd2 *BasicDigest) bool {
// Equal tests for equality between two Config types
func (bd1 *Config) Equal(bd2 *Config) bool {
if bd1 == bd2 {
return true
}
@ -140,7 +140,7 @@ func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
return nil, err
}
return &BasicDigest{
return &Config{
Type: at,
Realm: realm,
File: passFile,

View file

@ -109,7 +109,7 @@ func TestIngressAuth(t *testing.T) {
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
auth, ok := i.(*BasicDigest)
auth, ok := i.(*Config)
if !ok {
t.Errorf("expected a BasicDigest type")
}

View file

@ -36,7 +36,7 @@ const (
)
// External returns external authentication configuration for an Ingress rule
type External struct {
type Config struct {
URL string `json:"url"`
// Host contains the hostname defined in the URL
Host string `json:"host"`
@ -45,8 +45,8 @@ type External struct {
ResponseHeaders []string `json:"responseHeaders,omitEmpty"`
}
// Equal tests for equality between two External types
func (e1 *External) Equal(e2 *External) bool {
// Equal tests for equality between two Config types
func (e1 *Config) Equal(e2 *Config) bool {
if e1 == e2 {
return true
}
@ -116,7 +116,7 @@ func NewParser() parser.IngressAnnotation {
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to use an external URL as source for authentication
// rule used to use an Config URL as source for authentication
func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
str, err := parser.GetStringAnnotation(authURL, ing)
if err != nil {
@ -165,7 +165,7 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
}
}
return &External{
return &Config{
URL: str,
Host: ur.Hostname(),
SigninURL: signin,

View file

@ -97,7 +97,7 @@ func TestAnnotations(t *testing.T) {
}
continue
}
u, ok := i.(*External)
u, ok := i.(*Config)
if !ok {
t.Errorf("%v: expected an External type", test.title)
}
@ -149,7 +149,7 @@ func TestHeaderAnnotations(t *testing.T) {
}
t.Log(i)
u, ok := i.(*External)
u, ok := i.(*Config)
if !ok {
t.Errorf("%v: expected an External type", test.title)
continue

View file

@ -20,11 +20,12 @@ import (
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
"regexp"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
"k8s.io/ingress-nginx/pkg/k8s"
"regexp"
)
const (
@ -41,17 +42,17 @@ var (
authVerifyClientRegex = regexp.MustCompile(`on|off|optional|optional_no_ca`)
)
// AuthSSLConfig contains the AuthSSLCert used for muthual autentication
// Config contains the AuthSSLCert used for muthual autentication
// and the configured ValidationDepth
type AuthSSLConfig struct {
type Config struct {
resolver.AuthSSLCert
VerifyClient string `json:"verify_client"`
ValidationDepth int `json:"validationDepth"`
ErrorPage string `json:"errorPage"`
}
// Equal tests for equality between two AuthSSLConfig types
func (assl1 *AuthSSLConfig) Equal(assl2 *AuthSSLConfig) bool {
// Equal tests for equality between two Config types
func (assl1 *Config) Equal(assl2 *Config) bool {
if assl1 == assl2 {
return true
}
@ -88,16 +89,16 @@ func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing)
if err != nil {
return &AuthSSLConfig{}, err
return &Config{}, err
}
if tlsauthsecret == "" {
return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
return &Config{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
}
_, _, err = k8s.ParseNameNS(tlsauthsecret)
if err != nil {
return &AuthSSLConfig{}, ing_errors.NewLocationDenied(err.Error())
return &Config{}, ing_errors.NewLocationDenied(err.Error())
}
tlsVerifyClient, err := parser.GetStringAnnotation(annotationAuthVerifyClient, ing)
@ -112,7 +113,7 @@ func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
authCert, err := a.certResolver.GetAuthCertificate(tlsauthsecret)
if err != nil {
return &AuthSSLConfig{}, ing_errors.LocationDenied{
return &Config{}, ing_errors.LocationDenied{
Reason: errors.Wrap(err, "error obtaining certificate"),
}
}
@ -122,7 +123,7 @@ func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
errorpage = ""
}
return &AuthSSLConfig{
return &Config{
AuthSSLCert: *authCert,
VerifyClient: tlsVerifyClient,
ValidationDepth: tlsdepth,

View file

@ -51,8 +51,8 @@ var (
type cors struct {
}
// CorsConfig contains the Cors configuration to be used in the Ingress
type CorsConfig struct {
// Config contains the Cors configuration to be used in the Ingress
type Config struct {
CorsEnabled bool `json:"corsEnabled"`
CorsAllowOrigin string `json:"corsAllowOrigin"`
CorsAllowMethods string `json:"corsAllowMethods"`
@ -66,7 +66,7 @@ func NewParser() parser.IngressAnnotation {
}
// Equal tests for equality between two External types
func (c1 *CorsConfig) Equal(c2 *CorsConfig) bool {
func (c1 *Config) Equal(c2 *Config) bool {
if c1 == c2 {
return true
}
@ -120,7 +120,7 @@ func (a cors) Parse(ing *extensions.Ingress) (interface{}, error) {
corsallowcredentials = true
}
return &CorsConfig{
return &Config{
CorsEnabled: corsenabled,
CorsAllowOrigin: corsalloworigin,
CorsAllowHeaders: corsallowheaders,

View file

@ -72,7 +72,7 @@ func TestIngressCorsConfig(t *testing.T) {
ing.SetAnnotations(data)
corst, _ := NewParser().Parse(ing)
nginxCors, ok := corst.(*CorsConfig)
nginxCors, ok := corst.(*Config)
if !ok {
t.Errorf("expected a Config type")
}

View file

@ -28,9 +28,9 @@ const (
upsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
)
// Upstream returns the URL and method to use check the status of
// Config returns the URL and method to use check the status of
// the upstream server/s
type Upstream struct {
type Config struct {
MaxFails int `json:"maxFails"`
FailTimeout int `json:"failTimeout"`
}
@ -49,7 +49,7 @@ func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) {
defBackend := a.backendResolver.GetDefaultBackend()
if ing.GetAnnotations() == nil {
return &Upstream{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil
return &Config{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil
}
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
@ -62,5 +62,5 @@ func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) {
ft = defBackend.UpstreamFailTimeout
}
return &Upstream{mf, ft}, nil
return &Config{mf, ft}, nil
}

View file

@ -77,7 +77,7 @@ func TestIngressHealthCheck(t *testing.T) {
ing.SetAnnotations(data)
hzi, _ := NewParser(mockBackend{}).Parse(ing)
nginxHz, ok := hzi.(*Upstream)
nginxHz, ok := hzi.(*Config)
if !ok {
t.Errorf("expected a Upstream type")
}

View file

@ -17,6 +17,7 @@ limitations under the License.
package portinredirect
import (
"fmt"
"testing"
api "k8s.io/api/core/v1"
@ -24,8 +25,6 @@ import (
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"fmt"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
)

View file

@ -36,8 +36,8 @@ const (
requestBuffering = "ingress.kubernetes.io/proxy-request-buffering"
)
// Configuration returns the proxy timeout to use in the upstream server/s
type Configuration struct {
// Config returns the proxy timeout to use in the upstream server/s
type Config struct {
BodySize string `json:"bodySize"`
ConnectTimeout int `json:"connectTimeout"`
SendTimeout int `json:"sendTimeout"`
@ -51,7 +51,7 @@ type Configuration struct {
}
// Equal tests for equality between two Configuration types
func (l1 *Configuration) Equal(l2 *Configuration) bool {
func (l1 *Config) Equal(l2 *Config) bool {
if l1 == l2 {
return true
}
@ -156,5 +156,5 @@ func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
rb = defBackend.ProxyRequestBuffering
}
return &Configuration{bs, ct, st, rt, bufs, cd, cp, nu, pp, rb}, nil
return &Config{bs, ct, st, rt, bufs, cd, cp, nu, pp, rb}, nil
}

View file

@ -97,9 +97,9 @@ func TestProxy(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error parsing a valid")
}
p, ok := i.(*Configuration)
p, ok := i.(*Config)
if !ok {
t.Fatalf("expected a Configuration type")
t.Fatalf("expected a Config type")
}
if p.ConnectTimeout != 1 {
t.Errorf("expected 1 as connect-timeout but returned %v", p.ConnectTimeout)
@ -137,9 +137,9 @@ func TestProxyWithNoAnnotation(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error parsing a valid")
}
p, ok := i.(*Configuration)
p, ok := i.(*Config)
if !ok {
t.Fatalf("expected a Configuration type")
t.Fatalf("expected a Config type")
}
if p.ConnectTimeout != 10 {
t.Errorf("expected 10 as connect-timeout but returned %v", p.ConnectTimeout)

View file

@ -45,11 +45,11 @@ const (
defSharedSize = 5
)
// RateLimit returns rate limit configuration for an Ingress rule limiting the
// Config returns rate limit configuration for an Ingress rule limiting the
// number of connections per IP address and/or connections per second.
// If you both annotations are specified in a single Ingress rule, RPS limits
// takes precedence
type RateLimit struct {
type Config struct {
// Connections indicates a limit with the number of connections per IP address
Connections Zone `json:"connections"`
// RPS indicates a limit with the number of connections per second
@ -69,7 +69,7 @@ type RateLimit struct {
}
// Equal tests for equality between two RateLimit types
func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool {
func (rt1 *Config) Equal(rt2 *Config) bool {
if rt1 == rt2 {
return true
}
@ -185,7 +185,7 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
}
if rpm == 0 && rps == 0 && conn == 0 {
return &RateLimit{
return &Config{
Connections: Zone{},
RPS: Zone{},
RPM: Zone{},
@ -196,7 +196,7 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
zoneName := fmt.Sprintf("%v_%v", ing.GetNamespace(), ing.GetName())
return &RateLimit{
return &Config{
Connections: Zone{
Name: fmt.Sprintf("%v_conn", zoneName),
Limit: conn,

View file

@ -107,7 +107,7 @@ func TestBadRateLimiting(t *testing.T) {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
rateLimit, ok := i.(*RateLimit)
rateLimit, ok := i.(*Config)
if !ok {
t.Errorf("expected a RateLimit type")
}

View file

@ -33,8 +33,8 @@ const (
www = "ingress.kubernetes.io/from-to-www-redirect"
)
// Redirect returns the redirect configuration for an Ingress rule
type Redirect struct {
// Config returns the redirect configuration for an Ingress rule
type Config struct {
URL string `json:"url"`
Code int `json:"code"`
FromToWWW bool `json:"fromToWWW"`
@ -64,7 +64,7 @@ func (a redirect) Parse(ing *extensions.Ingress) (interface{}, error) {
return nil, err
}
return &Redirect{
return &Config{
URL: tr,
Code: http.StatusFound,
FromToWWW: r3w,
@ -81,7 +81,7 @@ func (a redirect) Parse(ing *extensions.Ingress) (interface{}, error) {
return nil, err
}
return &Redirect{
return &Config{
URL: pr,
Code: http.StatusMovedPermanently,
FromToWWW: r3w,
@ -89,7 +89,7 @@ func (a redirect) Parse(ing *extensions.Ingress) (interface{}, error) {
}
if r3w {
return &Redirect{
return &Config{
FromToWWW: r3w,
}, nil
}
@ -98,7 +98,7 @@ func (a redirect) Parse(ing *extensions.Ingress) (interface{}, error) {
}
// Equal tests for equality between two Redirect types
func (r1 *Redirect) Equal(r2 *Redirect) bool {
func (r1 *Config) Equal(r2 *Config) bool {
if r1 == r2 {
return true
}

View file

@ -32,8 +32,8 @@ const (
appRoot = "ingress.kubernetes.io/app-root"
)
// Redirect describes the per location redirect config
type Redirect struct {
// Config describes the per location redirect config
type Config struct {
// Target URI where the traffic must be redirected
Target string `json:"target"`
// AddBaseURL indicates if is required to add a base tag in the head
@ -50,7 +50,7 @@ type Redirect struct {
}
// Equal tests for equality between two Redirect types
func (r1 *Redirect) Equal(r2 *Redirect) bool {
func (r1 *Config) Equal(r2 *Config) bool {
if r1 == r2 {
return true
}
@ -103,7 +103,7 @@ func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
bus, _ := parser.GetStringAnnotation(baseURLScheme, ing)
ar, _ := parser.GetStringAnnotation(appRoot, ing)
return &Redirect{
return &Config{
Target: rt,
AddBaseURL: abu,
BaseURLScheme: bus,

View file

@ -93,7 +93,7 @@ func TestRedirect(t *testing.T) {
if err != nil {
t.Errorf("Unexpected error with ingress: %v", err)
}
redirect, ok := i.(*Redirect)
redirect, ok := i.(*Config)
if !ok {
t.Errorf("expected a Redirect type")
}
@ -110,7 +110,7 @@ func TestSSLRedirect(t *testing.T) {
ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{true}).Parse(ing)
redirect, ok := i.(*Redirect)
redirect, ok := i.(*Config)
if !ok {
t.Errorf("expected a Redirect type")
}
@ -122,7 +122,7 @@ func TestSSLRedirect(t *testing.T) {
ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{false}).Parse(ing)
redirect, ok = i.(*Redirect)
redirect, ok = i.(*Config)
if !ok {
t.Errorf("expected a Redirect type")
}
@ -139,7 +139,7 @@ func TestForceSSLRedirect(t *testing.T) {
ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{true}).Parse(ing)
redirect, ok := i.(*Redirect)
redirect, ok := i.(*Config)
if !ok {
t.Errorf("expected a Redirect type")
}
@ -151,7 +151,7 @@ func TestForceSSLRedirect(t *testing.T) {
ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{false}).Parse(ing)
redirect, ok = i.(*Redirect)
redirect, ok = i.(*Config)
if !ok {
t.Errorf("expected a Redirect type")
}
@ -167,12 +167,11 @@ func TestAppRoot(t *testing.T) {
ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{true}).Parse(ing)
redirect, ok := i.(*Redirect)
redirect, ok := i.(*Config)
if !ok {
t.Errorf("expected a App Context")
}
if redirect.AppRoot != "/app1" {
t.Errorf("Unexpected value got in AppRoot")
}
}

View file

@ -31,8 +31,8 @@ const (
secureVerifyCASecret = "ingress.kubernetes.io/secure-verify-ca-secret"
)
// Secure describes SSL backend configuration
type Secure struct {
// Config describes SSL backend configuration
type Config struct {
Secure bool `json:"secure"`
CACert resolver.AuthSSLCert `json:"caCert"`
}
@ -53,7 +53,7 @@ func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
func (a su) Parse(ing *extensions.Ingress) (interface{}, error) {
s, _ := parser.GetBoolAnnotation(secureUpstream, ing)
ca, _ := parser.GetStringAnnotation(secureVerifyCASecret, ing)
secure := &Secure{
secure := &Config{
Secure: s,
CACert: resolver.AuthSSLCert{},
}
@ -71,7 +71,7 @@ func (a su) Parse(ing *extensions.Ingress) (interface{}, error) {
if caCert == nil {
return secure, nil
}
return &Secure{
return &Config{
Secure: s,
CACert: *caCert,
}, nil

View file

@ -17,14 +17,13 @@ limitations under the License.
package secureupstream
import (
"fmt"
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)

View file

@ -42,24 +42,24 @@ var (
affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`)
)
// AffinityConfig describes the per ingress session affinity config
type AffinityConfig struct {
// Config describes the per ingress session affinity config
type Config struct {
// The type of affinity that will be used
AffinityType string `json:"type"`
CookieConfig
Type string `json:"type"`
Cookie
}
// CookieConfig describes the Config of cookie type affinity
type CookieConfig struct {
// Cookie describes the Config of cookie type affinity
type Cookie struct {
// The name of the cookie that will be used in case of cookie affinity type.
Name string `json:"name"`
// The hash that will be used to encode the cookie in case of cookie affinity type
Hash string `json:"hash"`
}
// CookieAffinityParse gets the annotation values related to Cookie Affinity
// cookieAffinityParse gets the annotation values related to Cookie Affinity
// It also sets default values when no value or incorrect value is found
func CookieAffinityParse(ing *extensions.Ingress) *CookieConfig {
func cookieAffinityParse(ing *extensions.Ingress) *Cookie {
sn, err := parser.GetStringAnnotation(annotationAffinityCookieName, ing)
@ -75,7 +75,7 @@ func CookieAffinityParse(ing *extensions.Ingress) *CookieConfig {
sh = defaultAffinityCookieHash
}
return &CookieConfig{
return &Cookie{
Name: sn,
Hash: sh,
}
@ -92,7 +92,7 @@ type affinity struct {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives
func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
cookieAffinityConfig := &CookieConfig{}
cookie := &Cookie{}
// Check the type of affinity that will be used
at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
if err != nil {
@ -101,15 +101,14 @@ func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
switch at {
case "cookie":
cookieAffinityConfig = CookieAffinityParse(ing)
cookie = cookieAffinityParse(ing)
default:
glog.V(3).Infof("No default affinity was found for Ingress %v", ing.Name)
}
return &AffinityConfig{
AffinityType: at,
CookieConfig: *cookieAffinityConfig,
}, nil
return &Config{
Type: at,
Cookie: *cookie,
}, nil
}

View file

@ -70,20 +70,20 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
ing.SetAnnotations(data)
affin, _ := NewParser().Parse(ing)
nginxAffinity, ok := affin.(*AffinityConfig)
nginxAffinity, ok := affin.(*Config)
if !ok {
t.Errorf("expected a Config type")
}
if nginxAffinity.AffinityType != "cookie" {
t.Errorf("expected cookie as sticky-type but returned %v", nginxAffinity.AffinityType)
if nginxAffinity.Type != "cookie" {
t.Errorf("expected cookie as sticky-type but returned %v", nginxAffinity.Type)
}
if nginxAffinity.CookieConfig.Hash != "md5" {
t.Errorf("expected md5 as sticky-hash but returned %v", nginxAffinity.CookieConfig.Hash)
if nginxAffinity.Cookie.Hash != "md5" {
t.Errorf("expected md5 as sticky-hash but returned %v", nginxAffinity.Cookie.Hash)
}
if nginxAffinity.CookieConfig.Name != "INGRESSCOOKIE" {
t.Errorf("expected route as sticky-name but returned %v", nginxAffinity.CookieConfig.Name)
if nginxAffinity.Cookie.Name != "INGRESSCOOKIE" {
t.Errorf("expected route as sticky-name but returned %v", nginxAffinity.Cookie.Name)
}
}

View file

@ -25,7 +25,6 @@ import (
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/client-go/tools/cache"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
@ -156,18 +155,3 @@ func (ic *NGINXController) checkMissingSecrets() {
}
}
}
// sslCertTracker holds a store of referenced Secrets in Ingress rules
type sslCertTracker struct {
cache.ThreadSafeStore
}
func newSSLCertTracker() *sslCertTracker {
return &sslCertTracker{
cache.NewThreadSafeStore(cache.Indexers{}, cache.Indices{}),
}
}
func (s *sslCertTracker) DeleteAll(key string) {
s.Delete(key)
}

View file

@ -37,6 +37,7 @@ import (
clientset "k8s.io/client-go/kubernetes"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
"k8s.io/ingress-nginx/pkg/ingress/annotations/healthcheck"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
@ -316,7 +317,7 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
for _, sp := range svc.Spec.Ports {
if sp.Name == svcPort {
if sp.Protocol == proto {
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Upstream{})
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Config{})
break
}
}
@ -327,7 +328,7 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
for _, sp := range svc.Spec.Ports {
if sp.Port == int32(targetPort) {
if sp.Protocol == proto {
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Upstream{})
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Config{})
break
}
}
@ -379,7 +380,7 @@ func (n *NGINXController) getDefaultUpstream() *ingress.Backend {
}
svc := svcObj.(*apiv1.Service)
endps := n.getEndpoints(svc, &svc.Spec.Ports[0], apiv1.ProtocolTCP, &healthcheck.Upstream{})
endps := n.getEndpoints(svc, &svc.Spec.Ports[0], apiv1.ProtocolTCP, &healthcheck.Config{})
if len(endps) == 0 {
glog.Warningf("service %v does not have any active endpoints", svcKey)
endps = []ingress.Endpoint{n.DefaultEndpoint()}
@ -398,8 +399,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
servers := n.createServers(ingresses, upstreams, du)
for _, ing := range ingresses {
affinity := n.annotations.SessionAffinity(ing)
anns := n.annotations.Extract(ing)
anns := n.getIngressAnnotations(ing)
for _, rule := range ing.Spec.Rules {
host := rule.Host
@ -418,13 +418,11 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
}
if server.CertificateAuth.CAFileName == "" {
ca := n.annotations.CertificateAuth(ing)
if ca != nil {
server.CertificateAuth = *ca
// It is possible that no CAFileName is found in the secret
if server.CertificateAuth.CAFileName == "" {
glog.V(3).Infof("secret %v does not contain 'ca.crt', mutual authentication not enabled - ingress rule %v/%v.", server.CertificateAuth.Secret, ing.Namespace, ing.Name)
}
server.CertificateAuth = anns.CertificateAuth
// It is possible that no CAFileName is found in the secret
if server.CertificateAuth.CAFileName == "" {
glog.V(3).Infof("secret %v does not contain 'ca.crt', mutual authentication not enabled - ingress rule %v/%v.", server.CertificateAuth.Secret, ing.Namespace, ing.Name)
}
} else {
glog.V(3).Infof("server %v already contains a mutual authentication configuration - ingress rule %v/%v", server.Hostname, ing.Namespace, ing.Name)
@ -461,7 +459,19 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
loc.Port = ups.Port
loc.Service = ups.Service
loc.Ingress = ing
mergeLocationAnnotations(loc, anns)
loc.BasicDigestAuth = anns.BasicDigestAuth
loc.ClientBodyBufferSize = anns.ClientBodyBufferSize
loc.ConfigurationSnippet = anns.ConfigurationSnippet
loc.CorsConfig = anns.CorsConfig
loc.ExternalAuth = anns.ExternalAuth
loc.Proxy = anns.Proxy
loc.RateLimit = anns.RateLimit
loc.Redirect = anns.Redirect
loc.Rewrite = anns.Rewrite
loc.UpstreamVhost = anns.UpstreamVhost
loc.VtsFilterKey = anns.VtsFilterKey
loc.Whitelist = anns.Whitelist
if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true
}
@ -472,14 +482,26 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
if addLoc {
glog.V(3).Infof("adding location %v in ingress rule %v/%v upstream %v", nginxPath, ing.Namespace, ing.Name, ups.Name)
loc := &ingress.Location{
Path: nginxPath,
Backend: ups.Name,
IsDefBackend: false,
Service: ups.Service,
Port: ups.Port,
Ingress: ing,
Path: nginxPath,
Backend: ups.Name,
IsDefBackend: false,
Service: ups.Service,
Port: ups.Port,
Ingress: ing,
BasicDigestAuth: anns.BasicDigestAuth,
ClientBodyBufferSize: anns.ClientBodyBufferSize,
ConfigurationSnippet: anns.ConfigurationSnippet,
CorsConfig: anns.CorsConfig,
ExternalAuth: anns.ExternalAuth,
Proxy: anns.Proxy,
RateLimit: anns.RateLimit,
Redirect: anns.Redirect,
Rewrite: anns.Rewrite,
UpstreamVhost: anns.UpstreamVhost,
VtsFilterKey: anns.VtsFilterKey,
Whitelist: anns.Whitelist,
}
mergeLocationAnnotations(loc, anns)
if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true
}
@ -487,12 +509,12 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
}
if ups.SessionAffinity.AffinityType == "" {
ups.SessionAffinity.AffinityType = affinity.AffinityType
ups.SessionAffinity.AffinityType = anns.SessionAffinity.Type
}
if affinity.AffinityType == "cookie" {
ups.SessionAffinity.CookieSessionAffinity.Name = affinity.CookieConfig.Name
ups.SessionAffinity.CookieSessionAffinity.Hash = affinity.CookieConfig.Hash
if anns.SessionAffinity.Type == "cookie" {
ups.SessionAffinity.CookieSessionAffinity.Name = anns.SessionAffinity.Cookie.Name
ups.SessionAffinity.CookieSessionAffinity.Hash = anns.SessionAffinity.Cookie.Hash
locs := ups.SessionAffinity.CookieSessionAffinity.Locations
if _, ok := locs[host]; !ok {
@ -519,7 +541,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
// check if the location contains endpoints and a custom default backend
if location.DefaultBackend != nil {
sp := location.DefaultBackend.Spec.Ports[0]
endps := n.getEndpoints(location.DefaultBackend, &sp, apiv1.ProtocolTCP, &healthcheck.Upstream{})
endps := n.getEndpoints(location.DefaultBackend, &sp, apiv1.ProtocolTCP, &healthcheck.Config{})
if len(endps) > 0 {
glog.V(3).Infof("using custom default backend in server %v location %v (service %v/%v)",
server.Hostname, location.Path, location.DefaultBackend.Namespace, location.DefaultBackend.Name)
@ -617,10 +639,7 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
upstreams[defUpstreamName] = du
for _, ing := range data {
secUpstream := n.annotations.SecureUpstream(ing)
hz := n.annotations.HealthCheck(ing)
serviceUpstream := n.annotations.ServiceUpstream(ing)
upstreamHashBy := n.annotations.UpstreamHashBy(ing)
anns := n.getIngressAnnotations(ing)
var defBackend string
if ing.Spec.Backend != nil {
@ -635,7 +654,7 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
// Add the service cluster endpoint as the upstream instead of individual endpoints
// if the serviceUpstream annotation is enabled
if serviceUpstream {
if anns.ServiceUpstream {
endpoint, err := n.getServiceClusterEndpoint(svcKey, ing.Spec.Backend)
if err != nil {
glog.Errorf("Failed to get service cluster endpoint for service %s: %v", svcKey, err)
@ -645,7 +664,7 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
}
if len(upstreams[defBackend].Endpoints) == 0 {
endps, err := n.serviceEndpoints(svcKey, ing.Spec.Backend.ServicePort.String(), hz)
endps, err := n.serviceEndpoints(svcKey, ing.Spec.Backend.ServicePort.String(), &anns.HealthCheck)
upstreams[defBackend].Endpoints = append(upstreams[defBackend].Endpoints, endps...)
if err != nil {
glog.Warningf("error creating upstream %v: %v", defBackend, err)
@ -674,22 +693,22 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
upstreams[name].Port = path.Backend.ServicePort
if !upstreams[name].Secure {
upstreams[name].Secure = secUpstream.Secure
upstreams[name].Secure = anns.SecureUpstream.Secure
}
if upstreams[name].SecureCACert.Secret == "" {
upstreams[name].SecureCACert = secUpstream.CACert
upstreams[name].SecureCACert = anns.SecureUpstream.CACert
}
if upstreams[name].UpstreamHashBy == "" {
upstreams[name].UpstreamHashBy = upstreamHashBy
upstreams[name].UpstreamHashBy = anns.UpstreamHashBy
}
svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName)
// Add the service cluster endpoint as the upstream instead of individual endpoints
// if the serviceUpstream annotation is enabled
if serviceUpstream {
if anns.ServiceUpstream {
endpoint, err := n.getServiceClusterEndpoint(svcKey, &path.Backend)
if err != nil {
glog.Errorf("failed to get service cluster endpoint for service %s: %v", svcKey, err)
@ -699,7 +718,7 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
}
if len(upstreams[name].Endpoints) == 0 {
endp, err := n.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz)
endp, err := n.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), &anns.HealthCheck)
if err != nil {
glog.Warningf("error obtaining service endpoints: %v", err)
continue
@ -759,7 +778,7 @@ func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *exte
// serviceEndpoints returns the upstream servers (endpoints) associated
// to a service.
func (n *NGINXController) serviceEndpoints(svcKey, backendPort string,
hz *healthcheck.Upstream) ([]ingress.Endpoint, error) {
hz *healthcheck.Config) ([]ingress.Endpoint, error) {
svc, err := n.listers.Service.GetByName(svcKey)
var upstreams []ingress.Endpoint
@ -843,7 +862,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
aliases := make(map[string]string, len(data))
bdef := n.GetDefaultBackend()
ngxProxy := proxy.Configuration{
ngxProxy := proxy.Config{
BodySize: bdef.ProxyBodySize,
ConnectTimeout: bdef.ProxyConnectTimeout,
SendTimeout: bdef.ProxySendTimeout,
@ -884,9 +903,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
// initialize all the servers
for _, ing := range data {
// check if ssl passthrough is configured
sslpt := n.annotations.SSLPassthrough(ing)
anns := n.getIngressAnnotations(ing)
// default upstream server
un := du.Name
@ -930,16 +947,14 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
Service: &apiv1.Service{},
},
},
SSLPassthrough: sslpt,
SSLPassthrough: anns.SSLPassthrough,
}
}
}
// configure default location, alias, and SSL
for _, ing := range data {
// setup server-alias based on annotations
aliasAnnotation := n.annotations.Alias(ing)
srvsnippet := n.annotations.ServerSnippet(ing)
anns := n.getIngressAnnotations(ing)
for _, rule := range ing.Spec.Rules {
host := rule.Host
@ -948,11 +963,11 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
}
// setup server aliases
if aliasAnnotation != "" {
if anns.Alias != "" {
if servers[host].Alias == "" {
servers[host].Alias = aliasAnnotation
if _, ok := aliases[aliasAnnotation]; !ok {
aliases[aliasAnnotation] = host
servers[host].Alias = anns.Alias
if _, ok := aliases["Alias"]; !ok {
aliases["Alias"] = host
}
} else {
glog.Warningf("ingress %v/%v for host %v contains an Alias but one has already been configured.",
@ -961,14 +976,14 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
}
//notifying the user that it has already been configured.
if servers[host].ServerSnippet != "" && srvsnippet != "" {
if servers[host].ServerSnippet != "" && anns.ServerSnippet != "" {
glog.Warningf("ingress %v/%v for host %v contains a Server Snippet section that it has already been configured.",
ing.Namespace, ing.Name, host)
}
// only add a server snippet if the server does not have one previously configured
if servers[host].ServerSnippet == "" && srvsnippet != "" {
servers[host].ServerSnippet = srvsnippet
if servers[host].ServerSnippet == "" && anns.ServerSnippet != "" {
servers[host].ServerSnippet = anns.ServerSnippet
}
// only add a certificate if the server does not have one previously configured
@ -1044,7 +1059,7 @@ func (n *NGINXController) getEndpoints(
s *apiv1.Service,
servicePort *apiv1.ServicePort,
proto apiv1.Protocol,
hz *healthcheck.Upstream) []ingress.Endpoint {
hz *healthcheck.Config) []ingress.Endpoint {
upsServers := []ingress.Endpoint{}
@ -1152,6 +1167,7 @@ func (n *NGINXController) isForceReload() bool {
return atomic.LoadInt32(&n.forceReload) != 0
}
// SetForceReload sets if the ingress controller should be reloaded or not
func (n *NGINXController) SetForceReload(shouldReload bool) {
if shouldReload {
atomic.StoreInt32(&n.forceReload, 1)
@ -1160,3 +1176,24 @@ func (n *NGINXController) SetForceReload(shouldReload bool) {
atomic.StoreInt32(&n.forceReload, 0)
}
}
func (n *NGINXController) extractAnnotations(ing *extensions.Ingress) {
anns := n.annotations.Extract(ing)
glog.V(3).Infof("updating annotations information for ingres %v/%v", anns.Namespace, anns.Name)
n.listers.IngressAnnotation.Update(anns)
}
// getByIngress returns the parsed annotations from an Ingress
func (n *NGINXController) getIngressAnnotations(ing *extensions.Ingress) *annotations.Ingress {
key := fmt.Sprintf("%v/%v", ing.Namespace, ing.Name)
item, exists, err := n.listers.IngressAnnotation.GetByKey(key)
if err != nil {
glog.Errorf("unexpected error getting ingress annotation %v: %v", key, err)
return &annotations.Ingress{}
}
if !exists {
glog.Errorf("ingress annotation %v was not found", key)
return &annotations.Ingress{}
}
return item.(*annotations.Ingress)
}

View file

@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/cache"
cache_client "k8s.io/client-go/tools/cache"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
@ -60,7 +61,7 @@ func (c *cacheController) Run(stopCh chan struct{}) {
}
}
func (n *NGINXController) createListers(stopCh chan struct{}) *ingress.StoreLister {
func (n *NGINXController) createListers(stopCh chan struct{}) (*ingress.StoreLister, *cacheController) {
// from here to the end of the method all the code is just boilerplate
// required to watch Ingress, Secrets, ConfigMaps and Endoints.
// This is used to detect new content, updates or removals and act accordingly
@ -73,6 +74,7 @@ func (n *NGINXController) createListers(stopCh chan struct{}) *ingress.StoreList
return
}
n.extractAnnotations(addIng)
n.recorder.Eventf(addIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name))
n.syncQueue.Enqueue(obj)
},
@ -113,6 +115,7 @@ func (n *NGINXController) createListers(stopCh chan struct{}) *ingress.StoreList
n.recorder.Eventf(curIng, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
}
n.extractAnnotations(curIng)
n.syncQueue.Enqueue(cur)
},
}
@ -141,7 +144,7 @@ func (n *NGINXController) createListers(stopCh chan struct{}) *ingress.StoreList
}
}
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
n.sslCertTracker.DeleteAll(key)
n.sslCertTracker.Delete(key)
n.syncQueue.Enqueue(key)
},
}
@ -196,6 +199,7 @@ func (n *NGINXController) createListers(stopCh chan struct{}) *ingress.StoreList
}
lister := &ingress.StoreLister{}
lister.IngressAnnotation.Store = cache_client.NewStore(cache_client.DeletionHandlingMetaNamespaceKeyFunc)
controller := &cacheController{}
@ -219,7 +223,5 @@ func (n *NGINXController) createListers(stopCh chan struct{}) *ingress.StoreList
cache.NewListWatchFromClient(n.cfg.Client.CoreV1().RESTClient(), "services", n.cfg.Namespace, fields.Everything()),
&apiv1.Service{}, n.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
controller.Run(n.stopCh)
return lister
return lister, controller
}

View file

@ -43,6 +43,7 @@ import (
"k8s.io/kubernetes/pkg/util/filesystem"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
ngx_config "k8s.io/ingress-nginx/pkg/ingress/controller/config"
@ -50,6 +51,7 @@ import (
ngx_template "k8s.io/ingress-nginx/pkg/ingress/controller/template"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
"k8s.io/ingress-nginx/pkg/ingress/status"
"k8s.io/ingress-nginx/pkg/ingress/store"
ing_net "k8s.io/ingress-nginx/pkg/net"
"k8s.io/ingress-nginx/pkg/net/dns"
"k8s.io/ingress-nginx/pkg/net/ssl"
@ -102,7 +104,7 @@ func NewNGINXController(config *Configuration) *NGINXController {
resolver: h,
cfg: config,
sslCertTracker: newSSLCertTracker(),
sslCertTracker: store.NewSSLCertTracker(),
syncRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.3, 1),
recorder: eventBroadcaster.NewRecorder(scheme.Scheme, apiv1.EventSource{
@ -115,11 +117,13 @@ func NewNGINXController(config *Configuration) *NGINXController {
fileSystem: filesystem.DefaultFs{},
}
n.listers, n.controllers = n.createListers(n.stopCh)
n.stats = newStatsCollector(config.Namespace, config.IngressClass, n.binary, n.cfg.ListenPorts.Status)
n.syncQueue = task.NewTaskQueue(n.syncIngress)
n.listers = n.createListers(n.stopCh)
n.annotations = annotations.NewAnnotationExtractor(n)
if config.UpdateStatus {
n.syncStatus = status.NewStatusSyncer(status.Config{
@ -135,7 +139,6 @@ func NewNGINXController(config *Configuration) *NGINXController {
} else {
glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
}
n.annotations = newAnnotationExtractor(n)
var onChange func()
onChange = func() {
@ -170,9 +173,10 @@ Error loading new template : %v
type NGINXController struct {
cfg *Configuration
listers *ingress.StoreLister
listers *ingress.StoreLister
controllers *cacheController
annotations annotationExtractor
annotations annotations.Extractor
recorder record.EventRecorder
@ -182,7 +186,7 @@ type NGINXController struct {
// local store of SSL certificates
// (only certificates used in ingress)
sslCertTracker *sslCertTracker
sslCertTracker *store.SSLCertTracker
syncRateLimiter flowcontrol.RateLimiter
@ -234,6 +238,8 @@ type NGINXController struct {
func (n *NGINXController) Start() {
glog.Infof("starting Ingress controller")
n.controllers.Run(n.stopCh)
// initial sync of secrets to avoid unnecessary reloads
glog.Info("running initial sync of secrets")
for _, obj := range n.listers.Ingress.List() {
@ -425,12 +431,12 @@ func (n *NGINXController) SetConfig(cmap *apiv1.ConfigMap) {
n.backendDefaults = c.Backend
}
// OnUpdate is called by syncQueue in https://github.com/kubernetes/ingress-nginx/blob/master/pkg/ingress/controller/controller.go#L426
// periodically to keep the configuration in sync.
// OnUpdate is called periodically by syncQueue to keep the configuration in sync.
//
// 1. converts configmap configuration to custom configuration object
// 2. write the custom template (the complexity depends on the implementation)
// 3. write the configuration file
//
// convert configmap to custom configuration object (different in each implementation)
// write the custom template (the complexity depends on the implementation)
// write the configuration file
// returning nill implies the backend will be reloaded.
// if an error is returned means requeue the update
func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {

View file

@ -354,8 +354,8 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string {
}
// TODO: Needs Unit Tests
func filterRateLimits(input interface{}) []ratelimit.RateLimit {
ratelimits := []ratelimit.RateLimit{}
func filterRateLimits(input interface{}) []ratelimit.Config {
ratelimits := []ratelimit.Config{}
found := sets.String{}
servers, ok := input.([]*ingress.Server)

View file

@ -114,7 +114,7 @@ func TestBuildLocation(t *testing.T) {
for k, tc := range tmplFuncTestcases {
loc := &ingress.Location{
Path: tc.Path,
Rewrite: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL},
Rewrite: rewrite.Config{Target: tc.Target, AddBaseURL: tc.AddBaseURL},
}
newLoc := buildLocation(loc)
@ -128,7 +128,7 @@ func TestBuildProxyPass(t *testing.T) {
for k, tc := range tmplFuncTestcases {
loc := &ingress.Location{
Path: tc.Path,
Rewrite: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL, BaseURLScheme: tc.BaseURLScheme},
Rewrite: rewrite.Config{Target: tc.Target, AddBaseURL: tc.AddBaseURL, BaseURLScheme: tc.BaseURLScheme},
Backend: "upstream-name",
}
@ -141,7 +141,7 @@ func TestBuildProxyPass(t *testing.T) {
func TestBuildAuthResponseHeaders(t *testing.T) {
loc := &ingress.Location{
ExternalAuth: authreq.External{ResponseHeaders: []string{"h1", "H-With-Caps-And-Dashes"}},
ExternalAuth: authreq.Config{ResponseHeaders: []string{"h1", "H-With-Caps-And-Dashes"}},
}
headers := buildAuthResponseHeaders(loc)
expected := []string{

View file

@ -21,17 +21,12 @@ import (
"github.com/golang/glog"
"github.com/imdario/mergo"
api "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/util/sysctl"
"k8s.io/ingress-nginx/pkg/ingress"
)
// DeniedKeyName name of the key that contains the reason to deny a location
const DeniedKeyName = "Denied"
// newUpstream creates an upstream without servers.
func newUpstream(name string) *ingress.Backend {
return &ingress.Backend{
@ -46,17 +41,6 @@ func newUpstream(name string) *ingress.Backend {
}
}
func mergeLocationAnnotations(loc *ingress.Location, anns map[string]interface{}) {
if _, ok := anns[DeniedKeyName]; ok {
loc.Denied = anns[DeniedKeyName].(error)
}
delete(anns, DeniedKeyName)
err := mergo.Map(loc, anns)
if err != nil {
glog.Errorf("unexpected error merging extracted annotations in location type: %v", err)
}
}
// sysctlSomaxconn returns the value of net.core.somaxconn, i.e.
// maximum number of connections that can be queued for acceptance
// http://nginx.org/en/docs/http/ngx_http_core_module.html#listen

View file

@ -17,18 +17,7 @@ limitations under the License.
package controller
import (
"reflect"
"testing"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
"k8s.io/ingress-nginx/pkg/ingress/annotations/cors"
"k8s.io/ingress-nginx/pkg/ingress/annotations/ipwhitelist"
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/pkg/ingress/annotations/redirect"
"k8s.io/ingress-nginx/pkg/ingress/annotations/rewrite"
)
type fakeError struct{}
@ -37,51 +26,6 @@ func (fe *fakeError) Error() string {
return "fakeError"
}
func TestMergeLocationAnnotations(t *testing.T) {
// initial parameters
loc := ingress.Location{}
annotations := map[string]interface{}{
"Path": "/checkpath",
"IsDefBackend": true,
"Backend": "foo_backend",
"BasicDigestAuth": auth.BasicDigest{},
DeniedKeyName: &fakeError{},
"CorsConfig": cors.CorsConfig{},
"ExternalAuth": authreq.External{},
"RateLimit": ratelimit.RateLimit{},
"Redirect": redirect.Redirect{},
"Rewrite": rewrite.Redirect{},
"Whitelist": ipwhitelist.SourceRange{},
"Proxy": proxy.Configuration{},
"UsePortInRedirects": true,
}
// create test table
type fooMergeLocationAnnotationsStruct struct {
fName string
er interface{}
}
fooTests := []fooMergeLocationAnnotationsStruct{}
for name, value := range annotations {
fva := fooMergeLocationAnnotationsStruct{name, value}
fooTests = append(fooTests, fva)
}
// execute test
mergeLocationAnnotations(&loc, annotations)
// check result
for _, foo := range fooTests {
fv := reflect.ValueOf(loc).FieldByName(foo.fName).Interface()
if !reflect.DeepEqual(fv, foo.er) {
t.Errorf("Returned %v but expected %v for the field %s", fv, foo.er, foo.fName)
}
}
if _, ok := annotations[DeniedKeyName]; ok {
t.Errorf("%s should be removed after mergeLocationAnnotations", DeniedKeyName)
}
}
func TestIntInSlice(t *testing.T) {
fooTests := []struct {
i int

View file

@ -28,6 +28,11 @@ type IngressLister struct {
cache.Store
}
// IngressAnnotationsLister makes a Store that lists annotations in Ingress rules.
type IngressAnnotationsLister struct {
cache.Store
}
// SecretLister makes a Store that lists Secrets.
type SecretLister struct {
cache.Store
@ -94,3 +99,15 @@ func (s *EndpointLister) GetServiceEndpoints(svc *apiv1.Service) (*apiv1.Endpoin
}
return nil, fmt.Errorf("could not find endpoints for service: %v", svc.Name)
}
// SSLCertTracker holds a store of referenced Secrets in Ingress rules
type SSLCertTracker struct {
cache.ThreadSafeStore
}
// NewSSLCertTracker creates a new SSLCertTracker store
func NewSSLCertTracker() *SSLCertTracker {
return &SSLCertTracker{
cache.NewThreadSafeStore(cache.Indexers{}, cache.Indices{}),
}
}

View file

@ -47,11 +47,12 @@ var (
// StoreLister returns the configured stores for ingresses, services,
// endpoints, secrets and configmaps.
type StoreLister struct {
Ingress store.IngressLister
Service store.ServiceLister
Endpoint store.EndpointLister
Secret store.SecretLister
ConfigMap store.ConfigMapLister
Ingress store.IngressLister
Service store.ServiceLister
Endpoint store.EndpointLister
Secret store.SecretLister
ConfigMap store.ConfigMapLister
IngressAnnotation store.IngressAnnotationsLister
}
// Configuration holds the definition of all the parts required to describe all
@ -165,7 +166,7 @@ type Server struct {
RedirectFromToWWW bool `json:"redirectFromToWWW,omitempty"`
// CertificateAuth indicates the this server requires mutual authentication
// +optional
CertificateAuth authtls.AuthSSLConfig `json:"certificateAuth"`
CertificateAuth authtls.Config `json:"certificateAuth"`
// ServerSnippet returns the snippet of server
// +optional
@ -211,28 +212,28 @@ type Location struct {
// BasicDigestAuth returns authentication configuration for
// an Ingress rule.
// +optional
BasicDigestAuth auth.BasicDigest `json:"basicDigestAuth,omitempty"`
BasicDigestAuth auth.Config `json:"basicDigestAuth,omitempty"`
// Denied returns an error when this location cannot not be allowed
// Requesting a denied location should return HTTP code 403.
Denied error `json:"denied,omitempty"`
// CorsConfig returns the Cors Configration for the ingress rule
// +optional
CorsConfig cors.CorsConfig `json:"corsConfig,omitempty"`
CorsConfig cors.Config `json:"corsConfig,omitempty"`
// ExternalAuth indicates the access to this location requires
// authentication using an external provider
// +optional
ExternalAuth authreq.External `json:"externalAuth,omitempty"`
ExternalAuth authreq.Config `json:"externalAuth,omitempty"`
// RateLimit describes a limit in the number of connections per IP
// address or connections per second.
// The Redirect annotation precedes RateLimit
// +optional
RateLimit ratelimit.RateLimit `json:"rateLimit,omitempty"`
RateLimit ratelimit.Config `json:"rateLimit,omitempty"`
// Redirect describes a temporal o permanent redirection this location.
// +optional
Redirect redirect.Redirect `json:"redirect,omitempty"`
Redirect redirect.Config `json:"redirect,omitempty"`
// Rewrite describes the redirection this location.
// +optional
Rewrite rewrite.Redirect `json:"rewrite,omitempty"`
Rewrite rewrite.Config `json:"rewrite,omitempty"`
// Whitelist indicates only connections from certain client
// addresses or networks are allowed.
// +optional
@ -240,7 +241,7 @@ type Location struct {
// Proxy contains information about timeouts and buffer sizes
// to be used in connections against endpoints
// +optional
Proxy proxy.Configuration `json:"proxy,omitempty"`
Proxy proxy.Config `json:"proxy,omitempty"`
// UsePortInRedirects indicates if redirects must specify the port
// +optional
UsePortInRedirects bool `json:"usePortInRedirects"`