Add Generic interface

This commit is contained in:
Manuel de Brito Fontes 2016-11-16 15:24:26 -03:00
parent f2b627486d
commit 5a8e090736
36 changed files with 58014 additions and 675 deletions

View file

@ -30,5 +30,5 @@ script:
# enable kubernetes/ingress in
# coveralls.io and add cover task
- make fmt lint vet test
- make controllers controllers-images
#- make controllers controllers-images
#- make test-e2e

View file

@ -25,14 +25,16 @@ import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/defaults"
"errors"
"k8s.io/ingress/controllers/nginx/pkg/config"
ngx_template "k8s.io/ingress/controllers/nginx/pkg/template"
"k8s.io/ingress/controllers/nginx/pkg/version"
"k8s.io/kubernetes/pkg/api"
)
var (
@ -75,6 +77,8 @@ Error loading new template : %v
}
n.t = ngxTpl
go n.Start()
return n
}
@ -85,7 +89,7 @@ type NGINXController struct {
binary string
}
// Start ...
// Start start a new NGINX master process running in foreground.
func (n NGINXController) Start() {
glog.Info("starting NGINX process...")
cmd := exec.Command(n.binary, "-c", cfgPath)
@ -99,14 +103,13 @@ func (n NGINXController) Start() {
}
}
// Stop ...
func (n NGINXController) Stop() error {
n.t.Close()
return exec.Command(n.binary, "-s", "stop").Run()
}
// Reload checks if the running configuration file is different
// to the specified and reload nginx if required
func (n NGINXController) Reload(data []byte) ([]byte, error) {
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)
if err != nil {
return nil, err
@ -120,15 +123,15 @@ func (n NGINXController) Test(file string) *exec.Cmd {
return exec.Command(n.binary, "-t", "-c", file)
}
// UpstreamDefaults returns the nginx defaults
func (n NGINXController) UpstreamDefaults() defaults.Backend {
// BackendDefaults returns the nginx defaults
func (n NGINXController) BackendDefaults() defaults.Backend {
d := config.NewDefault()
return d.Backend
}
// IsReloadRequired check if the new configuration file is different
// from the current one.
func (n NGINXController) IsReloadRequired(data []byte) bool {
func (n NGINXController) isReloadRequired(data []byte) bool {
in, err := os.Open(cfgPath)
if err != nil {
return false
@ -167,8 +170,13 @@ func (n NGINXController) IsReloadRequired(data []byte) bool {
}
// Info return build information
func (n NGINXController) Info() string {
return fmt.Sprintf("build version %v from repo %v commit %v", version.RELEASE, version.REPO, version.COMMIT)
func (n NGINXController) Info() *ingress.BackendInfo {
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
@ -183,12 +191,13 @@ func (n NGINXController) testTemplate(cfg []byte) error {
out, err := n.Test(tmpfile.Name()).CombinedOutput()
if err != nil {
// 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
%v
-------------------------------------------------------------------------------
`, err, string(out))
return errors.New(oe)
}
os.Remove(tmpfile.Name())
@ -207,9 +216,9 @@ func (n NGINXController) OnUpdate(cmap *api.ConfigMap, ingressCfg ingress.Config
var longestName int
var serverNames int
for _, srv := range ingressCfg.Servers {
serverNames += len([]byte(srv.Name))
if longestName < len(srv.Name) {
longestName = len(srv.Name)
serverNames += len([]byte(srv.Hostname))
if longestName < len(srv.Hostname) {
longestName = len(srv.Hostname)
}
}
@ -234,21 +243,17 @@ func (n NGINXController) OnUpdate(cmap *api.ConfigMap, ingressCfg ingress.Config
cfg.ServerNameHashMaxSize = serverNameHashMaxSize
}
conf := make(map[string]interface{})
// adjust the size of the backlog
conf["backlogSize"] = sysctlSomaxconn()
conf["upstreams"] = ingressCfg.Upstreams
conf["passthroughUpstreams"] = ingressCfg.PassthroughUpstreams
conf["servers"] = ingressCfg.Servers
conf["tcpUpstreams"] = ingressCfg.TCPEndpoints
conf["udpUpstreams"] = ingressCfg.UPDEndpoints
conf["healthzURL"] = ingressCfg.HealthzURL
conf["defResolver"] = cfg.Resolver
conf["sslDHParam"] = ""
conf["customErrors"] = len(cfg.CustomHTTPErrors) > 0
conf["cfg"] = ngx_template.StandarizeKeyNames(cfg)
return n.t.Write(conf, n.testTemplate)
return n.t.Write(config.TemplateConfig{
BacklogSize: sysctlSomaxconn(),
Backends: ingressCfg.Backends,
PassthrougBackends: ingressCfg.PassthroughBackends,
Servers: ingressCfg.Servers,
TCPBackends: ingressCfg.TCPEndpoints,
UDPBackends: ingressCfg.UPDEndpoints,
HealthzURI: "/healthz",
CustomErrors: len(cfg.CustomHTTPErrors) > 0,
Cfg: cfg,
}, n.testTemplate)
}
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2

View file

@ -19,9 +19,10 @@ package config
import (
"runtime"
"k8s.io/ingress/core/pkg/ingress/defaults"
"github.com/golang/glog"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
const (
@ -216,8 +217,7 @@ type Configuration struct {
WorkerProcesses int `structs:"worker-processes,omitempty"`
}
// NewDefault returns the default configuration contained
// in the file default-conf.json
// NewDefault returns the default nginx configuration
func NewDefault() Configuration {
cfg := Configuration{
BodySize: bodySize,
@ -264,3 +264,15 @@ func NewDefault() Configuration {
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
}

View file

@ -27,10 +27,10 @@ import (
"github.com/mitchellh/mapstructure"
go_camelcase "github.com/segmentio/go-camelcase"
"k8s.io/kubernetes/pkg/api"
"k8s.io/ingress/controllers/nginx/pkg/config"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/kubernetes/pkg/api"
)
const (
@ -50,9 +50,9 @@ func ReadConfig(conf *api.ConfigMap) config.Configuration {
return config.NewDefault()
}
var errors []int
var skipUrls []string
var whitelist []string
errors := make([]int, 0)
skipUrls := make([]string, 0)
whitelist := make([]string, 0)
if val, ok := conf.Data[customHTTPErrors]; ok {
delete(conf.Data, customHTTPErrors)

View file

@ -27,6 +27,7 @@ import (
"github.com/golang/glog"
"k8s.io/ingress/controllers/nginx/pkg/config"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/watch"
)
@ -73,12 +74,19 @@ func (t *Template) Close() {
// Write populates a buffer using a template with NGINX configuration
// and the servers and upstreams created by Ingress rules
func (t *Template) Write(conf map[string]interface{},
isValidTemplate func([]byte) error) ([]byte, error) {
func (t *Template) Write(conf config.TemplateConfig, isValidTemplate func([]byte) error) ([]byte, error) {
defer t.tmplBuf.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) {
b, err := json.Marshal(conf)
if err != nil {
@ -88,12 +96,8 @@ func (t *Template) Write(conf map[string]interface{},
}
err := t.tmpl.Execute(t.tmplBuf, conf)
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 err != nil {
return nil, err
}
// squeezes multiple adjacent empty lines to be single
@ -124,12 +128,12 @@ var (
}
return true
},
"buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"buildProxyPass": buildProxyPass,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"getSSPassthroughUpstream": getSSPassthroughUpstream,
"buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"buildProxyPass": buildProxyPass,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"buildSSPassthroughUpstreams": buildSSPassthroughUpstreams,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
@ -139,13 +143,32 @@ var (
}
)
func getSSPassthroughUpstream(input interface{}) string {
s, ok := input.(*ingress.Server)
if !ok {
return ""
func buildSSPassthroughUpstreams(b interface{}, sslb interface{}) string {
backends := b.([]*ingress.Backend)
sslBackends := sslb.([]*ingress.SSLPassthroughBackend)
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
@ -184,20 +207,27 @@ func buildAuthLocation(input interface{}) string {
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
// 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
func buildProxyPass(input interface{}) string {
location, ok := input.(*ingress.Location)
func buildProxyPass(b interface{}, loc interface{}) string {
backends := b.([]*ingress.Backend)
location, ok := loc.(*ingress.Location)
if !ok {
return ""
}
path := location.Path
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 := 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 path == location.Redirect.Target {
return defProxyPass
@ -227,13 +257,13 @@ func buildProxyPass(input interface{}) string {
rewrite %s(.*) /$1 break;
rewrite %s / break;
proxy_pass %s://%s;
%v`, path, location.Path, proto, location.Backend.Name, abu)
%v`, path, location.Path, proto, location.Backend, abu)
}
return fmt.Sprintf(`
rewrite %s(.*) %s/$1 break;
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

View file

@ -17,14 +17,21 @@ limitations under the License.
package template
import (
"encoding/json"
"os"
"path"
"strings"
"testing"
"io/ioutil"
"k8s.io/ingress/controllers/nginx/pkg/config"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
)
var (
// TODO: add tests for secure endpoints
tmplFuncTestcases = map[string]struct {
Path string
Target string
@ -88,12 +95,77 @@ func TestBuildProxyPass(t *testing.T) {
loc := &ingress.Location{
Path: tc.Path,
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) {
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 })
}
}

View file

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# 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 \
diffutils \

View file

@ -1,20 +1,20 @@
{{ $cfg := .cfg }}{{ $healthzURL := .healthzURL }}
{{ $cfg := .Cfg }}{{ $healthzURI := .HealthzURI }}{{ $backends := .Backends }}
daemon off;
worker_processes {{ $cfg.workerProcesses }};
worker_processes {{ $cfg.WorkerProcesses }};
pid /run/nginx.pid;
worker_rlimit_nofile 131072;
events {
multi_accept on;
worker_connections {{ $cfg.maxWorkerConnections }};
worker_connections {{ $cfg.MaxWorkerConnections }};
use epoll;
}
http {
{{/* we use the value of the header X-Forwarded-For to be able to use the geo_ip module */}}
{{ if $cfg.useProxyProtocol }}
set_real_ip_from {{ $cfg.proxyRealIpCidr }};
{{ if $cfg.UseProxyProtocol }}
set_real_ip_from {{ $cfg.ProxyRealIpCidr }};
real_ip_header proxy_protocol;
{{ else }}
real_ip_header X-Forwarded-For;
@ -30,8 +30,8 @@ http {
geoip_city /etc/nginx/GeoLiteCity.dat;
geoip_proxy_recursive on;
{{ if $cfg.enableVtsStatus }}
vhost_traffic_status_zone shared:vhost_traffic_status:{{ $cfg.vtsStatusZoneSize }};
{{ if $cfg.EnableVtsStatus }}
vhost_traffic_status_zone shared:vhost_traffic_status:{{ $cfg.VtsStatusZoneSize }};
vhost_traffic_status_filter_by_set_key $geoip_country_code country::*;
{{ end }}
@ -50,43 +50,43 @@ http {
reset_timedout_connection on;
keepalive_timeout {{ $cfg.keepAlive }}s;
keepalive_timeout {{ $cfg.KeepAlive }}s;
types_hash_max_size 2048;
server_names_hash_max_size {{ $cfg.serverNameHashMaxSize }};
server_names_hash_bucket_size {{ $cfg.serverNameHashBucketSize }};
map_hash_bucket_size {{ $cfg.mapHashBucketSize }};
server_names_hash_max_size {{ $cfg.ServerNameHashMaxSize }};
server_names_hash_bucket_size {{ $cfg.ServerNameHashBucketSize }};
map_hash_bucket_size {{ $cfg.MapHashBucketSize }};
include /etc/nginx/mime.types;
default_type text/html;
{{ if $cfg.useGzip }}
{{ if $cfg.UseGzip }}
gzip on;
gzip_comp_level 5;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types {{ $cfg.gzipTypes }};
gzip_types {{ $cfg.GzipTypes }};
gzip_proxied any;
{{ 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" '
'$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 */}}
{{/* http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log */}}
map $request $loggable {
{{ range $reqUri := $cfg.skipAccessLogUrls }}
{{ range $reqUri := $cfg.SkipAccessLogURLs }}
{{ $reqUri }} 0;{{ end }}
default 1;
}
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.
resolver {{ .defResolver }} valid=30s;
{{ if not (empty $cfg.Resolver) }}# Custom dns resolver.
resolver {{ $cfg.Resolver }} valid=30s;
resolver_timeout 10s;
{{ end }}
@ -98,14 +98,14 @@ http {
{{/* normal nginx behavior we have to use this approach. */}}
# Retain the default nginx handling of requests without a "Connection" header
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
default upgrade;
'' close;
}
# trust http_x_forwarded_proto headers correctly indicate ssl offloading
map $http_x_forwarded_proto $pass_access_scheme {
default $http_x_forwarded_proto;
'' $scheme;
default $http_x_forwarded_proto;
'' $scheme;
}
# Map a response error watching the header Content-Type
@ -124,51 +124,51 @@ http {
}
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
{{ if $cfg.sslSessionCache }}
ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.sslSessionCacheSize }};
ssl_session_timeout {{ $cfg.sslSessionTimeout }};
{{ if $cfg.SSLSessionCache }}
ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.SSLSessionCacheSize }};
ssl_session_timeout {{ $cfg.SSLSessionTimeout }};
{{ end }}
# 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
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
ssl_ciphers '{{ $cfg.sslCiphers }}';
ssl_ciphers '{{ $cfg.SSLCiphers }}';
ssl_prefer_server_ciphers on;
{{ 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
ssl_dhparam {{ .sslDHParam }};
ssl_dhparam {{ $cfg.SSLDHParam }};
{{ end }}
{{ if not $cfg.enableDynamicTlsRecords }}
{{ if not $cfg.EnableDynamicTLSRecords }}
ssl_dyn_rec_size_lo 0;
{{ end }}
{{ if .customErrors }}
{{ if .CustomErrors }}
# Custom error pages
proxy_intercept_errors on;
{{ end }}
{{ range $errCode := $cfg.customHttpErrors }}
{{ range $errCode := $cfg.CustomHTTPErrors }}
error_page {{ $errCode }} = @custom_{{ $errCode }};{{ end }}
# 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}} {
{{ if $cfg.enableStickySessions }}
{{ if $cfg.EnableStickySessions }}
sticky hash=sha1 httponly;
{{ else }}
least_conn;
@ -180,26 +180,26 @@ http {
{{/* 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 */}}
{{ range $zone := (buildRateLimitZones .servers) }}
{{ range $zone := (buildRateLimitZones .Servers) }}
{{ $zone }}
{{ end }}
{{ range $server := .servers }}
{{ range $server := .Servers }}
server {
server_name {{ $server.Name }};
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 }};
server_name {{ $server.Hostname }};
listen 80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ 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 */}}
# PEM sha: {{ $server.SSLPemChecksum }}
ssl_certificate {{ $server.SSLCertificate }};
ssl_certificate_key {{ $server.SSLCertificate }};
{{ end }}
{{ if (and $server.SSL $cfg.hsts) }}
more_set_headers "Strict-Transport-Security: max-age={{ $cfg.hstsMaxAge }}{{ if $cfg.hstsIncludeSubdomains }}; includeSubDomains{{ end }}; preload";
{{ if (and (not (empty $server.SSLCertificate)) $cfg.HSTS) }}
more_set_headers "Strict-Transport-Security: max-age={{ $cfg.HSTSMaxAge }}{{ if $cfg.HSTSIncludeSubdomains }}; includeSubDomains{{ end }}; preload";
{{ 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 }}
{{ $path := buildLocation $location }}
@ -240,7 +240,7 @@ http {
auth_request {{ $authPath }};
{{ end }}
{{ if (and $server.SSL $location.Redirect.SSLRedirect) }}
{{ if (and (not (empty $server.SSLCertificate)) $location.Redirect.SSLRedirect) }}
# enforce ssl on server side
if ($scheme = http) {
return 301 https://$host$request_uri;
@ -256,7 +256,6 @@ http {
auth_basic "{{ $location.BasicDigestAuth.Realm }}";
auth_basic_user_file {{ $location.BasicDigestAuth.File }};
{{ else }}
#TODO: add nginx-http-auth-digest module
auth_digest "{{ $location.BasicDigestAuth.Realm }}";
auth_digest_user_file {{ $location.BasicDigestAuth.File }};
{{ end }}
@ -300,14 +299,14 @@ http {
proxy_set_header Accept-Encoding "";
{{ end }}
set $proxy_upstream_name "{{ $location.Backend.Name }}";
{{ buildProxyPass $location }}
set $proxy_upstream_name "{{ $location.Backend }}";
{{ buildProxyPass $backends $location }}
}
{{ end }}
{{ if eq $server.Name "_" }}
{{ if eq $server.Hostname "_" }}
# health checks in cloud providers require the use of port 80
location {{ $healthzURL }} {
location {{ $healthzURI }} {
access_log off;
return 200;
}
@ -322,6 +321,7 @@ http {
stub_status on;
}
{{ end }}
{{ 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.
# Changing this value requires a change in:
# 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;
return 200;
}
location /nginx_status {
{{ if $cfg.enableVtsStatus }}
{{ if $cfg.EnableVtsStatus }}
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
{{ else }}
@ -362,7 +362,7 @@ http {
set $proxy_upstream_name "-";
location / {
{{ if .customErrors }}
{{ if .CustomErrors }}
content_by_lua_block {
openURL(503)
}
@ -376,15 +376,14 @@ http {
stream {
# map FQDN that requires SSL passthrough
map $ssl_preread_server_name $stream_upstream {
{{ range $i, $passthrough := .passthroughUpstreams }}
{{ $passthrough.Host }} {{ $passthrough.Upstream.Name }}-{{ $i }}-pt;
{{ range $i, $passthrough := .PassthrougBackends }}
{{ $passthrough.Hostname }} {{ $passthrough.Backend }};
{{ end }}
# send SSL traffic to this nginx in a different port
default nginx-ssl-backend;
}
log_format log_stream '$remote_addr [$time_local] $protocol [$ssl_preread_server_name] [$stream_upstream] '
'$status $bytes_sent $bytes_received $session_time';
log_format log_stream '$remote_addr [$time_local] $protocol [$ssl_preread_server_name] [$stream_upstream] $status $bytes_sent $bytes_received $session_time';
access_log /var/log/nginx/access.log log_stream;
error_log /var/log/nginx/error.log;
@ -394,56 +393,20 @@ stream {
server 127.0.0.1:442;
}
{{ range $i, $passthrough := .passthroughUpstreams }}
upstream {{ $passthrough.Name }}-{{ $i }}-pt {
{{ range $server := $passthrough.Endpoints }}server {{ $server.Address }}:{{ $server.Port }};
{{ end }}
}
{{ end }}
{{ buildSSPassthroughUpstreams $backends .PassthrougBackends }}
server {
listen 443;
{{ if $cfg.useProxyProtocol }}proxy_protocol on;{{ end }}
{{ if $cfg.UseProxyProtocol }}proxy_protocol on;{{ end }}
proxy_pass $stream_upstream;
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 */}}
{{ define "CUSTOM_ERRORS" }}
{{ range $errCode := .customHttpErrors }}
{{ range $errCode := .CustomHTTPErrors }}
location @custom_{{ $errCode }} {
internal;
content_by_lua_block {

File diff suppressed because it is too large Load diff

View file

@ -23,10 +23,10 @@ import (
"os"
"regexp"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
)
const (
@ -59,10 +59,10 @@ var (
// BasicDigest returns authentication configuration for an Ingress rule
type BasicDigest struct {
Type string
Realm string
File string
Secured bool
Type string `json:"type"`
Realm string `json:"realm"`
File string `json:"file"`
Secured bool `json:"secured"`
}
// ParseAnnotations parses the annotations contained in the ingress

View file

@ -21,8 +21,9 @@ import (
"net/url"
"strings"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
)
const (
@ -34,9 +35,9 @@ const (
// External returns external authentication configuration for an Ingress rule
type External struct {
URL string
Method string
SendBody bool
URL string `json:"url"`
Method string `json:"method"`
SendBody bool `json:"sendBody"`
}
var (

View file

@ -19,10 +19,10 @@ package authtls
import (
"fmt"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/k8s"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
@ -32,11 +32,11 @@ const (
// SSLCert returns external authentication configuration for an Ingress rule
type SSLCert struct {
Secret string
CertFileName string
KeyFileName string
CAFileName string
PemSHA string
Secret string `json:"secret"`
CertFileName string `json:"certFilename"`
KeyFileName string `json:"keyFilename"`
CAFileName string `json:"caFilename"`
PemSHA string `json:"pemSha"`
}
// ParseAnnotations parses the annotations contained in the ingress

View file

@ -17,9 +17,9 @@ limitations under the License.
package cors
import (
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
)
const (

View file

@ -31,8 +31,8 @@ const (
// Upstream returns the URL and method to use check the status of
// the upstream server/s
type Upstream struct {
MaxFails int
FailTimeout int
MaxFails int `json:"maxFails"`
FailTimeout int `json:"failTimeout"`
}
// ParseAnnotations parses the annotations contained in the ingress

View file

@ -20,11 +20,11 @@ import (
"errors"
"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/util/net/sets"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
const (
@ -39,7 +39,7 @@ var (
// SourceRange returns the CIDR
type SourceRange struct {
CIDR []string
CIDR []string `json:"cidr"`
}
// ParseAnnotations parses the annotations contained in the ingress

View file

@ -32,10 +32,10 @@ const (
// Configuration returns the proxy timeout to use in the upstream server/s
type Configuration struct {
ConnectTimeout int
SendTimeout int
ReadTimeout int
BufferSize string
ConnectTimeout int `json:"conectTimeout"`
SendTimeout int `json:"sendTimeout"`
ReadTimeout int `json:"readTimeout"`
BufferSize string `json:"bufferSize"`
}
// ParseAnnotations parses the annotations contained in the ingress

View file

@ -20,9 +20,9 @@ import (
"errors"
"fmt"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
)
const (
@ -48,19 +48,19 @@ var (
// Note: Is possible to specify both limits
type RateLimit struct {
// 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 Zone
RPS Zone `json:"rps"`
}
// 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
type Zone struct {
Name string
Limit int
Burst int
Name string `json:"name"`
Limit int `json:"limit"`
Burst int `json:"burst"`
// SharedSize amount of shared memory for the zone
SharedSize int
SharedSize int `json:"sharedSize"`
}
// ParseAnnotations parses the annotations contained in the ingress

View file

@ -19,10 +19,10 @@ package rewrite
import (
"errors"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
@ -34,12 +34,12 @@ const (
// Redirect describes the per location redirect config
type Redirect struct {
// 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
// of the responses from the upstream servers
AddBaseURL bool
AddBaseURL bool `json:"addBaseUrl"`
// SSLRedirect indicates if the location section is accessible SSL only
SSLRedirect bool
SSLRedirect bool `json:"sslRedirect"`
}
// ParseAnnotations parses the annotations contained in the ingress

View file

@ -17,9 +17,9 @@ limitations under the License.
package secureupstream
import (
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
)
const (

View file

@ -19,10 +19,10 @@ package sslpassthrough
import (
"fmt"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (

View file

@ -21,11 +21,12 @@ import (
"strings"
"time"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/cache"
"github.com/golang/glog"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
ssl "k8s.io/ingress/core/pkg/net/ssl"

View file

@ -301,7 +301,7 @@ func (ic GenericController) Check(_ *http.Request) error {
}
// Info returns information about the backend
func (ic GenericController) Info() string {
func (ic GenericController) Info() *ingress.BackendInfo {
return ic.cfg.Backend.Info()
}
@ -368,31 +368,25 @@ func (ic *GenericController) sync(key interface{}) error {
continue
}
passUpstreams = append(passUpstreams, &ingress.SSLPassthroughBackend{
Upstream: loc.Backend,
Host: server.Name,
Backend: loc.Backend,
Hostname: server.Hostname,
})
break
}
}
data, err := ic.cfg.Backend.OnUpdate(cfg, ingress.Configuration{
HealthzURL: ic.cfg.DefaultHealthzURL,
Upstreams: upstreams,
Servers: servers,
TCPEndpoints: ic.getTCPServices(),
UPDEndpoints: ic.getUDPServices(),
PassthroughUpstreams: passUpstreams,
Backends: upstreams,
Servers: servers,
TCPEndpoints: ic.getTCPServices(),
UPDEndpoints: ic.getUDPServices(),
PassthroughBackends: passUpstreams,
})
if err != nil {
return err
}
if !ic.cfg.Backend.IsReloadRequired(data) {
return nil
}
glog.Infof("reloading ingress backend...")
out, err := ic.cfg.Backend.Restart(data)
out, err := ic.cfg.Backend.Reload(data)
if err != nil {
incReloadErrorCount()
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{
Path: k,
Upstream: ingress.Backend{
Name: fmt.Sprintf("%v-%v-%v", svcNs, svcName, port),
Endpoints: endps,
},
Path: k,
Backend: fmt.Sprintf("%v-%v-%v", svcNs, svcName, port),
})
}
@ -579,7 +570,7 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
upstreams := ic.createUpstreams(ings)
servers := ic.createServers(ings, upstreams)
upsDefaults := ic.cfg.Backend.UpstreamDefaults()
upsDefaults := ic.cfg.Backend.BackendDefaults()
for _, ingIf := range ings {
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)
}
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)
if err != nil {
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 &&
host != defServerName {
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
}
@ -690,19 +676,19 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
addLoc = false
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
}
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)
loc.Backend = *ups
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.Name
loc.IsDefBackend = false
loc.BasicDigestAuth = *nginxAuth
loc.RateLimit = *rl
loc.Redirect = *locRew
loc.SecureUpstream = secUpstream
//loc.SecureUpstream = secUpstream
loc.Whitelist = *wl
loc.Backend = *ups
loc.Backend = ups.Name
loc.EnableCORS = eCORS
loc.ExternalAuth = ra
loc.Proxy = *prx
@ -712,15 +698,15 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
}
// is a new location
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{
Path: nginxPath,
Upstream: *ups,
Backend: ups.Name,
IsDefBackend: false,
BasicDigestAuth: *nginxAuth,
RateLimit: *rl,
Redirect: *locRew,
SecureUpstream: secUpstream,
//SecureUpstream: secUpstream,
Whitelist: *wl,
EnableCORS: eCORS,
ExternalAuth: ra,
@ -776,10 +762,15 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
upstreams := make(map[string]*ingress.Backend)
upstreams[defUpstreamName] = ic.getDefaultUpstream()
upsDefaults := ic.cfg.Backend.UpstreamDefaults()
upsDefaults := ic.cfg.Backend.BackendDefaults()
for _, ingIf := range data {
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)
var defBackend string
@ -817,7 +808,9 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
glog.V(3).Infof("creating upstream %v", name)
upstreams[name] = newUpstream(name)
if !upstreams[name].Secure {
upstreams[name].Secure = secUpstream
}
svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName)
endp, err := ic.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz)
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 {
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
servers[defServerName] = &ingress.Server{
Name: defServerName,
Hostname: defServerName,
Locations: []*ingress.Location{
{
Path: rootLocation,
IsDefBackend: true,
Upstream: *ic.getDefaultUpstream(),
Backend: ic.getDefaultUpstream().Name,
Proxy: ngxProxy,
},
}}
@ -906,12 +899,12 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
continue
}
servers[host] = &ingress.Server{
Name: host,
Hostname: host,
Locations: []*ingress.Location{
{
Path: rootLocation,
IsDefBackend: true,
Upstream: *ic.getDefaultUpstream(),
Backend: ic.getDefaultUpstream().Name,
Proxy: ngxProxy,
},
}, 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
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)
bc, exists := ic.sslCertTracker.Get(key)
if exists {
cert := bc.(*ingress.SSLCert)
if isHostValid(host, cert) {
servers[host].SSL = true
servers[host].SSLCertificate = cert.PemFileName
//servers[host].SSLCertificateKey = cert.PemFileName
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")
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.
func (ic GenericController) Start() {
glog.Infof("starting Ingress controller")
go ic.cfg.Backend.Start()
go ic.ingController.Run(ic.stopCh)
go ic.endpController.Run(ic.stopCh)

View file

@ -1,6 +1,7 @@
package controller
import (
"encoding/json"
"flag"
"fmt"
"net/http"
@ -12,15 +13,15 @@ import (
"github.com/golang/glog"
"github.com/spf13/pflag"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/k8s"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/healthz"
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
@ -160,7 +161,8 @@ func registerHandlers(enableProfiling bool, port int, ic *GenericController) {
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
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) {

View file

@ -24,11 +24,11 @@ import (
"github.com/golang/glog"
"k8s.io/ingress/core/pkg/ingress/annotations/service"
"k8s.io/kubernetes/pkg/api"
podutil "k8s.io/kubernetes/pkg/api/pod"
"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

View file

@ -19,10 +19,10 @@ package controller
import (
"strings"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress"
"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.
@ -33,7 +33,7 @@ func newDefaultServer() ingress.Endpoint {
// newUpstream creates an upstream without servers.
func newUpstream(name string) *ingress.Backend {
return &ingress.Backend{
Name: name,
Name: name,
Endpoints: []ingress.Endpoint{},
}
}

63
core/pkg/ingress/doc.go Normal file
View 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",
// }

View 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 }

View file

@ -23,17 +23,17 @@ import (
"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/apis/extensions"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/leaderelection"
"k8s.io/kubernetes/pkg/labels"
"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 (

243
core/pkg/ingress/types.go Normal file
View 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"`
}

View file

@ -21,6 +21,7 @@ import (
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
@ -92,7 +93,8 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
_, err := pemCert.Verify(opts)
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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
echo "running ginkgo"

View file

@ -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
}