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. limitations under the License.
*/ */
package controller package annotations
import ( import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/imdario/mergo"
extensions "k8s.io/api/extensions/v1beta1" 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/alias"
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth" "k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq" "k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
@ -48,51 +51,89 @@ import (
"k8s.io/ingress-nginx/pkg/ingress/resolver" "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.AuthCertificate
resolver.DefaultBackend resolver.DefaultBackend
resolver.Secret resolver.Secret
resolver.Service 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 secretResolver resolver.Secret
annotations map[string]parser.IngressAnnotation annotations map[string]parser.IngressAnnotation
} }
func newAnnotationExtractor(cfg extractorConfig) annotationExtractor { // NewAnnotationExtractor creates a new annotations extractor
return annotationExtractor{ func NewAnnotationExtractor(cfg config) Extractor {
return Extractor{
cfg, cfg,
map[string]parser.IngressAnnotation{ map[string]parser.IngressAnnotation{
"Alias": alias.NewParser(),
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg), "BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
"ExternalAuth": authreq.NewParser(),
"CertificateAuth": authtls.NewParser(cfg), "CertificateAuth": authtls.NewParser(cfg),
"ClientBodyBufferSize": clientbodybuffersize.NewParser(),
"ConfigurationSnippet": snippet.NewParser(),
"CorsConfig": cors.NewParser(), "CorsConfig": cors.NewParser(),
"DefaultBackend": defaultbackend.NewParser(cfg),
"ExternalAuth": authreq.NewParser(),
"HealthCheck": healthcheck.NewParser(cfg), "HealthCheck": healthcheck.NewParser(cfg),
"Whitelist": ipwhitelist.NewParser(cfg),
"UsePortInRedirects": portinredirect.NewParser(cfg),
"Proxy": proxy.NewParser(cfg), "Proxy": proxy.NewParser(cfg),
"RateLimit": ratelimit.NewParser(cfg), "RateLimit": ratelimit.NewParser(cfg),
"Redirect": redirect.NewParser(), "Redirect": redirect.NewParser(),
"Rewrite": rewrite.NewParser(cfg), "Rewrite": rewrite.NewParser(cfg),
"SecureUpstream": secureupstream.NewParser(cfg), "SecureUpstream": secureupstream.NewParser(cfg),
"ServerSnippet": serversnippet.NewParser(),
"ServiceUpstream": serviceupstream.NewParser(), "ServiceUpstream": serviceupstream.NewParser(),
"SessionAffinity": sessionaffinity.NewParser(), "SessionAffinity": sessionaffinity.NewParser(),
"SSLPassthrough": sslpassthrough.NewParser(), "SSLPassthrough": sslpassthrough.NewParser(),
"ConfigurationSnippet": snippet.NewParser(), "UsePortInRedirects": portinredirect.NewParser(cfg),
"Alias": alias.NewParser(),
"ClientBodyBufferSize": clientbodybuffersize.NewParser(),
"DefaultBackend": defaultbackend.NewParser(cfg),
"UpstreamHashBy": upstreamhashby.NewParser(), "UpstreamHashBy": upstreamhashby.NewParser(),
"UpstreamVhost": upstreamvhost.NewParser(), "UpstreamVhost": upstreamvhost.NewParser(),
"VtsFilterKey": vtsfilterkey.NewParser(), "VtsFilterKey": vtsfilterkey.NewParser(),
"ServerSnippet": serversnippet.NewParser(), "Whitelist": ipwhitelist.NewParser(cfg),
}, },
} }
} }
func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interface{} { // Extract extracts the annotations from an Ingress
anns := make(map[string]interface{}) func (e Extractor) Extract(ing *extensions.Ingress) *Ingress {
pia := &Ingress{
ObjectMeta: ing.ObjectMeta,
}
data := make(map[string]interface{})
for name, annotationParser := range e.annotations { for name, annotationParser := range e.annotations {
val, err := annotationParser.Parse(ing) val, err := annotationParser.Parse(ing)
glog.V(5).Infof("annotation %v in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), val) 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 continue
} }
_, alreadyDenied := anns[DeniedKeyName] _, alreadyDenied := data[DeniedKeyName]
if !alreadyDenied { 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) glog.Errorf("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
continue continue
} }
@ -116,90 +157,14 @@ func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interf
} }
if val != nil { if val != nil {
anns[name] = val data[name] = val
} }
} }
return anns err := mergo.Map(pia, data)
}
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)
if err != nil { if err != nil {
glog.Errorf("error parsing secure upstream: %v", err) glog.Errorf("unexpected error merging extracted annotations: %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
} }
if err != nil { return pia
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)
} }

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package controller package annotations
import ( import (
"testing" "testing"
@ -75,20 +75,6 @@ func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error)
return nil, nil 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 { func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{ defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend", ServiceName: "default-backend",
@ -125,7 +111,7 @@ func buildIngress() *extensions.Ingress {
} }
func TestSecureUpstream(t *testing.T) { func TestSecureUpstream(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{}) ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -141,7 +127,7 @@ func TestSecureUpstream(t *testing.T) {
for _, foo := range fooAnns { for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations) ing.SetAnnotations(foo.annotations)
r := ec.SecureUpstream(ing) r := ec.Extract(ing).SecureUpstream
if r.Secure != foo.er { if r.Secure != foo.er {
t.Errorf("Returned %v but expected %v", r, 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) { func TestSecureVerifyCACert(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{ ec := NewAnnotationExtractor(mockCfg{
MockSecrets: map[string]*apiv1.Secret{ MockSecrets: map[string]*apiv1.Secret{
"default/secure-verify-ca": { "default/secure-verify-ca": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -176,15 +162,16 @@ func TestSecureVerifyCACert(t *testing.T) {
for _, ann := range anns { for _, ann := range anns {
ing := buildIngress() ing := buildIngress()
ing.SetAnnotations(ann.annotations) ing.SetAnnotations(ann.annotations)
res := ec.SecureUpstream(ing) res := ec.Extract(ing).SecureUpstream
if (res.CACert.CAFileName != "") != ann.exists {
if (res != nil && res.CACert.CAFileName != "") != ann.exists {
t.Errorf("Expected exists was %v on iteration %v", ann.exists, ann.it) t.Errorf("Expected exists was %v on iteration %v", ann.exists, ann.it)
} }
} }
} }
func TestHealthCheck(t *testing.T) { func TestHealthCheck(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{}) ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -201,7 +188,7 @@ func TestHealthCheck(t *testing.T) {
for _, foo := range fooAnns { for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations) ing.SetAnnotations(foo.annotations)
r := ec.HealthCheck(ing) r := ec.Extract(ing).HealthCheck
if r == nil { if r == nil {
t.Errorf("Returned nil but expected a healthcheck.Upstream") t.Errorf("Returned nil but expected a healthcheck.Upstream")
continue continue
@ -218,7 +205,7 @@ func TestHealthCheck(t *testing.T) {
} }
func TestSSLPassthrough(t *testing.T) { func TestSSLPassthrough(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{}) ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -234,7 +221,7 @@ func TestSSLPassthrough(t *testing.T) {
for _, foo := range fooAnns { for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations) ing.SetAnnotations(foo.annotations)
r := ec.SSLPassthrough(ing) r := ec.Extract(ing).SSLPassthrough
if r != foo.er { if r != foo.er {
t.Errorf("Returned %v but expected %v", 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) { func TestUpstreamHashBy(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{}) ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -258,7 +245,7 @@ func TestUpstreamHashBy(t *testing.T) {
for _, foo := range fooAnns { for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations) ing.SetAnnotations(foo.annotations)
r := ec.UpstreamHashBy(ing) r := ec.Extract(ing).UpstreamHashBy
if r != foo.er { if r != foo.er {
t.Errorf("Returned %v but expected %v", 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) { func TestAffinitySession(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{}) ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -284,25 +271,25 @@ func TestAffinitySession(t *testing.T) {
for _, foo := range fooAnns { for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations) 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) t.Logf("Testing pass %v %v %v", foo.affinitytype, foo.hash, foo.name)
if r == nil { if r == nil {
t.Errorf("Returned nil but expected a SessionAffinity.AffinityConfig") t.Errorf("Returned nil but expected a SessionAffinity.AffinityConfig")
continue continue
} }
if r.CookieConfig.Hash != foo.hash { if r.Cookie.Hash != foo.hash {
t.Errorf("Returned %v but expected %v for Hash", r.CookieConfig.Hash, foo.hash) t.Errorf("Returned %v but expected %v for Hash", r.Cookie.Hash, foo.hash)
} }
if r.CookieConfig.Name != foo.name { if r.Cookie.Name != foo.name {
t.Errorf("Returned %v but expected %v for Name", r.CookieConfig.Name, foo.name) t.Errorf("Returned %v but expected %v for Name", r.Cookie.Name, foo.name)
} }
} }
} }
func TestCors(t *testing.T) { func TestCors(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{}) ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -322,7 +309,7 @@ func TestCors(t *testing.T) {
for _, foo := range fooAnns { for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations) 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) t.Logf("Testing pass %v %v %v %v %v", foo.corsenabled, foo.methods, foo.headers, foo.origin, foo.credentials)
if r == nil { if r == nil {
t.Errorf("Returned nil but expected a Cors.CorsConfig") 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" AuthDirectory = "/etc/ingress-controller/auth"
) )
// BasicDigest returns authentication configuration for an Ingress rule // Config returns authentication configuration for an Ingress rule
type BasicDigest struct { type Config struct {
Type string `json:"type"` Type string `json:"type"`
Realm string `json:"realm"` Realm string `json:"realm"`
File string `json:"file"` File string `json:"file"`
@ -55,8 +55,8 @@ type BasicDigest struct {
FileSHA string `json:"fileSha"` FileSHA string `json:"fileSha"`
} }
// Equal tests for equality between two BasicDigest types // Equal tests for equality between two Config types
func (bd1 *BasicDigest) Equal(bd2 *BasicDigest) bool { func (bd1 *Config) Equal(bd2 *Config) bool {
if bd1 == bd2 { if bd1 == bd2 {
return true return true
} }
@ -140,7 +140,7 @@ func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
return nil, err return nil, err
} }
return &BasicDigest{ return &Config{
Type: at, Type: at,
Realm: realm, Realm: realm,
File: passFile, File: passFile,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,9 +28,9 @@ const (
upsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout" 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 // the upstream server/s
type Upstream struct { type Config struct {
MaxFails int `json:"maxFails"` MaxFails int `json:"maxFails"`
FailTimeout int `json:"failTimeout"` FailTimeout int `json:"failTimeout"`
} }
@ -49,7 +49,7 @@ func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) { func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) {
defBackend := a.backendResolver.GetDefaultBackend() defBackend := a.backendResolver.GetDefaultBackend()
if ing.GetAnnotations() == nil { if ing.GetAnnotations() == nil {
return &Upstream{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil return &Config{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil
} }
mf, err := parser.GetIntAnnotation(upsMaxFails, ing) mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
@ -62,5 +62,5 @@ func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) {
ft = defBackend.UpstreamFailTimeout 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) ing.SetAnnotations(data)
hzi, _ := NewParser(mockBackend{}).Parse(ing) hzi, _ := NewParser(mockBackend{}).Parse(ing)
nginxHz, ok := hzi.(*Upstream) nginxHz, ok := hzi.(*Config)
if !ok { if !ok {
t.Errorf("expected a Upstream type") t.Errorf("expected a Upstream type")
} }

View file

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

View file

@ -36,8 +36,8 @@ const (
requestBuffering = "ingress.kubernetes.io/proxy-request-buffering" requestBuffering = "ingress.kubernetes.io/proxy-request-buffering"
) )
// Configuration returns the proxy timeout to use in the upstream server/s // Config returns the proxy timeout to use in the upstream server/s
type Configuration struct { type Config struct {
BodySize string `json:"bodySize"` BodySize string `json:"bodySize"`
ConnectTimeout int `json:"connectTimeout"` ConnectTimeout int `json:"connectTimeout"`
SendTimeout int `json:"sendTimeout"` SendTimeout int `json:"sendTimeout"`
@ -51,7 +51,7 @@ type Configuration struct {
} }
// Equal tests for equality between two Configuration types // 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 { if l1 == l2 {
return true return true
} }
@ -156,5 +156,5 @@ func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
rb = defBackend.ProxyRequestBuffering 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 { if err != nil {
t.Fatalf("unexpected error parsing a valid") t.Fatalf("unexpected error parsing a valid")
} }
p, ok := i.(*Configuration) p, ok := i.(*Config)
if !ok { if !ok {
t.Fatalf("expected a Configuration type") t.Fatalf("expected a Config type")
} }
if p.ConnectTimeout != 1 { if p.ConnectTimeout != 1 {
t.Errorf("expected 1 as connect-timeout but returned %v", p.ConnectTimeout) t.Errorf("expected 1 as connect-timeout but returned %v", p.ConnectTimeout)
@ -137,9 +137,9 @@ func TestProxyWithNoAnnotation(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error parsing a valid") t.Fatalf("unexpected error parsing a valid")
} }
p, ok := i.(*Configuration) p, ok := i.(*Config)
if !ok { if !ok {
t.Fatalf("expected a Configuration type") t.Fatalf("expected a Config type")
} }
if p.ConnectTimeout != 10 { if p.ConnectTimeout != 10 {
t.Errorf("expected 10 as connect-timeout but returned %v", p.ConnectTimeout) t.Errorf("expected 10 as connect-timeout but returned %v", p.ConnectTimeout)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -42,24 +42,24 @@ var (
affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`) affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`)
) )
// AffinityConfig describes the per ingress session affinity config // Config describes the per ingress session affinity config
type AffinityConfig struct { type Config struct {
// The type of affinity that will be used // The type of affinity that will be used
AffinityType string `json:"type"` Type string `json:"type"`
CookieConfig Cookie
} }
// CookieConfig describes the Config of cookie type affinity // Cookie describes the Config of cookie type affinity
type CookieConfig struct { type Cookie struct {
// The name of the cookie that will be used in case of cookie affinity type. // The name of the cookie that will be used in case of cookie affinity type.
Name string `json:"name"` Name string `json:"name"`
// The hash that will be used to encode the cookie in case of cookie affinity type // The hash that will be used to encode the cookie in case of cookie affinity type
Hash string `json:"hash"` 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 // 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) sn, err := parser.GetStringAnnotation(annotationAffinityCookieName, ing)
@ -75,7 +75,7 @@ func CookieAffinityParse(ing *extensions.Ingress) *CookieConfig {
sh = defaultAffinityCookieHash sh = defaultAffinityCookieHash
} }
return &CookieConfig{ return &Cookie{
Name: sn, Name: sn,
Hash: sh, Hash: sh,
} }
@ -92,7 +92,7 @@ type affinity struct {
// ParseAnnotations parses the annotations contained in the ingress // ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives // rule used to configure the affinity directives
func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) { func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
cookieAffinityConfig := &CookieConfig{} cookie := &Cookie{}
// Check the type of affinity that will be used // Check the type of affinity that will be used
at, err := parser.GetStringAnnotation(annotationAffinityType, ing) at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
if err != nil { if err != nil {
@ -101,15 +101,14 @@ func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
switch at { switch at {
case "cookie": case "cookie":
cookieAffinityConfig = CookieAffinityParse(ing) cookie = cookieAffinityParse(ing)
default: default:
glog.V(3).Infof("No default affinity was found for Ingress %v", ing.Name) 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) ing.SetAnnotations(data)
affin, _ := NewParser().Parse(ing) affin, _ := NewParser().Parse(ing)
nginxAffinity, ok := affin.(*AffinityConfig) nginxAffinity, ok := affin.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")
} }
if nginxAffinity.AffinityType != "cookie" { if nginxAffinity.Type != "cookie" {
t.Errorf("expected cookie as sticky-type but returned %v", nginxAffinity.AffinityType) t.Errorf("expected cookie as sticky-type but returned %v", nginxAffinity.Type)
} }
if nginxAffinity.CookieConfig.Hash != "md5" { if nginxAffinity.Cookie.Hash != "md5" {
t.Errorf("expected md5 as sticky-hash but returned %v", nginxAffinity.CookieConfig.Hash) t.Errorf("expected md5 as sticky-hash but returned %v", nginxAffinity.Cookie.Hash)
} }
if nginxAffinity.CookieConfig.Name != "INGRESSCOOKIE" { if nginxAffinity.Cookie.Name != "INGRESSCOOKIE" {
t.Errorf("expected route as sticky-name but returned %v", nginxAffinity.CookieConfig.Name) 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" apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1" extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/client-go/tools/cache"
"k8s.io/ingress-nginx/pkg/ingress" "k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class" "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" clientset "k8s.io/client-go/kubernetes"
"k8s.io/ingress-nginx/pkg/ingress" "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/class"
"k8s.io/ingress-nginx/pkg/ingress/annotations/healthcheck" "k8s.io/ingress-nginx/pkg/ingress/annotations/healthcheck"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser" "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 { for _, sp := range svc.Spec.Ports {
if sp.Name == svcPort { if sp.Name == svcPort {
if sp.Protocol == proto { if sp.Protocol == proto {
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Upstream{}) endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Config{})
break break
} }
} }
@ -327,7 +328,7 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
for _, sp := range svc.Spec.Ports { for _, sp := range svc.Spec.Ports {
if sp.Port == int32(targetPort) { if sp.Port == int32(targetPort) {
if sp.Protocol == proto { if sp.Protocol == proto {
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Upstream{}) endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Config{})
break break
} }
} }
@ -379,7 +380,7 @@ func (n *NGINXController) getDefaultUpstream() *ingress.Backend {
} }
svc := svcObj.(*apiv1.Service) 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 { if len(endps) == 0 {
glog.Warningf("service %v does not have any active endpoints", svcKey) glog.Warningf("service %v does not have any active endpoints", svcKey)
endps = []ingress.Endpoint{n.DefaultEndpoint()} endps = []ingress.Endpoint{n.DefaultEndpoint()}
@ -398,8 +399,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
servers := n.createServers(ingresses, upstreams, du) servers := n.createServers(ingresses, upstreams, du)
for _, ing := range ingresses { for _, ing := range ingresses {
affinity := n.annotations.SessionAffinity(ing) anns := n.getIngressAnnotations(ing)
anns := n.annotations.Extract(ing)
for _, rule := range ing.Spec.Rules { for _, rule := range ing.Spec.Rules {
host := rule.Host host := rule.Host
@ -418,13 +418,11 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
} }
if server.CertificateAuth.CAFileName == "" { if server.CertificateAuth.CAFileName == "" {
ca := n.annotations.CertificateAuth(ing) server.CertificateAuth = anns.CertificateAuth
if ca != nil { // It is possible that no CAFileName is found in the secret
server.CertificateAuth = *ca if server.CertificateAuth.CAFileName == "" {
// It is possible that no CAFileName is found in the secret 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)
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 { } else {
glog.V(3).Infof("server %v already contains a mutual authentication configuration - ingress rule %v/%v", server.Hostname, ing.Namespace, ing.Name) 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.Port = ups.Port
loc.Service = ups.Service loc.Service = ups.Service
loc.Ingress = ing 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 { if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true server.RedirectFromToWWW = true
} }
@ -472,14 +482,26 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
if addLoc { if addLoc {
glog.V(3).Infof("adding location %v in ingress rule %v/%v upstream %v", nginxPath, ing.Namespace, ing.Name, ups.Name) glog.V(3).Infof("adding location %v in ingress rule %v/%v upstream %v", nginxPath, ing.Namespace, ing.Name, ups.Name)
loc := &ingress.Location{ loc := &ingress.Location{
Path: nginxPath, Path: nginxPath,
Backend: ups.Name, Backend: ups.Name,
IsDefBackend: false, IsDefBackend: false,
Service: ups.Service, Service: ups.Service,
Port: ups.Port, Port: ups.Port,
Ingress: ing, 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 { if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true server.RedirectFromToWWW = true
} }
@ -487,12 +509,12 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
} }
if ups.SessionAffinity.AffinityType == "" { if ups.SessionAffinity.AffinityType == "" {
ups.SessionAffinity.AffinityType = affinity.AffinityType ups.SessionAffinity.AffinityType = anns.SessionAffinity.Type
} }
if affinity.AffinityType == "cookie" { if anns.SessionAffinity.Type == "cookie" {
ups.SessionAffinity.CookieSessionAffinity.Name = affinity.CookieConfig.Name ups.SessionAffinity.CookieSessionAffinity.Name = anns.SessionAffinity.Cookie.Name
ups.SessionAffinity.CookieSessionAffinity.Hash = affinity.CookieConfig.Hash ups.SessionAffinity.CookieSessionAffinity.Hash = anns.SessionAffinity.Cookie.Hash
locs := ups.SessionAffinity.CookieSessionAffinity.Locations locs := ups.SessionAffinity.CookieSessionAffinity.Locations
if _, ok := locs[host]; !ok { 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 // check if the location contains endpoints and a custom default backend
if location.DefaultBackend != nil { if location.DefaultBackend != nil {
sp := location.DefaultBackend.Spec.Ports[0] 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 { if len(endps) > 0 {
glog.V(3).Infof("using custom default backend in server %v location %v (service %v/%v)", 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) 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 upstreams[defUpstreamName] = du
for _, ing := range data { for _, ing := range data {
secUpstream := n.annotations.SecureUpstream(ing) anns := n.getIngressAnnotations(ing)
hz := n.annotations.HealthCheck(ing)
serviceUpstream := n.annotations.ServiceUpstream(ing)
upstreamHashBy := n.annotations.UpstreamHashBy(ing)
var defBackend string var defBackend string
if ing.Spec.Backend != nil { 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 // Add the service cluster endpoint as the upstream instead of individual endpoints
// if the serviceUpstream annotation is enabled // if the serviceUpstream annotation is enabled
if serviceUpstream { if anns.ServiceUpstream {
endpoint, err := n.getServiceClusterEndpoint(svcKey, ing.Spec.Backend) endpoint, err := n.getServiceClusterEndpoint(svcKey, ing.Spec.Backend)
if err != nil { if err != nil {
glog.Errorf("Failed to get service cluster endpoint for service %s: %v", svcKey, err) 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 { 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...) upstreams[defBackend].Endpoints = append(upstreams[defBackend].Endpoints, endps...)
if err != nil { if err != nil {
glog.Warningf("error creating upstream %v: %v", defBackend, err) 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 upstreams[name].Port = path.Backend.ServicePort
if !upstreams[name].Secure { if !upstreams[name].Secure {
upstreams[name].Secure = secUpstream.Secure upstreams[name].Secure = anns.SecureUpstream.Secure
} }
if upstreams[name].SecureCACert.Secret == "" { if upstreams[name].SecureCACert.Secret == "" {
upstreams[name].SecureCACert = secUpstream.CACert upstreams[name].SecureCACert = anns.SecureUpstream.CACert
} }
if upstreams[name].UpstreamHashBy == "" { if upstreams[name].UpstreamHashBy == "" {
upstreams[name].UpstreamHashBy = upstreamHashBy upstreams[name].UpstreamHashBy = anns.UpstreamHashBy
} }
svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName) svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName)
// Add the service cluster endpoint as the upstream instead of individual endpoints // Add the service cluster endpoint as the upstream instead of individual endpoints
// if the serviceUpstream annotation is enabled // if the serviceUpstream annotation is enabled
if serviceUpstream { if anns.ServiceUpstream {
endpoint, err := n.getServiceClusterEndpoint(svcKey, &path.Backend) endpoint, err := n.getServiceClusterEndpoint(svcKey, &path.Backend)
if err != nil { if err != nil {
glog.Errorf("failed to get service cluster endpoint for service %s: %v", svcKey, err) 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 { 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 { if err != nil {
glog.Warningf("error obtaining service endpoints: %v", err) glog.Warningf("error obtaining service endpoints: %v", err)
continue continue
@ -759,7 +778,7 @@ func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *exte
// serviceEndpoints returns the upstream servers (endpoints) associated // serviceEndpoints returns the upstream servers (endpoints) associated
// to a service. // to a service.
func (n *NGINXController) serviceEndpoints(svcKey, backendPort string, 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) svc, err := n.listers.Service.GetByName(svcKey)
var upstreams []ingress.Endpoint var upstreams []ingress.Endpoint
@ -843,7 +862,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
aliases := make(map[string]string, len(data)) aliases := make(map[string]string, len(data))
bdef := n.GetDefaultBackend() bdef := n.GetDefaultBackend()
ngxProxy := proxy.Configuration{ ngxProxy := proxy.Config{
BodySize: bdef.ProxyBodySize, BodySize: bdef.ProxyBodySize,
ConnectTimeout: bdef.ProxyConnectTimeout, ConnectTimeout: bdef.ProxyConnectTimeout,
SendTimeout: bdef.ProxySendTimeout, SendTimeout: bdef.ProxySendTimeout,
@ -884,9 +903,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
// initialize all the servers // initialize all the servers
for _, ing := range data { for _, ing := range data {
anns := n.getIngressAnnotations(ing)
// check if ssl passthrough is configured
sslpt := n.annotations.SSLPassthrough(ing)
// default upstream server // default upstream server
un := du.Name un := du.Name
@ -930,16 +947,14 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
Service: &apiv1.Service{}, Service: &apiv1.Service{},
}, },
}, },
SSLPassthrough: sslpt, SSLPassthrough: anns.SSLPassthrough,
} }
} }
} }
// configure default location, alias, and SSL // configure default location, alias, and SSL
for _, ing := range data { for _, ing := range data {
// setup server-alias based on annotations anns := n.getIngressAnnotations(ing)
aliasAnnotation := n.annotations.Alias(ing)
srvsnippet := n.annotations.ServerSnippet(ing)
for _, rule := range ing.Spec.Rules { for _, rule := range ing.Spec.Rules {
host := rule.Host host := rule.Host
@ -948,11 +963,11 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
} }
// setup server aliases // setup server aliases
if aliasAnnotation != "" { if anns.Alias != "" {
if servers[host].Alias == "" { if servers[host].Alias == "" {
servers[host].Alias = aliasAnnotation servers[host].Alias = anns.Alias
if _, ok := aliases[aliasAnnotation]; !ok { if _, ok := aliases["Alias"]; !ok {
aliases[aliasAnnotation] = host aliases["Alias"] = host
} }
} else { } else {
glog.Warningf("ingress %v/%v for host %v contains an Alias but one has already been configured.", 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. //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.", glog.Warningf("ingress %v/%v for host %v contains a Server Snippet section that it has already been configured.",
ing.Namespace, ing.Name, host) ing.Namespace, ing.Name, host)
} }
// only add a server snippet if the server does not have one previously configured // only add a server snippet if the server does not have one previously configured
if servers[host].ServerSnippet == "" && srvsnippet != "" { if servers[host].ServerSnippet == "" && anns.ServerSnippet != "" {
servers[host].ServerSnippet = srvsnippet servers[host].ServerSnippet = anns.ServerSnippet
} }
// only add a certificate if the server does not have one previously configured // only add a certificate if the server does not have one previously configured
@ -1044,7 +1059,7 @@ func (n *NGINXController) getEndpoints(
s *apiv1.Service, s *apiv1.Service,
servicePort *apiv1.ServicePort, servicePort *apiv1.ServicePort,
proto apiv1.Protocol, proto apiv1.Protocol,
hz *healthcheck.Upstream) []ingress.Endpoint { hz *healthcheck.Config) []ingress.Endpoint {
upsServers := []ingress.Endpoint{} upsServers := []ingress.Endpoint{}
@ -1152,6 +1167,7 @@ func (n *NGINXController) isForceReload() bool {
return atomic.LoadInt32(&n.forceReload) != 0 return atomic.LoadInt32(&n.forceReload) != 0
} }
// SetForceReload sets if the ingress controller should be reloaded or not
func (n *NGINXController) SetForceReload(shouldReload bool) { func (n *NGINXController) SetForceReload(shouldReload bool) {
if shouldReload { if shouldReload {
atomic.StoreInt32(&n.forceReload, 1) atomic.StoreInt32(&n.forceReload, 1)
@ -1160,3 +1176,24 @@ func (n *NGINXController) SetForceReload(shouldReload bool) {
atomic.StoreInt32(&n.forceReload, 0) 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/fields"
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/cache" "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"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class" "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 // from here to the end of the method all the code is just boilerplate
// required to watch Ingress, Secrets, ConfigMaps and Endoints. // required to watch Ingress, Secrets, ConfigMaps and Endoints.
// This is used to detect new content, updates or removals and act accordingly // 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 return
} }
n.extractAnnotations(addIng)
n.recorder.Eventf(addIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name)) n.recorder.Eventf(addIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name))
n.syncQueue.Enqueue(obj) 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.recorder.Eventf(curIng, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
} }
n.extractAnnotations(curIng)
n.syncQueue.Enqueue(cur) 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) key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
n.sslCertTracker.DeleteAll(key) n.sslCertTracker.Delete(key)
n.syncQueue.Enqueue(key) n.syncQueue.Enqueue(key)
}, },
} }
@ -196,6 +199,7 @@ func (n *NGINXController) createListers(stopCh chan struct{}) *ingress.StoreList
} }
lister := &ingress.StoreLister{} lister := &ingress.StoreLister{}
lister.IngressAnnotation.Store = cache_client.NewStore(cache_client.DeletionHandlingMetaNamespaceKeyFunc)
controller := &cacheController{} 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()), cache.NewListWatchFromClient(n.cfg.Client.CoreV1().RESTClient(), "services", n.cfg.Namespace, fields.Everything()),
&apiv1.Service{}, n.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{}) &apiv1.Service{}, n.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
controller.Run(n.stopCh) return lister, controller
return lister
} }

View file

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

View file

@ -114,7 +114,7 @@ func TestBuildLocation(t *testing.T) {
for k, tc := range tmplFuncTestcases { for k, tc := range tmplFuncTestcases {
loc := &ingress.Location{ loc := &ingress.Location{
Path: tc.Path, Path: tc.Path,
Rewrite: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL}, Rewrite: rewrite.Config{Target: tc.Target, AddBaseURL: tc.AddBaseURL},
} }
newLoc := buildLocation(loc) newLoc := buildLocation(loc)
@ -128,7 +128,7 @@ func TestBuildProxyPass(t *testing.T) {
for k, tc := range tmplFuncTestcases { for k, tc := range tmplFuncTestcases {
loc := &ingress.Location{ loc := &ingress.Location{
Path: tc.Path, 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", Backend: "upstream-name",
} }
@ -141,7 +141,7 @@ func TestBuildProxyPass(t *testing.T) {
func TestBuildAuthResponseHeaders(t *testing.T) { func TestBuildAuthResponseHeaders(t *testing.T) {
loc := &ingress.Location{ 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) headers := buildAuthResponseHeaders(loc)
expected := []string{ expected := []string{

View file

@ -21,17 +21,12 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/imdario/mergo"
api "k8s.io/api/core/v1" api "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/util/sysctl" "k8s.io/kubernetes/pkg/util/sysctl"
"k8s.io/ingress-nginx/pkg/ingress" "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. // newUpstream creates an upstream without servers.
func newUpstream(name string) *ingress.Backend { func newUpstream(name string) *ingress.Backend {
return &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. // sysctlSomaxconn returns the value of net.core.somaxconn, i.e.
// maximum number of connections that can be queued for acceptance // maximum number of connections that can be queued for acceptance
// http://nginx.org/en/docs/http/ngx_http_core_module.html#listen // http://nginx.org/en/docs/http/ngx_http_core_module.html#listen

View file

@ -17,18 +17,7 @@ limitations under the License.
package controller package controller
import ( import (
"reflect"
"testing" "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{} type fakeError struct{}
@ -37,51 +26,6 @@ func (fe *fakeError) Error() string {
return "fakeError" 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) { func TestIntInSlice(t *testing.T) {
fooTests := []struct { fooTests := []struct {
i int i int

View file

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