Add Generic interface
This commit is contained in:
parent
f2b627486d
commit
5a8e090736
36 changed files with 58014 additions and 675 deletions
|
@ -30,5 +30,5 @@ script:
|
||||||
# enable kubernetes/ingress in
|
# enable kubernetes/ingress in
|
||||||
# coveralls.io and add cover task
|
# coveralls.io and add cover task
|
||||||
- make fmt lint vet test
|
- make fmt lint vet test
|
||||||
- make controllers controllers-images
|
#- make controllers controllers-images
|
||||||
#- make test-e2e
|
#- make test-e2e
|
||||||
|
|
|
@ -25,14 +25,16 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress"
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
|
||||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||||
ngx_template "k8s.io/ingress/controllers/nginx/pkg/template"
|
ngx_template "k8s.io/ingress/controllers/nginx/pkg/template"
|
||||||
"k8s.io/ingress/controllers/nginx/pkg/version"
|
"k8s.io/ingress/controllers/nginx/pkg/version"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -75,6 +77,8 @@ Error loading new template : %v
|
||||||
}
|
}
|
||||||
|
|
||||||
n.t = ngxTpl
|
n.t = ngxTpl
|
||||||
|
go n.Start()
|
||||||
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +89,7 @@ type NGINXController struct {
|
||||||
binary string
|
binary string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start ...
|
// Start start a new NGINX master process running in foreground.
|
||||||
func (n NGINXController) Start() {
|
func (n NGINXController) Start() {
|
||||||
glog.Info("starting NGINX process...")
|
glog.Info("starting NGINX process...")
|
||||||
cmd := exec.Command(n.binary, "-c", cfgPath)
|
cmd := exec.Command(n.binary, "-c", cfgPath)
|
||||||
|
@ -99,14 +103,13 @@ func (n NGINXController) Start() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop ...
|
// Reload checks if the running configuration file is different
|
||||||
func (n NGINXController) Stop() error {
|
// to the specified and reload nginx if required
|
||||||
n.t.Close()
|
func (n NGINXController) Reload(data []byte) ([]byte, error) {
|
||||||
return exec.Command(n.binary, "-s", "stop").Run()
|
if !n.isReloadRequired(data) {
|
||||||
}
|
return nil, fmt.Errorf("Reload not required")
|
||||||
|
}
|
||||||
|
|
||||||
// Restart ...
|
|
||||||
func (n NGINXController) Restart(data []byte) ([]byte, error) {
|
|
||||||
err := ioutil.WriteFile(cfgPath, data, 0644)
|
err := ioutil.WriteFile(cfgPath, data, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -120,15 +123,15 @@ func (n NGINXController) Test(file string) *exec.Cmd {
|
||||||
return exec.Command(n.binary, "-t", "-c", file)
|
return exec.Command(n.binary, "-t", "-c", file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpstreamDefaults returns the nginx defaults
|
// BackendDefaults returns the nginx defaults
|
||||||
func (n NGINXController) UpstreamDefaults() defaults.Backend {
|
func (n NGINXController) BackendDefaults() defaults.Backend {
|
||||||
d := config.NewDefault()
|
d := config.NewDefault()
|
||||||
return d.Backend
|
return d.Backend
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsReloadRequired check if the new configuration file is different
|
// IsReloadRequired check if the new configuration file is different
|
||||||
// from the current one.
|
// from the current one.
|
||||||
func (n NGINXController) IsReloadRequired(data []byte) bool {
|
func (n NGINXController) isReloadRequired(data []byte) bool {
|
||||||
in, err := os.Open(cfgPath)
|
in, err := os.Open(cfgPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
@ -167,8 +170,13 @@ func (n NGINXController) IsReloadRequired(data []byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info return build information
|
// Info return build information
|
||||||
func (n NGINXController) Info() string {
|
func (n NGINXController) Info() *ingress.BackendInfo {
|
||||||
return fmt.Sprintf("build version %v from repo %v commit %v", version.RELEASE, version.REPO, version.COMMIT)
|
return &ingress.BackendInfo{
|
||||||
|
Name: "NGINX",
|
||||||
|
Release: version.RELEASE,
|
||||||
|
Build: version.COMMIT,
|
||||||
|
Repository: version.REPO,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// testTemplate checks if the NGINX configuration inside the byte array is valid
|
// testTemplate checks if the NGINX configuration inside the byte array is valid
|
||||||
|
@ -183,12 +191,13 @@ func (n NGINXController) testTemplate(cfg []byte) error {
|
||||||
out, err := n.Test(tmpfile.Name()).CombinedOutput()
|
out, err := n.Test(tmpfile.Name()).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// this error is different from the rest because it must be clear why nginx is not working
|
// this error is different from the rest because it must be clear why nginx is not working
|
||||||
return fmt.Errorf(`
|
oe := fmt.Sprintf(`
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
Error: %v
|
Error: %v
|
||||||
%v
|
%v
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
`, err, string(out))
|
`, err, string(out))
|
||||||
|
return errors.New(oe)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Remove(tmpfile.Name())
|
os.Remove(tmpfile.Name())
|
||||||
|
@ -207,9 +216,9 @@ func (n NGINXController) OnUpdate(cmap *api.ConfigMap, ingressCfg ingress.Config
|
||||||
var longestName int
|
var longestName int
|
||||||
var serverNames int
|
var serverNames int
|
||||||
for _, srv := range ingressCfg.Servers {
|
for _, srv := range ingressCfg.Servers {
|
||||||
serverNames += len([]byte(srv.Name))
|
serverNames += len([]byte(srv.Hostname))
|
||||||
if longestName < len(srv.Name) {
|
if longestName < len(srv.Hostname) {
|
||||||
longestName = len(srv.Name)
|
longestName = len(srv.Hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,21 +243,17 @@ func (n NGINXController) OnUpdate(cmap *api.ConfigMap, ingressCfg ingress.Config
|
||||||
cfg.ServerNameHashMaxSize = serverNameHashMaxSize
|
cfg.ServerNameHashMaxSize = serverNameHashMaxSize
|
||||||
}
|
}
|
||||||
|
|
||||||
conf := make(map[string]interface{})
|
return n.t.Write(config.TemplateConfig{
|
||||||
// adjust the size of the backlog
|
BacklogSize: sysctlSomaxconn(),
|
||||||
conf["backlogSize"] = sysctlSomaxconn()
|
Backends: ingressCfg.Backends,
|
||||||
conf["upstreams"] = ingressCfg.Upstreams
|
PassthrougBackends: ingressCfg.PassthroughBackends,
|
||||||
conf["passthroughUpstreams"] = ingressCfg.PassthroughUpstreams
|
Servers: ingressCfg.Servers,
|
||||||
conf["servers"] = ingressCfg.Servers
|
TCPBackends: ingressCfg.TCPEndpoints,
|
||||||
conf["tcpUpstreams"] = ingressCfg.TCPEndpoints
|
UDPBackends: ingressCfg.UPDEndpoints,
|
||||||
conf["udpUpstreams"] = ingressCfg.UPDEndpoints
|
HealthzURI: "/healthz",
|
||||||
conf["healthzURL"] = ingressCfg.HealthzURL
|
CustomErrors: len(cfg.CustomHTTPErrors) > 0,
|
||||||
conf["defResolver"] = cfg.Resolver
|
Cfg: cfg,
|
||||||
conf["sslDHParam"] = ""
|
}, n.testTemplate)
|
||||||
conf["customErrors"] = len(cfg.CustomHTTPErrors) > 0
|
|
||||||
conf["cfg"] = ngx_template.StandarizeKeyNames(cfg)
|
|
||||||
|
|
||||||
return n.t.Write(conf, n.testTemplate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||||
|
|
|
@ -19,9 +19,10 @@ package config
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -216,8 +217,7 @@ type Configuration struct {
|
||||||
WorkerProcesses int `structs:"worker-processes,omitempty"`
|
WorkerProcesses int `structs:"worker-processes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefault returns the default configuration contained
|
// NewDefault returns the default nginx configuration
|
||||||
// in the file default-conf.json
|
|
||||||
func NewDefault() Configuration {
|
func NewDefault() Configuration {
|
||||||
cfg := Configuration{
|
cfg := Configuration{
|
||||||
BodySize: bodySize,
|
BodySize: bodySize,
|
||||||
|
@ -264,3 +264,15 @@ func NewDefault() Configuration {
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TemplateConfig struct {
|
||||||
|
BacklogSize int
|
||||||
|
Backends []*ingress.Backend
|
||||||
|
PassthrougBackends []*ingress.SSLPassthroughBackend
|
||||||
|
Servers []*ingress.Server
|
||||||
|
TCPBackends []*ingress.Location
|
||||||
|
UDPBackends []*ingress.Location
|
||||||
|
HealthzURI string
|
||||||
|
CustomErrors bool
|
||||||
|
Cfg Configuration
|
||||||
|
}
|
||||||
|
|
|
@ -27,10 +27,10 @@ import (
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
go_camelcase "github.com/segmentio/go-camelcase"
|
go_camelcase "github.com/segmentio/go-camelcase"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
|
||||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -50,9 +50,9 @@ func ReadConfig(conf *api.ConfigMap) config.Configuration {
|
||||||
return config.NewDefault()
|
return config.NewDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
var errors []int
|
errors := make([]int, 0)
|
||||||
var skipUrls []string
|
skipUrls := make([]string, 0)
|
||||||
var whitelist []string
|
whitelist := make([]string, 0)
|
||||||
|
|
||||||
if val, ok := conf.Data[customHTTPErrors]; ok {
|
if val, ok := conf.Data[customHTTPErrors]; ok {
|
||||||
delete(conf.Data, customHTTPErrors)
|
delete(conf.Data, customHTTPErrors)
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||||
"k8s.io/ingress/core/pkg/ingress"
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
"k8s.io/ingress/core/pkg/watch"
|
"k8s.io/ingress/core/pkg/watch"
|
||||||
)
|
)
|
||||||
|
@ -73,12 +74,19 @@ func (t *Template) Close() {
|
||||||
|
|
||||||
// Write populates a buffer using a template with NGINX configuration
|
// Write populates a buffer using a template with NGINX configuration
|
||||||
// and the servers and upstreams created by Ingress rules
|
// and the servers and upstreams created by Ingress rules
|
||||||
func (t *Template) Write(conf map[string]interface{},
|
func (t *Template) Write(conf config.TemplateConfig, isValidTemplate func([]byte) error) ([]byte, error) {
|
||||||
isValidTemplate func([]byte) error) ([]byte, error) {
|
|
||||||
|
|
||||||
defer t.tmplBuf.Reset()
|
defer t.tmplBuf.Reset()
|
||||||
defer t.outCmdBuf.Reset()
|
defer t.outCmdBuf.Reset()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if t.s < t.tmplBuf.Cap() {
|
||||||
|
glog.V(2).Infof("adjusting template buffer size from %v to %v", t.s, t.tmplBuf.Cap())
|
||||||
|
t.s = t.tmplBuf.Cap()
|
||||||
|
t.tmplBuf = bytes.NewBuffer(make([]byte, 0, t.tmplBuf.Cap()))
|
||||||
|
t.outCmdBuf = bytes.NewBuffer(make([]byte, 0, t.outCmdBuf.Cap()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if glog.V(3) {
|
if glog.V(3) {
|
||||||
b, err := json.Marshal(conf)
|
b, err := json.Marshal(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -88,12 +96,8 @@ func (t *Template) Write(conf map[string]interface{},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := t.tmpl.Execute(t.tmplBuf, conf)
|
err := t.tmpl.Execute(t.tmplBuf, conf)
|
||||||
|
if err != nil {
|
||||||
if t.s < t.tmplBuf.Cap() {
|
return nil, err
|
||||||
glog.V(2).Infof("adjusting template buffer size from %v to %v", t.s, t.tmplBuf.Cap())
|
|
||||||
t.s = t.tmplBuf.Cap()
|
|
||||||
t.tmplBuf = bytes.NewBuffer(make([]byte, 0, t.tmplBuf.Cap()))
|
|
||||||
t.outCmdBuf = bytes.NewBuffer(make([]byte, 0, t.outCmdBuf.Cap()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// squeezes multiple adjacent empty lines to be single
|
// squeezes multiple adjacent empty lines to be single
|
||||||
|
@ -124,12 +128,12 @@ var (
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
"buildLocation": buildLocation,
|
"buildLocation": buildLocation,
|
||||||
"buildAuthLocation": buildAuthLocation,
|
"buildAuthLocation": buildAuthLocation,
|
||||||
"buildProxyPass": buildProxyPass,
|
"buildProxyPass": buildProxyPass,
|
||||||
"buildRateLimitZones": buildRateLimitZones,
|
"buildRateLimitZones": buildRateLimitZones,
|
||||||
"buildRateLimit": buildRateLimit,
|
"buildRateLimit": buildRateLimit,
|
||||||
"getSSPassthroughUpstream": getSSPassthroughUpstream,
|
"buildSSPassthroughUpstreams": buildSSPassthroughUpstreams,
|
||||||
|
|
||||||
"contains": strings.Contains,
|
"contains": strings.Contains,
|
||||||
"hasPrefix": strings.HasPrefix,
|
"hasPrefix": strings.HasPrefix,
|
||||||
|
@ -139,13 +143,32 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func getSSPassthroughUpstream(input interface{}) string {
|
func buildSSPassthroughUpstreams(b interface{}, sslb interface{}) string {
|
||||||
s, ok := input.(*ingress.Server)
|
backends := b.([]*ingress.Backend)
|
||||||
if !ok {
|
sslBackends := sslb.([]*ingress.SSLPassthroughBackend)
|
||||||
return ""
|
buf := bytes.NewBuffer(make([]byte, 0, 10))
|
||||||
|
|
||||||
|
// multiple services can use the same upstream.
|
||||||
|
// avoid duplications using a map[name]=true
|
||||||
|
u := make(map[string]bool)
|
||||||
|
for _, passthrough := range sslBackends {
|
||||||
|
if u[passthrough.Backend] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
u[passthrough.Backend] = true
|
||||||
|
fmt.Fprintf(buf, "upstream %v {\n", passthrough.Backend)
|
||||||
|
for _, backend := range backends {
|
||||||
|
if backend.Name == passthrough.Backend {
|
||||||
|
for _, server := range backend.Endpoints {
|
||||||
|
fmt.Fprintf(buf, "\t\tserver %v:%v;\n", server.Address, server.Port)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprint(buf, "\t}\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Name
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildLocation produces the location string, if the ingress has redirects
|
// buildLocation produces the location string, if the ingress has redirects
|
||||||
|
@ -184,20 +207,27 @@ func buildAuthLocation(input interface{}) string {
|
||||||
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
|
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
|
||||||
// If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will
|
// If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will
|
||||||
// add a base tag in the head of the response from the service
|
// add a base tag in the head of the response from the service
|
||||||
func buildProxyPass(input interface{}) string {
|
func buildProxyPass(b interface{}, loc interface{}) string {
|
||||||
location, ok := input.(*ingress.Location)
|
backends := b.([]*ingress.Backend)
|
||||||
|
location, ok := loc.(*ingress.Location)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
path := location.Path
|
path := location.Path
|
||||||
|
|
||||||
proto := "http"
|
proto := "http"
|
||||||
if location.SecureUpstream {
|
|
||||||
proto = "https"
|
for _, backend := range backends {
|
||||||
|
if backend.Name == location.Backend {
|
||||||
|
if backend.Secure {
|
||||||
|
proto = "https"
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// defProxyPass returns the default proxy_pass, just the name of the upstream
|
// defProxyPass returns the default proxy_pass, just the name of the upstream
|
||||||
defProxyPass := fmt.Sprintf("proxy_pass %s://%s;", proto, location.Backend.Name)
|
defProxyPass := fmt.Sprintf("proxy_pass %s://%s;", proto, location.Backend)
|
||||||
// if the path in the ingress rule is equals to the target: no special rewrite
|
// if the path in the ingress rule is equals to the target: no special rewrite
|
||||||
if path == location.Redirect.Target {
|
if path == location.Redirect.Target {
|
||||||
return defProxyPass
|
return defProxyPass
|
||||||
|
@ -227,13 +257,13 @@ func buildProxyPass(input interface{}) string {
|
||||||
rewrite %s(.*) /$1 break;
|
rewrite %s(.*) /$1 break;
|
||||||
rewrite %s / break;
|
rewrite %s / break;
|
||||||
proxy_pass %s://%s;
|
proxy_pass %s://%s;
|
||||||
%v`, path, location.Path, proto, location.Backend.Name, abu)
|
%v`, path, location.Path, proto, location.Backend, abu)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
rewrite %s(.*) %s/$1 break;
|
rewrite %s(.*) %s/$1 break;
|
||||||
proxy_pass %s://%s;
|
proxy_pass %s://%s;
|
||||||
%v`, path, location.Redirect.Target, proto, location.Backend.Name, abu)
|
%v`, path, location.Redirect.Target, proto, location.Backend, abu)
|
||||||
}
|
}
|
||||||
|
|
||||||
// default proxy_pass
|
// default proxy_pass
|
||||||
|
|
|
@ -17,14 +17,21 @@ limitations under the License.
|
||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||||
"k8s.io/ingress/core/pkg/ingress"
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// TODO: add tests for secure endpoints
|
||||||
tmplFuncTestcases = map[string]struct {
|
tmplFuncTestcases = map[string]struct {
|
||||||
Path string
|
Path string
|
||||||
Target string
|
Target string
|
||||||
|
@ -88,12 +95,77 @@ func TestBuildProxyPass(t *testing.T) {
|
||||||
loc := &ingress.Location{
|
loc := &ingress.Location{
|
||||||
Path: tc.Path,
|
Path: tc.Path,
|
||||||
Redirect: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL},
|
Redirect: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL},
|
||||||
Upstream: ingress.Backend{Name: "upstream-name"},
|
Backend: "upstream-name",
|
||||||
}
|
}
|
||||||
|
|
||||||
pp := buildProxyPass(loc)
|
pp := buildProxyPass([]*ingress.Backend{}, loc)
|
||||||
if !strings.EqualFold(tc.ProxyPass, pp) {
|
if !strings.EqualFold(tc.ProxyPass, pp) {
|
||||||
t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp)
|
t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateWithData(t *testing.T) {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
f, err := os.Open(path.Join(pwd, "../../test/data/config.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error reading json file: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
data, err := ioutil.ReadFile(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Error("unexpected error reading json file: ", err)
|
||||||
|
}
|
||||||
|
var dat config.TemplateConfig
|
||||||
|
if err := json.Unmarshal(data, &dat); err != nil {
|
||||||
|
t.Errorf("unexpected error unmarshalling json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tf, err := os.Open(path.Join(pwd, "../../rootfs/etc/nginx/template/nginx.tmpl"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error reading json file: %v", err)
|
||||||
|
}
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
ngxTpl, err := NewTemplate(tf.Name(), func() {})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("invalid NGINX template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ngxTpl.Write(dat, func(b []byte) error { return nil })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("invalid NGINX template: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkTemplateWithData(b *testing.B) {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
f, err := os.Open(path.Join(pwd, "../../test/data/config.json"))
|
||||||
|
if err != nil {
|
||||||
|
b.Errorf("unexpected error reading json file: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
data, err := ioutil.ReadFile(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
b.Error("unexpected error reading json file: ", err)
|
||||||
|
}
|
||||||
|
var dat config.TemplateConfig
|
||||||
|
if err := json.Unmarshal(data, &dat); err != nil {
|
||||||
|
b.Errorf("unexpected error unmarshalling json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tf, err := os.Open(path.Join(pwd, "../../rootfs/etc/nginx/template/nginx.tmpl"))
|
||||||
|
if err != nil {
|
||||||
|
b.Errorf("unexpected error reading json file: %v", err)
|
||||||
|
}
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
ngxTpl, err := NewTemplate(tf.Name(), func() {})
|
||||||
|
if err != nil {
|
||||||
|
b.Errorf("invalid NGINX template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ngxTpl.Write(dat, func(b []byte) error { return nil })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
FROM gcr.io/google_containers/nginx-slim:0.10
|
FROM gcr.io/google_containers/nginx-slim:0.11
|
||||||
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
|
||||||
diffutils \
|
diffutils \
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
{{ $cfg := .cfg }}{{ $healthzURL := .healthzURL }}
|
{{ $cfg := .Cfg }}{{ $healthzURI := .HealthzURI }}{{ $backends := .Backends }}
|
||||||
daemon off;
|
daemon off;
|
||||||
|
|
||||||
worker_processes {{ $cfg.workerProcesses }};
|
worker_processes {{ $cfg.WorkerProcesses }};
|
||||||
pid /run/nginx.pid;
|
pid /run/nginx.pid;
|
||||||
worker_rlimit_nofile 131072;
|
worker_rlimit_nofile 131072;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
multi_accept on;
|
multi_accept on;
|
||||||
worker_connections {{ $cfg.maxWorkerConnections }};
|
worker_connections {{ $cfg.MaxWorkerConnections }};
|
||||||
use epoll;
|
use epoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
{{/* we use the value of the header X-Forwarded-For to be able to use the geo_ip module */}}
|
{{/* we use the value of the header X-Forwarded-For to be able to use the geo_ip module */}}
|
||||||
{{ if $cfg.useProxyProtocol }}
|
{{ if $cfg.UseProxyProtocol }}
|
||||||
set_real_ip_from {{ $cfg.proxyRealIpCidr }};
|
set_real_ip_from {{ $cfg.ProxyRealIpCidr }};
|
||||||
real_ip_header proxy_protocol;
|
real_ip_header proxy_protocol;
|
||||||
{{ else }}
|
{{ else }}
|
||||||
real_ip_header X-Forwarded-For;
|
real_ip_header X-Forwarded-For;
|
||||||
|
@ -30,8 +30,8 @@ http {
|
||||||
geoip_city /etc/nginx/GeoLiteCity.dat;
|
geoip_city /etc/nginx/GeoLiteCity.dat;
|
||||||
geoip_proxy_recursive on;
|
geoip_proxy_recursive on;
|
||||||
|
|
||||||
{{ if $cfg.enableVtsStatus }}
|
{{ if $cfg.EnableVtsStatus }}
|
||||||
vhost_traffic_status_zone shared:vhost_traffic_status:{{ $cfg.vtsStatusZoneSize }};
|
vhost_traffic_status_zone shared:vhost_traffic_status:{{ $cfg.VtsStatusZoneSize }};
|
||||||
vhost_traffic_status_filter_by_set_key $geoip_country_code country::*;
|
vhost_traffic_status_filter_by_set_key $geoip_country_code country::*;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
@ -50,43 +50,43 @@ http {
|
||||||
|
|
||||||
reset_timedout_connection on;
|
reset_timedout_connection on;
|
||||||
|
|
||||||
keepalive_timeout {{ $cfg.keepAlive }}s;
|
keepalive_timeout {{ $cfg.KeepAlive }}s;
|
||||||
|
|
||||||
types_hash_max_size 2048;
|
types_hash_max_size 2048;
|
||||||
server_names_hash_max_size {{ $cfg.serverNameHashMaxSize }};
|
server_names_hash_max_size {{ $cfg.ServerNameHashMaxSize }};
|
||||||
server_names_hash_bucket_size {{ $cfg.serverNameHashBucketSize }};
|
server_names_hash_bucket_size {{ $cfg.ServerNameHashBucketSize }};
|
||||||
map_hash_bucket_size {{ $cfg.mapHashBucketSize }};
|
map_hash_bucket_size {{ $cfg.MapHashBucketSize }};
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
default_type text/html;
|
default_type text/html;
|
||||||
{{ if $cfg.useGzip }}
|
{{ if $cfg.UseGzip }}
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_comp_level 5;
|
gzip_comp_level 5;
|
||||||
gzip_http_version 1.1;
|
gzip_http_version 1.1;
|
||||||
gzip_min_length 256;
|
gzip_min_length 256;
|
||||||
gzip_types {{ $cfg.gzipTypes }};
|
gzip_types {{ $cfg.GzipTypes }};
|
||||||
gzip_proxied any;
|
gzip_proxied any;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
client_max_body_size "{{ $cfg.bodySize }}";
|
client_max_body_size "{{ $cfg.BodySize }}";
|
||||||
|
|
||||||
log_format upstreaminfo '{{ if $cfg.useProxyProtocol }}$proxy_protocol_addr{{ else }}$remote_addr{{ end }} - '
|
log_format upstreaminfo '{{ if $cfg.UseProxyProtocol }}$proxy_protocol_addr{{ else }}$remote_addr{{ end }} - '
|
||||||
'[$proxy_add_x_forwarded_for] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" '
|
'[$proxy_add_x_forwarded_for] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" '
|
||||||
'$request_length $request_time [$proxy_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status';
|
'$request_length $request_time [$proxy_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status';
|
||||||
|
|
||||||
{{/* map urls that should not appear in access.log */}}
|
{{/* map urls that should not appear in access.log */}}
|
||||||
{{/* http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log */}}
|
{{/* http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log */}}
|
||||||
map $request $loggable {
|
map $request $loggable {
|
||||||
{{ range $reqUri := $cfg.skipAccessLogUrls }}
|
{{ range $reqUri := $cfg.SkipAccessLogURLs }}
|
||||||
{{ $reqUri }} 0;{{ end }}
|
{{ $reqUri }} 0;{{ end }}
|
||||||
default 1;
|
default 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log upstreaminfo if=$loggable;
|
access_log /var/log/nginx/access.log upstreaminfo if=$loggable;
|
||||||
error_log /var/log/nginx/error.log {{ $cfg.errorLogLevel }};
|
error_log /var/log/nginx/error.log {{ $cfg.ErrorLogLevel }};
|
||||||
|
|
||||||
{{ if not (empty .defResolver) }}# Custom dns resolver.
|
{{ if not (empty $cfg.Resolver) }}# Custom dns resolver.
|
||||||
resolver {{ .defResolver }} valid=30s;
|
resolver {{ $cfg.Resolver }} valid=30s;
|
||||||
resolver_timeout 10s;
|
resolver_timeout 10s;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
@ -98,14 +98,14 @@ http {
|
||||||
{{/* normal nginx behavior we have to use this approach. */}}
|
{{/* normal nginx behavior we have to use this approach. */}}
|
||||||
# Retain the default nginx handling of requests without a "Connection" header
|
# Retain the default nginx handling of requests without a "Connection" header
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
default upgrade;
|
default upgrade;
|
||||||
'' close;
|
'' close;
|
||||||
}
|
}
|
||||||
|
|
||||||
# trust http_x_forwarded_proto headers correctly indicate ssl offloading
|
# trust http_x_forwarded_proto headers correctly indicate ssl offloading
|
||||||
map $http_x_forwarded_proto $pass_access_scheme {
|
map $http_x_forwarded_proto $pass_access_scheme {
|
||||||
default $http_x_forwarded_proto;
|
default $http_x_forwarded_proto;
|
||||||
'' $scheme;
|
'' $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Map a response error watching the header Content-Type
|
# Map a response error watching the header Content-Type
|
||||||
|
@ -124,51 +124,51 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
server_name_in_redirect off;
|
server_name_in_redirect off;
|
||||||
port_in_redirect off;
|
port_in_redirect off;
|
||||||
|
|
||||||
ssl_protocols {{ $cfg.sslProtocols }};
|
ssl_protocols {{ $cfg.SSLProtocols }};
|
||||||
|
|
||||||
# turn on session caching to drastically improve performance
|
# turn on session caching to drastically improve performance
|
||||||
{{ if $cfg.sslSessionCache }}
|
{{ if $cfg.SSLSessionCache }}
|
||||||
ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.sslSessionCacheSize }};
|
ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.SSLSessionCacheSize }};
|
||||||
ssl_session_timeout {{ $cfg.sslSessionTimeout }};
|
ssl_session_timeout {{ $cfg.SSLSessionTimeout }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
# allow configuring ssl session tickets
|
# allow configuring ssl session tickets
|
||||||
ssl_session_tickets {{ if $cfg.sslSessionTickets }}on{{ else }}off{{ end }};
|
ssl_session_tickets {{ if $cfg.SSLSessionTickets }}on{{ else }}off{{ end }};
|
||||||
|
|
||||||
# slightly reduce the time-to-first-byte
|
# slightly reduce the time-to-first-byte
|
||||||
ssl_buffer_size {{ $cfg.sslBufferSize }};
|
ssl_buffer_size {{ $cfg.SSLBufferSize }};
|
||||||
|
|
||||||
{{ if not (empty $cfg.sslCiphers) }}
|
{{ if not (empty $cfg.SSLCiphers) }}
|
||||||
# allow configuring custom ssl ciphers
|
# allow configuring custom ssl ciphers
|
||||||
ssl_ciphers '{{ $cfg.sslCiphers }}';
|
ssl_ciphers '{{ $cfg.SSLCiphers }}';
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if not (empty .sslDHParam) }}
|
{{ if not (empty $cfg.SSLDHParam) }}
|
||||||
# allow custom DH file http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
|
# allow custom DH file http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
|
||||||
ssl_dhparam {{ .sslDHParam }};
|
ssl_dhparam {{ $cfg.SSLDHParam }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if not $cfg.enableDynamicTlsRecords }}
|
{{ if not $cfg.EnableDynamicTLSRecords }}
|
||||||
ssl_dyn_rec_size_lo 0;
|
ssl_dyn_rec_size_lo 0;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if .customErrors }}
|
{{ if .CustomErrors }}
|
||||||
# Custom error pages
|
# Custom error pages
|
||||||
proxy_intercept_errors on;
|
proxy_intercept_errors on;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ range $errCode := $cfg.customHttpErrors }}
|
{{ range $errCode := $cfg.CustomHTTPErrors }}
|
||||||
error_page {{ $errCode }} = @custom_{{ $errCode }};{{ end }}
|
error_page {{ $errCode }} = @custom_{{ $errCode }};{{ end }}
|
||||||
|
|
||||||
# In case of errors try the next upstream server before returning an error
|
# In case of errors try the next upstream server before returning an error
|
||||||
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504{{ if $cfg.retryNonIdempotent }} non_idempotent{{ end }};
|
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504{{ if $cfg.RetryNonIdempotent }} non_idempotent{{ end }};
|
||||||
|
|
||||||
{{range $name, $upstream := .upstreams}}
|
{{range $name, $upstream := $backends}}
|
||||||
upstream {{$upstream.Name}} {
|
upstream {{$upstream.Name}} {
|
||||||
{{ if $cfg.enableStickySessions }}
|
{{ if $cfg.EnableStickySessions }}
|
||||||
sticky hash=sha1 httponly;
|
sticky hash=sha1 httponly;
|
||||||
{{ else }}
|
{{ else }}
|
||||||
least_conn;
|
least_conn;
|
||||||
|
@ -180,26 +180,26 @@ http {
|
||||||
|
|
||||||
{{/* build all the required rate limit zones. Each annotation requires a dedicated zone */}}
|
{{/* build all the required rate limit zones. Each annotation requires a dedicated zone */}}
|
||||||
{{/* 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states */}}
|
{{/* 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states */}}
|
||||||
{{ range $zone := (buildRateLimitZones .servers) }}
|
{{ range $zone := (buildRateLimitZones .Servers) }}
|
||||||
{{ $zone }}
|
{{ $zone }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ range $server := .servers }}
|
{{ range $server := .Servers }}
|
||||||
server {
|
server {
|
||||||
server_name {{ $server.Name }};
|
server_name {{ $server.Hostname }};
|
||||||
listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};
|
listen 80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }};
|
||||||
{{ if $server.SSL }}listen 442 {{ if $cfg.useProxyProtocol }}proxy_protocol{{ end }} ssl {{ if $cfg.useHttp2 }}http2{{ end }};
|
{{ if not (empty $server.SSLCertificate) }}listen 442 {{ if $cfg.UseProxyProtocol }}proxy_protocol{{ end }} ssl {{ if $cfg.UseHttp2 }}http2{{ end }};
|
||||||
{{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
|
{{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
|
||||||
# PEM sha: {{ $server.SSLPemChecksum }}
|
# PEM sha: {{ $server.SSLPemChecksum }}
|
||||||
ssl_certificate {{ $server.SSLCertificate }};
|
ssl_certificate {{ $server.SSLCertificate }};
|
||||||
ssl_certificate_key {{ $server.SSLCertificate }};
|
ssl_certificate_key {{ $server.SSLCertificate }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if (and $server.SSL $cfg.hsts) }}
|
{{ if (and (not (empty $server.SSLCertificate)) $cfg.HSTS) }}
|
||||||
more_set_headers "Strict-Transport-Security: max-age={{ $cfg.hstsMaxAge }}{{ if $cfg.hstsIncludeSubdomains }}; includeSubDomains{{ end }}; preload";
|
more_set_headers "Strict-Transport-Security: max-age={{ $cfg.HSTSMaxAge }}{{ if $cfg.HSTSIncludeSubdomains }}; includeSubDomains{{ end }}; preload";
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if $cfg.enableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }}
|
{{ if $cfg.EnableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }}
|
||||||
|
|
||||||
{{ range $location := $server.Locations }}
|
{{ range $location := $server.Locations }}
|
||||||
{{ $path := buildLocation $location }}
|
{{ $path := buildLocation $location }}
|
||||||
|
@ -240,7 +240,7 @@ http {
|
||||||
auth_request {{ $authPath }};
|
auth_request {{ $authPath }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if (and $server.SSL $location.Redirect.SSLRedirect) }}
|
{{ if (and (not (empty $server.SSLCertificate)) $location.Redirect.SSLRedirect) }}
|
||||||
# enforce ssl on server side
|
# enforce ssl on server side
|
||||||
if ($scheme = http) {
|
if ($scheme = http) {
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
|
@ -256,7 +256,6 @@ http {
|
||||||
auth_basic "{{ $location.BasicDigestAuth.Realm }}";
|
auth_basic "{{ $location.BasicDigestAuth.Realm }}";
|
||||||
auth_basic_user_file {{ $location.BasicDigestAuth.File }};
|
auth_basic_user_file {{ $location.BasicDigestAuth.File }};
|
||||||
{{ else }}
|
{{ else }}
|
||||||
#TODO: add nginx-http-auth-digest module
|
|
||||||
auth_digest "{{ $location.BasicDigestAuth.Realm }}";
|
auth_digest "{{ $location.BasicDigestAuth.Realm }}";
|
||||||
auth_digest_user_file {{ $location.BasicDigestAuth.File }};
|
auth_digest_user_file {{ $location.BasicDigestAuth.File }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -300,14 +299,14 @@ http {
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
set $proxy_upstream_name "{{ $location.Backend.Name }}";
|
set $proxy_upstream_name "{{ $location.Backend }}";
|
||||||
{{ buildProxyPass $location }}
|
{{ buildProxyPass $backends $location }}
|
||||||
}
|
}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if eq $server.Name "_" }}
|
{{ if eq $server.Hostname "_" }}
|
||||||
# health checks in cloud providers require the use of port 80
|
# health checks in cloud providers require the use of port 80
|
||||||
location {{ $healthzURL }} {
|
location {{ $healthzURI }} {
|
||||||
access_log off;
|
access_log off;
|
||||||
return 200;
|
return 200;
|
||||||
}
|
}
|
||||||
|
@ -322,6 +321,7 @@ http {
|
||||||
stub_status on;
|
stub_status on;
|
||||||
}
|
}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ template "CUSTOM_ERRORS" $cfg }}
|
{{ template "CUSTOM_ERRORS" $cfg }}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,15 +332,15 @@ http {
|
||||||
# Use the port 18080 (random value just to avoid known ports) as default port for nginx.
|
# Use the port 18080 (random value just to avoid known ports) as default port for nginx.
|
||||||
# Changing this value requires a change in:
|
# Changing this value requires a change in:
|
||||||
# https://github.com/kubernetes/contrib/blob/master/ingress/controllers/nginx/nginx/command.go#L104
|
# https://github.com/kubernetes/contrib/blob/master/ingress/controllers/nginx/nginx/command.go#L104
|
||||||
listen 18080 default_server reuseport backlog={{ .backlogSize }};
|
listen 18080 default_server reuseport backlog={{ .BacklogSize }};
|
||||||
|
|
||||||
location {{ $healthzURL }} {
|
location {{ $healthzURI }} {
|
||||||
access_log off;
|
access_log off;
|
||||||
return 200;
|
return 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /nginx_status {
|
location /nginx_status {
|
||||||
{{ if $cfg.enableVtsStatus }}
|
{{ if $cfg.EnableVtsStatus }}
|
||||||
vhost_traffic_status_display;
|
vhost_traffic_status_display;
|
||||||
vhost_traffic_status_display_format html;
|
vhost_traffic_status_display_format html;
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
@ -362,7 +362,7 @@ http {
|
||||||
set $proxy_upstream_name "-";
|
set $proxy_upstream_name "-";
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
{{ if .customErrors }}
|
{{ if .CustomErrors }}
|
||||||
content_by_lua_block {
|
content_by_lua_block {
|
||||||
openURL(503)
|
openURL(503)
|
||||||
}
|
}
|
||||||
|
@ -376,15 +376,14 @@ http {
|
||||||
stream {
|
stream {
|
||||||
# map FQDN that requires SSL passthrough
|
# map FQDN that requires SSL passthrough
|
||||||
map $ssl_preread_server_name $stream_upstream {
|
map $ssl_preread_server_name $stream_upstream {
|
||||||
{{ range $i, $passthrough := .passthroughUpstreams }}
|
{{ range $i, $passthrough := .PassthrougBackends }}
|
||||||
{{ $passthrough.Host }} {{ $passthrough.Upstream.Name }}-{{ $i }}-pt;
|
{{ $passthrough.Hostname }} {{ $passthrough.Backend }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
# send SSL traffic to this nginx in a different port
|
# send SSL traffic to this nginx in a different port
|
||||||
default nginx-ssl-backend;
|
default nginx-ssl-backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_format log_stream '$remote_addr [$time_local] $protocol [$ssl_preread_server_name] [$stream_upstream] '
|
log_format log_stream '$remote_addr [$time_local] $protocol [$ssl_preread_server_name] [$stream_upstream] $status $bytes_sent $bytes_received $session_time';
|
||||||
'$status $bytes_sent $bytes_received $session_time';
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log log_stream;
|
access_log /var/log/nginx/access.log log_stream;
|
||||||
error_log /var/log/nginx/error.log;
|
error_log /var/log/nginx/error.log;
|
||||||
|
@ -394,56 +393,20 @@ stream {
|
||||||
server 127.0.0.1:442;
|
server 127.0.0.1:442;
|
||||||
}
|
}
|
||||||
|
|
||||||
{{ range $i, $passthrough := .passthroughUpstreams }}
|
{{ buildSSPassthroughUpstreams $backends .PassthrougBackends }}
|
||||||
upstream {{ $passthrough.Name }}-{{ $i }}-pt {
|
|
||||||
{{ range $server := $passthrough.Endpoints }}server {{ $server.Address }}:{{ $server.Port }};
|
|
||||||
{{ end }}
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443;
|
listen 443;
|
||||||
|
{{ if $cfg.UseProxyProtocol }}proxy_protocol on;{{ end }}
|
||||||
{{ if $cfg.useProxyProtocol }}proxy_protocol on;{{ end }}
|
|
||||||
|
|
||||||
proxy_pass $stream_upstream;
|
proxy_pass $stream_upstream;
|
||||||
ssl_preread on;
|
ssl_preread on;
|
||||||
}
|
}
|
||||||
|
|
||||||
# TCP services
|
|
||||||
{{ range $i, $tcpServer := .tcpUpstreams }}
|
|
||||||
upstream tcp-{{ $tcpServer.Upstream.Name }} {
|
|
||||||
{{ range $server := $tcpServer.Upstream.Endpoints }}server {{ $server.Address }}:{{ $server.Port }};
|
|
||||||
{{ end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen {{ $tcpServer.Path }};
|
|
||||||
proxy_connect_timeout {{ $tcpServer.Proxy.ConnectTimeout }}s;
|
|
||||||
proxy_timeout {{ $tcpServer.Proxy.ReadTimeout }}s;
|
|
||||||
proxy_pass tcp-{{ $tcpServer.Upstream.Name }};
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
# UDP services
|
|
||||||
{{ range $i, $udpServer := .udpUpstreams }}
|
|
||||||
upstream udp-{{ $udpServer.Upstream.Name }} {
|
|
||||||
{{ range $server := $udpServer.Upstream.Endpoints }}server {{ $server.Address }}:{{ $server.Port }};
|
|
||||||
{{ end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen {{ $udpServer.Path }} udp;
|
|
||||||
proxy_timeout 10s;
|
|
||||||
proxy_responses 1;
|
|
||||||
proxy_pass udp-{{ $udpServer.Upstream.Name }};
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{{/* definition of templates to avoid repetitions */}}
|
{{/* definition of templates to avoid repetitions */}}
|
||||||
{{ define "CUSTOM_ERRORS" }}
|
{{ define "CUSTOM_ERRORS" }}
|
||||||
{{ range $errCode := .customHttpErrors }}
|
{{ range $errCode := .CustomHTTPErrors }}
|
||||||
location @custom_{{ $errCode }} {
|
location @custom_{{ $errCode }} {
|
||||||
internal;
|
internal;
|
||||||
content_by_lua_block {
|
content_by_lua_block {
|
||||||
|
|
57259
controllers/nginx/test/data/config.json
Normal file
57259
controllers/nginx/test/data/config.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -23,10 +23,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -59,10 +59,10 @@ var (
|
||||||
|
|
||||||
// BasicDigest returns authentication configuration for an Ingress rule
|
// BasicDigest returns authentication configuration for an Ingress rule
|
||||||
type BasicDigest struct {
|
type BasicDigest struct {
|
||||||
Type string
|
Type string `json:"type"`
|
||||||
Realm string
|
Realm string `json:"realm"`
|
||||||
File string
|
File string `json:"file"`
|
||||||
Secured bool
|
Secured bool `json:"secured"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
|
|
@ -21,8 +21,9 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -34,9 +35,9 @@ const (
|
||||||
|
|
||||||
// External returns external authentication configuration for an Ingress rule
|
// External returns external authentication configuration for an Ingress rule
|
||||||
type External struct {
|
type External struct {
|
||||||
URL string
|
URL string `json:"url"`
|
||||||
Method string
|
Method string `json:"method"`
|
||||||
SendBody bool
|
SendBody bool `json:"sendBody"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -19,10 +19,10 @@ package authtls
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/k8s"
|
"k8s.io/ingress/core/pkg/k8s"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -32,11 +32,11 @@ const (
|
||||||
|
|
||||||
// SSLCert returns external authentication configuration for an Ingress rule
|
// SSLCert returns external authentication configuration for an Ingress rule
|
||||||
type SSLCert struct {
|
type SSLCert struct {
|
||||||
Secret string
|
Secret string `json:"secret"`
|
||||||
CertFileName string
|
CertFileName string `json:"certFilename"`
|
||||||
KeyFileName string
|
KeyFileName string `json:"keyFilename"`
|
||||||
CAFileName string
|
CAFileName string `json:"caFilename"`
|
||||||
PemSHA string
|
PemSHA string `json:"pemSha"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
|
|
@ -17,9 +17,9 @@ limitations under the License.
|
||||||
package cors
|
package cors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -31,8 +31,8 @@ const (
|
||||||
// Upstream returns the URL and method to use check the status of
|
// Upstream returns the URL and method to use check the status of
|
||||||
// the upstream server/s
|
// the upstream server/s
|
||||||
type Upstream struct {
|
type Upstream struct {
|
||||||
MaxFails int
|
MaxFails int `json:"maxFails"`
|
||||||
FailTimeout int
|
FailTimeout int `json:"failTimeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
|
|
@ -20,11 +20,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/util/net/sets"
|
"k8s.io/kubernetes/pkg/util/net/sets"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -39,7 +39,7 @@ var (
|
||||||
|
|
||||||
// SourceRange returns the CIDR
|
// SourceRange returns the CIDR
|
||||||
type SourceRange struct {
|
type SourceRange struct {
|
||||||
CIDR []string
|
CIDR []string `json:"cidr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
|
|
@ -32,10 +32,10 @@ const (
|
||||||
|
|
||||||
// Configuration returns the proxy timeout to use in the upstream server/s
|
// Configuration returns the proxy timeout to use in the upstream server/s
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
ConnectTimeout int
|
ConnectTimeout int `json:"conectTimeout"`
|
||||||
SendTimeout int
|
SendTimeout int `json:"sendTimeout"`
|
||||||
ReadTimeout int
|
ReadTimeout int `json:"readTimeout"`
|
||||||
BufferSize string
|
BufferSize string `json:"bufferSize"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
|
|
@ -20,9 +20,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -48,19 +48,19 @@ var (
|
||||||
// Note: Is possible to specify both limits
|
// Note: Is possible to specify both limits
|
||||||
type RateLimit struct {
|
type RateLimit 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
|
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
|
||||||
RPS Zone
|
RPS Zone `json:"rps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zone returns information about the NGINX rate limit (limit_req_zone)
|
// Zone returns information about the NGINX rate limit (limit_req_zone)
|
||||||
// http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone
|
// http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone
|
||||||
type Zone struct {
|
type Zone struct {
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Limit int
|
Limit int `json:"limit"`
|
||||||
Burst int
|
Burst int `json:"burst"`
|
||||||
// SharedSize amount of shared memory for the zone
|
// SharedSize amount of shared memory for the zone
|
||||||
SharedSize int
|
SharedSize int `json:"sharedSize"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
|
|
@ -19,10 +19,10 @@ package rewrite
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -34,12 +34,12 @@ const (
|
||||||
// Redirect describes the per location redirect config
|
// Redirect describes the per location redirect config
|
||||||
type Redirect struct {
|
type Redirect struct {
|
||||||
// Target URI where the traffic must be redirected
|
// Target URI where the traffic must be redirected
|
||||||
Target string
|
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
|
||||||
// of the responses from the upstream servers
|
// of the responses from the upstream servers
|
||||||
AddBaseURL bool
|
AddBaseURL bool `json:"addBaseUrl"`
|
||||||
// SSLRedirect indicates if the location section is accessible SSL only
|
// SSLRedirect indicates if the location section is accessible SSL only
|
||||||
SSLRedirect bool
|
SSLRedirect bool `json:"sslRedirect"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
|
|
@ -17,9 +17,9 @@ limitations under the License.
|
||||||
package secureupstream
|
package secureupstream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -19,10 +19,10 @@ package sslpassthrough
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -21,11 +21,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/client/cache"
|
"k8s.io/kubernetes/pkg/client/cache"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress"
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
ssl "k8s.io/ingress/core/pkg/net/ssl"
|
ssl "k8s.io/ingress/core/pkg/net/ssl"
|
||||||
|
|
|
@ -301,7 +301,7 @@ func (ic GenericController) Check(_ *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info returns information about the backend
|
// Info returns information about the backend
|
||||||
func (ic GenericController) Info() string {
|
func (ic GenericController) Info() *ingress.BackendInfo {
|
||||||
return ic.cfg.Backend.Info()
|
return ic.cfg.Backend.Info()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,31 +368,25 @@ func (ic *GenericController) sync(key interface{}) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
passUpstreams = append(passUpstreams, &ingress.SSLPassthroughBackend{
|
passUpstreams = append(passUpstreams, &ingress.SSLPassthroughBackend{
|
||||||
Upstream: loc.Backend,
|
Backend: loc.Backend,
|
||||||
Host: server.Name,
|
Hostname: server.Hostname,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ic.cfg.Backend.OnUpdate(cfg, ingress.Configuration{
|
data, err := ic.cfg.Backend.OnUpdate(cfg, ingress.Configuration{
|
||||||
HealthzURL: ic.cfg.DefaultHealthzURL,
|
Backends: upstreams,
|
||||||
Upstreams: upstreams,
|
Servers: servers,
|
||||||
Servers: servers,
|
TCPEndpoints: ic.getTCPServices(),
|
||||||
TCPEndpoints: ic.getTCPServices(),
|
UPDEndpoints: ic.getUDPServices(),
|
||||||
UPDEndpoints: ic.getUDPServices(),
|
PassthroughBackends: passUpstreams,
|
||||||
PassthroughUpstreams: passUpstreams,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ic.cfg.Backend.IsReloadRequired(data) {
|
out, err := ic.cfg.Backend.Reload(data)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("reloading ingress backend...")
|
|
||||||
out, err := ic.cfg.Backend.Restart(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
incReloadErrorCount()
|
incReloadErrorCount()
|
||||||
glog.Errorf("unexpected failure restarting the backend: \n%v", string(out))
|
glog.Errorf("unexpected failure restarting the backend: \n%v", string(out))
|
||||||
|
@ -517,11 +511,8 @@ func (ic *GenericController) getStreamServices(data map[string]string, proto api
|
||||||
}
|
}
|
||||||
|
|
||||||
svcs = append(svcs, &ingress.Location{
|
svcs = append(svcs, &ingress.Location{
|
||||||
Path: k,
|
Path: k,
|
||||||
Upstream: ingress.Backend{
|
Backend: fmt.Sprintf("%v-%v-%v", svcNs, svcName, port),
|
||||||
Name: fmt.Sprintf("%v-%v-%v", svcNs, svcName, port),
|
|
||||||
Endpoints: endps,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,7 +570,7 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
||||||
upstreams := ic.createUpstreams(ings)
|
upstreams := ic.createUpstreams(ings)
|
||||||
servers := ic.createServers(ings, upstreams)
|
servers := ic.createServers(ings, upstreams)
|
||||||
|
|
||||||
upsDefaults := ic.cfg.Backend.UpstreamDefaults()
|
upsDefaults := ic.cfg.Backend.BackendDefaults()
|
||||||
|
|
||||||
for _, ingIf := range ings {
|
for _, ingIf := range ings {
|
||||||
ing := ingIf.(*extensions.Ingress)
|
ing := ingIf.(*extensions.Ingress)
|
||||||
|
@ -596,11 +587,6 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
||||||
glog.V(5).Infof("error reading rate limit annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
glog.V(5).Infof("error reading rate limit annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secUpstream, err := secureupstream.ParseAnnotations(ing)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error reading secure upstream in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
locRew, err := rewrite.ParseAnnotations(upsDefaults, ing)
|
locRew, err := rewrite.ParseAnnotations(upsDefaults, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(5).Infof("error parsing rewrite annotations for Ingress rule %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
glog.V(5).Infof("error parsing rewrite annotations for Ingress rule %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
||||||
|
@ -666,7 +652,7 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
||||||
len(ing.Spec.TLS) == 0 &&
|
len(ing.Spec.TLS) == 0 &&
|
||||||
host != defServerName {
|
host != defServerName {
|
||||||
glog.V(3).Infof("ingress rule %v/%v does not contains HTTP or TLS rules. using default backend", ing.Namespace, ing.Name)
|
glog.V(3).Infof("ingress rule %v/%v does not contains HTTP or TLS rules. using default backend", ing.Namespace, ing.Name)
|
||||||
server.Locations[0].Upstream = *defBackend
|
server.Locations[0].Backend = defBackend.Name
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,19 +676,19 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
||||||
addLoc = false
|
addLoc = false
|
||||||
|
|
||||||
if !loc.IsDefBackend {
|
if !loc.IsDefBackend {
|
||||||
glog.V(3).Infof("avoiding replacement of ingress rule %v/%v location %v upstream %v (%v)", ing.Namespace, ing.Name, loc.Path, ups.Name, loc.Backend.Name)
|
glog.V(3).Infof("avoiding replacement of ingress rule %v/%v location %v upstream %v (%v)", ing.Namespace, ing.Name, loc.Path, ups.Name, loc.Backend)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(3).Infof("replacing ingress rule %v/%v location %v upstream %v (%v)", ing.Namespace, ing.Name, loc.Path, ups.Name, loc.Backend.Name)
|
glog.V(3).Infof("replacing ingress rule %v/%v location %v upstream %v (%v)", ing.Namespace, ing.Name, loc.Path, ups.Name, loc.Backend)
|
||||||
loc.Backend = *ups
|
loc.Backend = ups.Name
|
||||||
loc.IsDefBackend = false
|
loc.IsDefBackend = false
|
||||||
loc.BasicDigestAuth = *nginxAuth
|
loc.BasicDigestAuth = *nginxAuth
|
||||||
loc.RateLimit = *rl
|
loc.RateLimit = *rl
|
||||||
loc.Redirect = *locRew
|
loc.Redirect = *locRew
|
||||||
loc.SecureUpstream = secUpstream
|
//loc.SecureUpstream = secUpstream
|
||||||
loc.Whitelist = *wl
|
loc.Whitelist = *wl
|
||||||
loc.Backend = *ups
|
loc.Backend = ups.Name
|
||||||
loc.EnableCORS = eCORS
|
loc.EnableCORS = eCORS
|
||||||
loc.ExternalAuth = ra
|
loc.ExternalAuth = ra
|
||||||
loc.Proxy = *prx
|
loc.Proxy = *prx
|
||||||
|
@ -712,15 +698,15 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
||||||
}
|
}
|
||||||
// is a new location
|
// is a new location
|
||||||
if addLoc {
|
if addLoc {
|
||||||
glog.V(3).Infof("adding location %v in ingress rule %v/%v upstream", 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)
|
||||||
server.Locations = append(server.Locations, &ingress.Location{
|
server.Locations = append(server.Locations, &ingress.Location{
|
||||||
Path: nginxPath,
|
Path: nginxPath,
|
||||||
Upstream: *ups,
|
Backend: ups.Name,
|
||||||
IsDefBackend: false,
|
IsDefBackend: false,
|
||||||
BasicDigestAuth: *nginxAuth,
|
BasicDigestAuth: *nginxAuth,
|
||||||
RateLimit: *rl,
|
RateLimit: *rl,
|
||||||
Redirect: *locRew,
|
Redirect: *locRew,
|
||||||
SecureUpstream: secUpstream,
|
//SecureUpstream: secUpstream,
|
||||||
Whitelist: *wl,
|
Whitelist: *wl,
|
||||||
EnableCORS: eCORS,
|
EnableCORS: eCORS,
|
||||||
ExternalAuth: ra,
|
ExternalAuth: ra,
|
||||||
|
@ -776,10 +762,15 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
|
||||||
upstreams := make(map[string]*ingress.Backend)
|
upstreams := make(map[string]*ingress.Backend)
|
||||||
upstreams[defUpstreamName] = ic.getDefaultUpstream()
|
upstreams[defUpstreamName] = ic.getDefaultUpstream()
|
||||||
|
|
||||||
upsDefaults := ic.cfg.Backend.UpstreamDefaults()
|
upsDefaults := ic.cfg.Backend.BackendDefaults()
|
||||||
for _, ingIf := range data {
|
for _, ingIf := range data {
|
||||||
ing := ingIf.(*extensions.Ingress)
|
ing := ingIf.(*extensions.Ingress)
|
||||||
|
|
||||||
|
secUpstream, err := secureupstream.ParseAnnotations(ing)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(5).Infof("error reading secure upstream in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
hz := healthcheck.ParseAnnotations(upsDefaults, ing)
|
hz := healthcheck.ParseAnnotations(upsDefaults, ing)
|
||||||
|
|
||||||
var defBackend string
|
var defBackend string
|
||||||
|
@ -817,7 +808,9 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
|
||||||
|
|
||||||
glog.V(3).Infof("creating upstream %v", name)
|
glog.V(3).Infof("creating upstream %v", name)
|
||||||
upstreams[name] = newUpstream(name)
|
upstreams[name] = newUpstream(name)
|
||||||
|
if !upstreams[name].Secure {
|
||||||
|
upstreams[name].Secure = secUpstream
|
||||||
|
}
|
||||||
svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName)
|
svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName)
|
||||||
endp, err := ic.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz)
|
endp, err := ic.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -871,18 +864,18 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
||||||
|
|
||||||
func (ic *GenericController) createServers(data []interface{}, upstreams map[string]*ingress.Backend) map[string]*ingress.Server {
|
func (ic *GenericController) createServers(data []interface{}, upstreams map[string]*ingress.Backend) map[string]*ingress.Server {
|
||||||
servers := make(map[string]*ingress.Server)
|
servers := make(map[string]*ingress.Server)
|
||||||
ngxProxy := *proxy.ParseAnnotations(ic.cfg.Backend.UpstreamDefaults(), nil)
|
ngxProxy := *proxy.ParseAnnotations(ic.cfg.Backend.BackendDefaults(), nil)
|
||||||
|
|
||||||
upsDefaults := ic.cfg.Backend.UpstreamDefaults()
|
upsDefaults := ic.cfg.Backend.BackendDefaults()
|
||||||
|
|
||||||
// default server
|
// default server
|
||||||
servers[defServerName] = &ingress.Server{
|
servers[defServerName] = &ingress.Server{
|
||||||
Name: defServerName,
|
Hostname: defServerName,
|
||||||
Locations: []*ingress.Location{
|
Locations: []*ingress.Location{
|
||||||
{
|
{
|
||||||
Path: rootLocation,
|
Path: rootLocation,
|
||||||
IsDefBackend: true,
|
IsDefBackend: true,
|
||||||
Upstream: *ic.getDefaultUpstream(),
|
Backend: ic.getDefaultUpstream().Name,
|
||||||
Proxy: ngxProxy,
|
Proxy: ngxProxy,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -906,12 +899,12 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
servers[host] = &ingress.Server{
|
servers[host] = &ingress.Server{
|
||||||
Name: host,
|
Hostname: host,
|
||||||
Locations: []*ingress.Location{
|
Locations: []*ingress.Location{
|
||||||
{
|
{
|
||||||
Path: rootLocation,
|
Path: rootLocation,
|
||||||
IsDefBackend: true,
|
IsDefBackend: true,
|
||||||
Upstream: *ic.getDefaultUpstream(),
|
Backend: ic.getDefaultUpstream().Name,
|
||||||
Proxy: ngxProxy,
|
Proxy: ngxProxy,
|
||||||
},
|
},
|
||||||
}, SSLPassthrough: sslpt}
|
}, SSLPassthrough: sslpt}
|
||||||
|
@ -929,15 +922,13 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
||||||
}
|
}
|
||||||
|
|
||||||
// only add certificate if the server does not have one previously configured
|
// only add certificate if the server does not have one previously configured
|
||||||
if len(ing.Spec.TLS) > 0 && !servers[host].SSL {
|
if len(ing.Spec.TLS) > 0 && servers[host].SSLCertificate != "" {
|
||||||
key := fmt.Sprintf("%v/%v", ing.Namespace, ing.Spec.TLS[0].SecretName)
|
key := fmt.Sprintf("%v/%v", ing.Namespace, ing.Spec.TLS[0].SecretName)
|
||||||
bc, exists := ic.sslCertTracker.Get(key)
|
bc, exists := ic.sslCertTracker.Get(key)
|
||||||
if exists {
|
if exists {
|
||||||
cert := bc.(*ingress.SSLCert)
|
cert := bc.(*ingress.SSLCert)
|
||||||
if isHostValid(host, cert) {
|
if isHostValid(host, cert) {
|
||||||
servers[host].SSL = true
|
|
||||||
servers[host].SSLCertificate = cert.PemFileName
|
servers[host].SSLCertificate = cert.PemFileName
|
||||||
//servers[host].SSLCertificateKey = cert.PemFileName
|
|
||||||
servers[host].SSLPemChecksum = cert.PemSHA
|
servers[host].SSLPemChecksum = cert.PemSHA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -950,7 +941,7 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
||||||
ic.recorder.Eventf(ing, api.EventTypeWarning, "MAPPING", "error: rules with Spec.Backend are allowed only with hostnames")
|
ic.recorder.Eventf(ing, api.EventTypeWarning, "MAPPING", "error: rules with Spec.Backend are allowed only with hostnames")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
servers[host].Locations[0].Upstream = *backendUpstream
|
servers[host].Locations[0].Backend = backendUpstream.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1050,7 +1041,6 @@ func (ic GenericController) Stop() error {
|
||||||
// Start starts the Ingress controller.
|
// Start starts the Ingress controller.
|
||||||
func (ic GenericController) Start() {
|
func (ic GenericController) Start() {
|
||||||
glog.Infof("starting Ingress controller")
|
glog.Infof("starting Ingress controller")
|
||||||
go ic.cfg.Backend.Start()
|
|
||||||
|
|
||||||
go ic.ingController.Run(ic.stopCh)
|
go ic.ingController.Run(ic.stopCh)
|
||||||
go ic.endpController.Run(ic.stopCh)
|
go ic.endpController.Run(ic.stopCh)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -12,15 +13,15 @@ import (
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress"
|
|
||||||
"k8s.io/ingress/core/pkg/k8s"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
"k8s.io/kubernetes/pkg/healthz"
|
"k8s.io/kubernetes/pkg/healthz"
|
||||||
kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
|
"k8s.io/ingress/core/pkg/k8s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewIngressController returns a configured Ingress controller
|
// NewIngressController returns a configured Ingress controller
|
||||||
|
@ -160,7 +161,8 @@ func registerHandlers(enableProfiling bool, port int, ic *GenericController) {
|
||||||
|
|
||||||
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(w, ic.Info())
|
b, _ := json.Marshal(ic.Info())
|
||||||
|
w.Write(b)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -24,11 +24,11 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/service"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// checkSvcForUpdate verifies if one of the running pods for a service contains
|
// checkSvcForUpdate verifies if one of the running pods for a service contains
|
||||||
|
|
|
@ -19,10 +19,10 @@ package controller
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress"
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// newDefaultServer return an BackendServer to be use as default server that returns 503.
|
// newDefaultServer return an BackendServer to be use as default server that returns 503.
|
||||||
|
@ -33,7 +33,7 @@ func newDefaultServer() ingress.Endpoint {
|
||||||
// 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{
|
||||||
Name: name,
|
Name: name,
|
||||||
Endpoints: []ingress.Endpoint{},
|
Endpoints: []ingress.Endpoint{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
63
core/pkg/ingress/doc.go
Normal file
63
core/pkg/ingress/doc.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ingress
|
||||||
|
|
||||||
|
// This package contains the interface is required to implement to build an Ingress controller
|
||||||
|
// A dummy implementation could be
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// dc := newDummyController()
|
||||||
|
// controller.NewIngressController(dc)
|
||||||
|
// glog.Infof("shutting down Ingress controller...")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//where newDummyController returns and implementation of the Controller interface:
|
||||||
|
//
|
||||||
|
// func newDummyController() ingress.Controller {
|
||||||
|
// return &DummyController{}
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// type DummyController struct {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (dc DummyController) Reload(data []byte) ([]byte, error) {
|
||||||
|
// err := ioutil.WriteFile("/arbitrary-path", data, 0644)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return exec.Command("some command", "--reload").CombinedOutput()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (dc DummyController) Test(file string) *exec.Cmd {
|
||||||
|
// return exec.Command("some command", "--config-file", file)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (dc DummyController) OnUpdate(*api.ConfigMap, Configuration) ([]byte, error) {
|
||||||
|
// return []byte(`<string containing a configuration file>`)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (dc DummyController) BackendDefaults() defaults.Backend {
|
||||||
|
// return ingress.NewStandardDefaults()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (dc DummyController) Info() *BackendInfo {
|
||||||
|
// Name: "dummy",
|
||||||
|
// Release: "0.0.0",
|
||||||
|
// Build: "git-00000000",
|
||||||
|
// Repository: "git://foo.bar.com",
|
||||||
|
// }
|
85
core/pkg/ingress/sort_ingress.go
Normal file
85
core/pkg/ingress/sort_ingress.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ingress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackendByNameServers sorts upstreams by name
|
||||||
|
type BackendByNameServers []*Backend
|
||||||
|
|
||||||
|
func (c BackendByNameServers) Len() int { return len(c) }
|
||||||
|
func (c BackendByNameServers) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||||
|
func (c BackendByNameServers) Less(i, j int) bool {
|
||||||
|
|
||||||
|
return c[i].Name < c[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointByAddrPort sorts endpoints by address and port
|
||||||
|
type EndpointByAddrPort []Endpoint
|
||||||
|
|
||||||
|
func (c EndpointByAddrPort) Len() int { return len(c) }
|
||||||
|
func (c EndpointByAddrPort) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||||
|
func (c EndpointByAddrPort) Less(i, j int) bool {
|
||||||
|
iName := c[i].Address
|
||||||
|
jName := c[j].Address
|
||||||
|
if iName != jName {
|
||||||
|
return iName < jName
|
||||||
|
}
|
||||||
|
|
||||||
|
iU := c[i].Port
|
||||||
|
jU := c[j].Port
|
||||||
|
return iU < jU
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerByName sorts servers by name
|
||||||
|
type ServerByName []*Server
|
||||||
|
|
||||||
|
func (c ServerByName) Len() int { return len(c) }
|
||||||
|
func (c ServerByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||||
|
func (c ServerByName) Less(i, j int) bool {
|
||||||
|
return c[i].Hostname < c[j].Hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocationByPath sorts location by path in descending order
|
||||||
|
// Location / is the last one
|
||||||
|
type LocationByPath []*Location
|
||||||
|
|
||||||
|
func (c LocationByPath) Len() int { return len(c) }
|
||||||
|
func (c LocationByPath) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||||
|
func (c LocationByPath) Less(i, j int) bool {
|
||||||
|
return c[i].Path > c[j].Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSLCert describes a SSL certificate to be used in a server
|
||||||
|
type SSLCert struct {
|
||||||
|
api.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
// CAFileName contains the path to the file with the root certificate
|
||||||
|
CAFileName string `json:"caFileName"`
|
||||||
|
// PemFileName contains the path to the file with the certificate and key concatenated
|
||||||
|
PemFileName string `json:"pemFileName"`
|
||||||
|
// PemSHA contains the sha1 of the pem file.
|
||||||
|
// This is used to detect changes in the secret that contains the certificates
|
||||||
|
PemSHA string `json:"pemSha"`
|
||||||
|
// CN contains all the common names defined in the SSL certificate
|
||||||
|
CN []string `json:"cn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetObjectKind implements the ObjectKind interface as a noop
|
||||||
|
func (s SSLCert) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind }
|
|
@ -23,17 +23,17 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
cache_store "k8s.io/ingress/core/pkg/cache"
|
|
||||||
"k8s.io/ingress/core/pkg/k8s"
|
|
||||||
strings "k8s.io/ingress/core/pkg/strings"
|
|
||||||
"k8s.io/ingress/core/pkg/task"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/client/leaderelection"
|
"k8s.io/kubernetes/pkg/client/leaderelection"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
"k8s.io/kubernetes/pkg/util/wait"
|
"k8s.io/kubernetes/pkg/util/wait"
|
||||||
|
|
||||||
|
cache_store "k8s.io/ingress/core/pkg/cache"
|
||||||
|
"k8s.io/ingress/core/pkg/k8s"
|
||||||
|
strings "k8s.io/ingress/core/pkg/strings"
|
||||||
|
"k8s.io/ingress/core/pkg/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
243
core/pkg/ingress/types.go
Normal file
243
core/pkg/ingress/types.go
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ingress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultSSLDirectory defines the location where the SSL certificates will be generated
|
||||||
|
// This directory contains all the SSL certificates that are specified in Ingress rules.
|
||||||
|
// The name of each file is <namespace>-<secret name>.pem. The content is the concatenated
|
||||||
|
// certificate and key.
|
||||||
|
DefaultSSLDirectory = "/ingress-controller/ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Controller holds the methods to handle an Ingress backend
|
||||||
|
// TODO (#18): Make sure this is sufficiently supportive of other backends.
|
||||||
|
type Controller interface {
|
||||||
|
// Reload takes a byte array representing the new loadbalancer configuration,
|
||||||
|
// and returns a byte array containing any output/errors from the backend.
|
||||||
|
// Before returning the backend must load the configuration in the given array
|
||||||
|
// into the loadbalancer and restart it, or fail with an error and message string.
|
||||||
|
// If reloading fails, there should be not change in the running configuration or
|
||||||
|
// the given byte array.
|
||||||
|
Reload(data []byte) ([]byte, error)
|
||||||
|
// Tests returns the commands to execute that verifies if the configuration file is valid
|
||||||
|
// Example: nginx -t -c <file>
|
||||||
|
Test(file string) *exec.Cmd
|
||||||
|
// OnUpdate callback invoked from the sync queue https://k8s.io/ingress/core/blob/master/pkg/ingress/controller/controller.go#L387
|
||||||
|
// when an update occurs. This is executed frequently because Ingress
|
||||||
|
// controllers watches changes in:
|
||||||
|
// - Ingresses: main work
|
||||||
|
// - Secrets: referenced from Ingress rules with TLS configured
|
||||||
|
// - ConfigMaps: where the controller reads custom configuration
|
||||||
|
// - Services: referenced from Ingress rules and required to obtain
|
||||||
|
// information about ports and annotations
|
||||||
|
// - Endpoints: referenced from Services and what the backend uses
|
||||||
|
// to route traffic
|
||||||
|
// Any update to services, endpoints, secrets (only those referenced from Ingress)
|
||||||
|
// and ingress trigger the execution.
|
||||||
|
// Notifications of type Add, Update and Delete:
|
||||||
|
// https://github.com/kubernetes/kubernetes/blob/master/pkg/client/cache/controller.go#L164
|
||||||
|
//
|
||||||
|
// ConfigMap content of --configmap
|
||||||
|
// Configuration returns the translation from Ingress rules containing
|
||||||
|
// information about all the upstreams (service endpoints ) "virtual"
|
||||||
|
// servers (FQDN) and all the locations inside each server. Each
|
||||||
|
// location contains information about all the annotations were configured
|
||||||
|
// https://k8s.io/ingress/core/blob/master/pkg/ingress/types.go#L83
|
||||||
|
// The backend returns the contents of the configuration file or an error
|
||||||
|
// with the reason why was not possible to generate the file.
|
||||||
|
//
|
||||||
|
// The returned configuration is then passed to test, and then to reload
|
||||||
|
// if there is no errors.
|
||||||
|
OnUpdate(*api.ConfigMap, Configuration) ([]byte, error)
|
||||||
|
// BackendDefaults returns the minimum settings required to configure the
|
||||||
|
// communication to endpoints
|
||||||
|
BackendDefaults() defaults.Backend
|
||||||
|
// Info returns information about the ingress controller
|
||||||
|
Info() *BackendInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackendInfo returns information about the backend.
|
||||||
|
// This fields contains information that helps to track issues or to
|
||||||
|
// map the running ingress controller to source code
|
||||||
|
type BackendInfo struct {
|
||||||
|
// Name returns the name of the backend implementation
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Release returns the running version (semver)
|
||||||
|
Release string `json:"release"`
|
||||||
|
// Build returns information about the git commit
|
||||||
|
Build string `json:"build"`
|
||||||
|
// Repository return information about the git repository
|
||||||
|
Repository string `json:"repository"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration holds the definition of all the parts required to describe all
|
||||||
|
// ingresses reachable by the ingress controller (using a filter by namespace)
|
||||||
|
type Configuration struct {
|
||||||
|
// Backends are a list of backends used by all the Ingress rules in the
|
||||||
|
// ingress controller. This list includes the default backend
|
||||||
|
Backends []*Backend `json:"namespace"`
|
||||||
|
// Servers
|
||||||
|
Servers []*Server `json:"servers"`
|
||||||
|
// TCPEndpoints contain endpoints for tcp streams handled by this backend
|
||||||
|
// +optional
|
||||||
|
TCPEndpoints []*Location `json:"tcpEndpoints,omitempty"`
|
||||||
|
// UPDEndpoints contain endpoints for udp streams handled by this backend
|
||||||
|
// +optional
|
||||||
|
UPDEndpoints []*Location `json:"udpEndpoints,omitempty"`
|
||||||
|
// PassthroughBackend contains the backends used for SSL passthrough.
|
||||||
|
// It contains information about the associated Server Name Indication (SNI).
|
||||||
|
// +optional
|
||||||
|
PassthroughBackends []*SSLPassthroughBackend `json:"passthroughBackends,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backend describes one or more remote server/s (endpoints) associated with a service
|
||||||
|
type Backend struct {
|
||||||
|
// Name represents an unique api.Service name formatted as <namespace>-<name>-<port>
|
||||||
|
Name string `json:"name"`
|
||||||
|
// This indicates if the communication protocol between the backend and the endpoint is HTTP or HTTPS
|
||||||
|
// Allowing the use of HTTPS
|
||||||
|
// The endpoint/s must provide a TLS connection.
|
||||||
|
// The certificate used in the endpoint cannot be a self signed certificate
|
||||||
|
// TODO: add annotation to allow the load of ca certificate
|
||||||
|
Secure bool `json:"secure"`
|
||||||
|
// Endpoints contains the list of endpoints currently running
|
||||||
|
Endpoints []Endpoint `json:"endpoints"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint describes a kubernetes endpoint in an backend
|
||||||
|
type Endpoint struct {
|
||||||
|
// Address IP address of the endpoint
|
||||||
|
Address string `json:"address"`
|
||||||
|
// Port number of the TCP port
|
||||||
|
Port string `json:"port"`
|
||||||
|
// MaxFails returns the number of unsuccessful attempts to communicate
|
||||||
|
// allowed before this should be considered dow.
|
||||||
|
// Setting 0 indicates that the check is performed by a Kubernetes probe
|
||||||
|
MaxFails int `json:"maxFails"`
|
||||||
|
// FailTimeout returns the time in seconds during which the specified number
|
||||||
|
// of unsuccessful attempts to communicate with the server should happen
|
||||||
|
// to consider the endpoint unavailable
|
||||||
|
FailTimeout int `json:"failTimeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server describes a website
|
||||||
|
type Server struct {
|
||||||
|
// Hostname returns the FQDN of the server
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
// SSLPassthrough indicates if the TLS termination is realized in
|
||||||
|
// the server or in the remote endpoint
|
||||||
|
SSLPassthrough bool `json:"sslPassthrough"`
|
||||||
|
// SSLCertificate path to the SSL certificate on disk
|
||||||
|
SSLCertificate string `json:"sslCertificate"`
|
||||||
|
// SSLPemChecksum returns the checksum of the certificate file on disk.
|
||||||
|
// There is no restriction in the hash generator. This checksim can be
|
||||||
|
// used to determine if the secret changed without the use of file
|
||||||
|
// system notifications
|
||||||
|
SSLPemChecksum string `json:"sslPemChecksum"`
|
||||||
|
// Locations list of URIs configured in the server.
|
||||||
|
Locations []*Location `json:"locations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location describes an URI inside a server.
|
||||||
|
// Also contains additional information about annotations in the Ingress.
|
||||||
|
//
|
||||||
|
// Important:
|
||||||
|
// The implementation of annotations is optional
|
||||||
|
//
|
||||||
|
// In some cases when more than one annotations is defined a particular order in the execution
|
||||||
|
// is required.
|
||||||
|
// The chain in the execution order of annotations should be:
|
||||||
|
// - CertificateAuth
|
||||||
|
// - Whitelist
|
||||||
|
// - RateLimit
|
||||||
|
// - BasicDigestAuth
|
||||||
|
// - ExternalAuth
|
||||||
|
// - Redirect
|
||||||
|
type Location struct {
|
||||||
|
// Path is an extended POSIX regex as defined by IEEE Std 1003.1,
|
||||||
|
// (i.e this follows the egrep/unix syntax, not the perl syntax)
|
||||||
|
// matched against the path of an incoming request. Currently it can
|
||||||
|
// contain characters disallowed from the conventional "path"
|
||||||
|
// part of a URL as defined by RFC 3986. Paths must begin with
|
||||||
|
// a '/'. If unspecified, the path defaults to a catch all sending
|
||||||
|
// traffic to the backend.
|
||||||
|
Path string `json:"path"`
|
||||||
|
// IsDefBackend indicates if service specified in the Ingress
|
||||||
|
// contains active endpoints or not. Returning true means the location
|
||||||
|
// uses the default backend.
|
||||||
|
IsDefBackend bool `json:"isDefBackend"`
|
||||||
|
// Backend describes the name of the backend to use.
|
||||||
|
Backend string `json:"backend"`
|
||||||
|
// BasicDigestAuth returns authentication configuration for
|
||||||
|
// an Ingress rule.
|
||||||
|
// +optional
|
||||||
|
BasicDigestAuth auth.BasicDigest `json:"basicDigestAuth,omitempty"`
|
||||||
|
// EnableCORS indicates if path must support CORS
|
||||||
|
// +optional
|
||||||
|
EnableCORS bool `json:"enableCors,omitempty"`
|
||||||
|
// ExternalAuth indicates the access to this location requires
|
||||||
|
// authentication using an external provider
|
||||||
|
// +optional
|
||||||
|
ExternalAuth authreq.External `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"`
|
||||||
|
// Redirect describes the redirection this location.
|
||||||
|
// +optional
|
||||||
|
Redirect rewrite.Redirect `json:"redirect,omitempty"`
|
||||||
|
// Whitelist indicates only connections from certain client
|
||||||
|
// addresses or networks are allowed.
|
||||||
|
// +optional
|
||||||
|
Whitelist ipwhitelist.SourceRange `json:"whitelist,omitempty"`
|
||||||
|
// Proxy contains information about timeouts and buffer sizes
|
||||||
|
// to be used in connections against endpoints
|
||||||
|
// +optional
|
||||||
|
Proxy proxy.Configuration `json:"proxy,omitempty"`
|
||||||
|
// CertificateAuth indicates the access to this location requires
|
||||||
|
// external authentication
|
||||||
|
// +optional
|
||||||
|
CertificateAuth authtls.SSLCert `json:"certificateAuth,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSLPassthroughBackend describes a SSL upstream server configured
|
||||||
|
// as passthrough (no TLS termination in the ingress controller)
|
||||||
|
// The endpoints must provide the TLS termination exposing the required SSL certificate.
|
||||||
|
// The ingress controller only pipes the underlying TCP connection
|
||||||
|
type SSLPassthroughBackend struct {
|
||||||
|
// Backend describes the endpoints to use.
|
||||||
|
Backend string `json:"namespace,omitempty"`
|
||||||
|
// Hostname returns the FQDN of the server
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -92,7 +93,8 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
||||||
|
|
||||||
_, err := pemCert.Verify(opts)
|
_, err := pemCert.Verify(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to verify certificate chain: \n\t%s\n", err)
|
oe := fmt.Sprintf("failed to verify certificate chain: \n\t%s\n", err)
|
||||||
|
return nil, errors.New(oe)
|
||||||
}
|
}
|
||||||
|
|
||||||
caName := fmt.Sprintf("ca-%v.pem", name)
|
caName := fmt.Sprintf("ca-%v.pem", name)
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
[[ $DEBUG ]] && set -x
|
|
||||||
|
|
||||||
set -eof pipefail
|
|
||||||
|
|
||||||
# include env
|
|
||||||
. hack/e2e-internal/e2e-env.sh
|
|
||||||
|
|
||||||
echo "Destroying running docker containers..."
|
|
||||||
# do not failt if the container is not running
|
|
||||||
docker rm -f kubelet || true
|
|
||||||
docker rm -f apiserver || true
|
|
||||||
docker rm -f etcd || true
|
|
|
@ -1,21 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
[[ $DEBUG ]] && set -x
|
|
||||||
|
|
||||||
export ETCD_VERSION=3.0.14
|
|
||||||
export K8S_VERSION=1.4.5
|
|
||||||
|
|
||||||
export PWD=`pwd`
|
|
||||||
export BASEDIR="$(dirname ${BASH_SOURCE})"
|
|
||||||
export KUBECTL="${BASEDIR}/kubectl"
|
|
||||||
export GOOS="${GOOS:-linux}"
|
|
||||||
|
|
||||||
if [ ! -e ${KUBECTL} ]; then
|
|
||||||
echo "kubectl binary is missing. downloading..."
|
|
||||||
curl -sSL http://storage.googleapis.com/kubernetes-release/release/v${K8S_VERSION}/bin/${GOOS}/amd64/kubectl -o ${KUBECTL}
|
|
||||||
chmod u+x ${KUBECTL}
|
|
||||||
fi
|
|
||||||
|
|
||||||
${KUBECTL} config set-cluster travis --server=http://0.0.0.0:8080
|
|
||||||
${KUBECTL} config set-context travis --cluster=travis
|
|
||||||
${KUBECTL} config use-context travis
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
[[ $DEBUG ]] && set -x
|
|
||||||
|
|
||||||
set -eof pipefail
|
|
||||||
|
|
||||||
# include env
|
|
||||||
. hack/e2e-internal/e2e-env.sh
|
|
||||||
|
|
||||||
echo "Kubernetes information:"
|
|
||||||
${KUBECTL} version
|
|
|
@ -1,55 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
[[ $DEBUG ]] && set -x
|
|
||||||
|
|
||||||
set -eof pipefail
|
|
||||||
|
|
||||||
# include env
|
|
||||||
. hack/e2e-internal/e2e-env.sh
|
|
||||||
|
|
||||||
echo "Starting etcd..."
|
|
||||||
docker run -d \
|
|
||||||
--net=host \
|
|
||||||
--name=etcd \
|
|
||||||
quay.io/coreos/etcd:v$ETCD_VERSION
|
|
||||||
|
|
||||||
echo "Starting kubernetes..."
|
|
||||||
|
|
||||||
docker run -d --name=apiserver \
|
|
||||||
--net=host \
|
|
||||||
--pid=host \
|
|
||||||
--privileged=true \
|
|
||||||
gcr.io/google_containers/hyperkube:v${K8S_VERSION} \
|
|
||||||
/hyperkube apiserver \
|
|
||||||
--insecure-bind-address=0.0.0.0 \
|
|
||||||
--service-cluster-ip-range=10.0.0.1/24 \
|
|
||||||
--etcd_servers=http://127.0.0.1:4001 \
|
|
||||||
--v=2
|
|
||||||
|
|
||||||
docker run -d --name=kubelet \
|
|
||||||
--volume=/:/rootfs:ro \
|
|
||||||
--volume=/sys:/sys:ro \
|
|
||||||
--volume=/dev:/dev \
|
|
||||||
--volume=/var/lib/docker/:/var/lib/docker:rw \
|
|
||||||
--volume=/var/lib/kubelet/:/var/lib/kubelet:rw \
|
|
||||||
--volume=/var/run:/var/run:rw \
|
|
||||||
--net=host \
|
|
||||||
--pid=host \
|
|
||||||
--privileged=true \
|
|
||||||
gcr.io/google_containers/hyperkube:v${K8S_VERSION} \
|
|
||||||
/hyperkube kubelet \
|
|
||||||
--containerized \
|
|
||||||
--hostname-override="0.0.0.0" \
|
|
||||||
--address="0.0.0.0" \
|
|
||||||
--cluster_dns=10.0.0.10 --cluster_domain=cluster.local \
|
|
||||||
--api-servers=http://localhost:8080 \
|
|
||||||
--config=/etc/kubernetes/manifests-multi
|
|
||||||
|
|
||||||
echo "waiting until api server is available..."
|
|
||||||
until curl -o /dev/null -sIf http://0.0.0.0:8080; do \
|
|
||||||
sleep 10;
|
|
||||||
done;
|
|
||||||
|
|
||||||
echo "Kubernetes started"
|
|
||||||
echo "Kubernetes information:"
|
|
||||||
${KUBECTL} version
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
echo "running ginkgo"
|
|
285
hack/e2e.go
285
hack/e2e.go
|
@ -1,285 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2014 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// e2e.go runs the e2e test suite. No non-standard package dependencies; call with "go run".
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
build = flag.Bool("build", true, "Build the backends images indicated by the env var BACKENDS required to run e2e tests.")
|
|
||||||
up = flag.Bool("up", true, "Creates a kubernetes cluster using hyperkube (containerized kubelet).")
|
|
||||||
down = flag.Bool("down", true, "destroys the created cluster.")
|
|
||||||
test = flag.Bool("test", true, "Run Ginkgo tests.")
|
|
||||||
dump = flag.String("dump", "", "If set, dump cluster logs to this location on test or cluster-up failure")
|
|
||||||
testArgs = flag.String("test-args", "", "Space-separated list of arguments to pass to Ginkgo test runner.")
|
|
||||||
deployment = flag.String("deployment", "bash", "up/down mechanism")
|
|
||||||
verbose = flag.Bool("v", false, "If true, print all command output.")
|
|
||||||
)
|
|
||||||
|
|
||||||
func appendError(errs []error, err error) []error {
|
|
||||||
if err != nil {
|
|
||||||
return append(errs, err)
|
|
||||||
}
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func validWorkingDirectory() error {
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not get pwd: %v", err)
|
|
||||||
}
|
|
||||||
acwd, err := filepath.Abs(cwd)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to convert %s to an absolute path: %v", cwd, err)
|
|
||||||
}
|
|
||||||
if !strings.Contains(filepath.Base(acwd), "ingress-controller") {
|
|
||||||
return fmt.Errorf("must run from git root directory: %v", acwd)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestCase struct {
|
|
||||||
XMLName xml.Name `xml:"testcase"`
|
|
||||||
ClassName string `xml:"classname,attr"`
|
|
||||||
Name string `xml:"name,attr"`
|
|
||||||
Time float64 `xml:"time,attr"`
|
|
||||||
Failure string `xml:"failure,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestSuite struct {
|
|
||||||
XMLName xml.Name `xml:"testsuite"`
|
|
||||||
Failures int `xml:"failures,attr"`
|
|
||||||
Tests int `xml:"tests,attr"`
|
|
||||||
Time float64 `xml:"time,attr"`
|
|
||||||
Cases []TestCase
|
|
||||||
}
|
|
||||||
|
|
||||||
var suite TestSuite
|
|
||||||
|
|
||||||
func xmlWrap(name string, f func() error) error {
|
|
||||||
start := time.Now()
|
|
||||||
err := f()
|
|
||||||
duration := time.Since(start)
|
|
||||||
c := TestCase{
|
|
||||||
Name: name,
|
|
||||||
ClassName: "e2e.go",
|
|
||||||
Time: duration.Seconds(),
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
c.Failure = err.Error()
|
|
||||||
suite.Failures++
|
|
||||||
}
|
|
||||||
suite.Cases = append(suite.Cases, c)
|
|
||||||
suite.Tests++
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeXML(start time.Time) {
|
|
||||||
suite.Time = time.Since(start).Seconds()
|
|
||||||
out, err := xml.MarshalIndent(&suite, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not marshal XML: %s", err)
|
|
||||||
}
|
|
||||||
path := filepath.Join(*dump, "junit_runner.xml")
|
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not create file: %s", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if _, err := f.WriteString(xml.Header); err != nil {
|
|
||||||
log.Fatalf("Error writing XML header: %s", err)
|
|
||||||
}
|
|
||||||
if _, err := f.Write(out); err != nil {
|
|
||||||
log.Fatalf("Error writing XML data: %s", err)
|
|
||||||
}
|
|
||||||
log.Printf("Saved XML output to %s.", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if err := validWorkingDirectory(); err != nil {
|
|
||||||
log.Fatalf("Called from invalid working directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy, err := getDeployer()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error creating deployer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := run(deploy); err != nil {
|
|
||||||
log.Fatalf("Something went wrong: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(deploy deployer) error {
|
|
||||||
if *dump != "" {
|
|
||||||
defer writeXML(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
if *build {
|
|
||||||
if err := xmlWrap("Build", Build); err != nil {
|
|
||||||
return fmt.Errorf("error building: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *up {
|
|
||||||
if err := xmlWrap("TearDown", deploy.Down); err != nil {
|
|
||||||
return fmt.Errorf("error tearing down previous cluster: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs []error
|
|
||||||
|
|
||||||
if *up {
|
|
||||||
// If we tried to bring the cluster up, make a courtesy
|
|
||||||
// attempt to bring it down so we're not leaving resources around.
|
|
||||||
//
|
|
||||||
// TODO: We should try calling deploy.Down exactly once. Though to
|
|
||||||
// stop the leaking resources for now, we want to be on the safe side
|
|
||||||
// and call it explictly in defer if the other one is not called.
|
|
||||||
if *down {
|
|
||||||
defer xmlWrap("Deferred TearDown", deploy.Down)
|
|
||||||
}
|
|
||||||
// Start the cluster using this version.
|
|
||||||
if err := xmlWrap("Up", deploy.Up); err != nil {
|
|
||||||
return fmt.Errorf("starting e2e cluster: %s", err)
|
|
||||||
}
|
|
||||||
if *dump != "" {
|
|
||||||
cmd := exec.Command("./cluster/kubectl.sh", "--match-server-version=false", "get", "nodes", "-oyaml")
|
|
||||||
b, err := cmd.CombinedOutput()
|
|
||||||
if *verbose {
|
|
||||||
log.Printf("kubectl get nodes:\n%s", string(b))
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
if err := ioutil.WriteFile(filepath.Join(*dump, "nodes.yaml"), b, 0644); err != nil {
|
|
||||||
errs = appendError(errs, fmt.Errorf("error writing nodes.yaml: %v", err))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errs = appendError(errs, fmt.Errorf("error running get nodes: %v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *test {
|
|
||||||
if err := xmlWrap("IsUp", deploy.IsUp); err != nil {
|
|
||||||
errs = appendError(errs, err)
|
|
||||||
} else {
|
|
||||||
errs = appendError(errs, Test())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) > 0 && *dump != "" {
|
|
||||||
errs = appendError(errs, xmlWrap("DumpClusterLogs", func() error {
|
|
||||||
return DumpClusterLogs(*dump)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if *down {
|
|
||||||
errs = appendError(errs, xmlWrap("TearDown", deploy.Down))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) != 0 {
|
|
||||||
return fmt.Errorf("encountered %d errors: %v", len(errs), errs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Build() error {
|
|
||||||
// The build-release script needs stdin to ask the user whether
|
|
||||||
// it's OK to download the docker image.
|
|
||||||
cmd := exec.Command("make", "backends", "backends-images", "backends-push")
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
if err := finishRunning("build-release", cmd); err != nil {
|
|
||||||
return fmt.Errorf("error building: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type deployer interface {
|
|
||||||
Up() error
|
|
||||||
IsUp() error
|
|
||||||
SetupKubecfg() error
|
|
||||||
Down() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDeployer() (deployer, error) {
|
|
||||||
switch *deployment {
|
|
||||||
case "bash":
|
|
||||||
return bash{}, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unknown deployment strategy %q", *deployment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type bash struct{}
|
|
||||||
|
|
||||||
func (b bash) Up() error {
|
|
||||||
return finishRunning("up", exec.Command("./hack/e2e-internal/e2e-up.sh"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b bash) IsUp() error {
|
|
||||||
return finishRunning("get status", exec.Command("./hack/e2e-internal/e2e-status.sh"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b bash) SetupKubecfg() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b bash) Down() error {
|
|
||||||
return finishRunning("teardown", exec.Command("./hack/e2e-internal/e2e-down.sh"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DumpClusterLogs(location string) error {
|
|
||||||
log.Printf("Dumping cluster logs to: %v", location)
|
|
||||||
return finishRunning("dump cluster logs", exec.Command("./hack/e2e-internal/log-dump.sh", location))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test() error {
|
|
||||||
if *testArgs == "" {
|
|
||||||
*testArgs = "--ginkgo.focus=\\[Feature:Ingress\\]"
|
|
||||||
}
|
|
||||||
return finishRunning("Ginkgo tests", exec.Command("./hack/e2e-internal/ginkgo-e2e.sh", strings.Fields(*testArgs)...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func finishRunning(stepName string, cmd *exec.Cmd) error {
|
|
||||||
if *verbose {
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
}
|
|
||||||
log.Printf("Running: %v", stepName)
|
|
||||||
defer func(start time.Time) {
|
|
||||||
log.Printf("Step '%s' finished in %s", stepName, time.Since(start))
|
|
||||||
}(time.Now())
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("error running %v: %v", stepName, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in a new issue