Merge bb85d6194c
into 1c8773fc98
This commit is contained in:
commit
e3b0863b03
1849 changed files with 293956 additions and 414647 deletions
34
.travis.yml
Normal file
34
.travis.yml
Normal file
|
@ -0,0 +1,34 @@
|
|||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
language: go
|
||||
|
||||
notifications:
|
||||
email: true
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
|
||||
go_import_path: k8s.io/ingress
|
||||
|
||||
env:
|
||||
global:
|
||||
- RELEASE="ci-${TRAVIS_BUILD_ID}"
|
||||
|
||||
install:
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get github.com/modocache/gover
|
||||
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||
|
||||
before_script:
|
||||
- export PATH=$PATH:$PWD/hack/e2e-internal/
|
||||
|
||||
script:
|
||||
# enable kubernetes/ingress in
|
||||
# coveralls.io and add cover task
|
||||
- make fmt lint vet test
|
||||
- make controllers controllers-images
|
||||
#- make test-e2e
|
1420
Godeps/Godeps.json
generated
1420
Godeps/Godeps.json
generated
File diff suppressed because it is too large
Load diff
69
Makefile
Normal file
69
Makefile
Normal file
|
@ -0,0 +1,69 @@
|
|||
all: push
|
||||
|
||||
BUILDTAGS=
|
||||
|
||||
# building inside travis generates a custom version of the
|
||||
# backends in order to run e2e tests agains the build.
|
||||
ifdef TRAVIS_BUILD_ID
|
||||
RELEASE := ci-build-${TRAVIS_BUILD_ID}
|
||||
endif
|
||||
|
||||
# 0.0 shouldn't clobber any release builds
|
||||
RELEASE?=0.0
|
||||
|
||||
# by default build a linux version
|
||||
GOOS?=linux
|
||||
|
||||
REPO_INFO=$(shell git config --get remote.origin.url)
|
||||
|
||||
ifndef COMMIT
|
||||
COMMIT := git-$(shell git rev-parse --short HEAD)
|
||||
endif
|
||||
|
||||
# base package. It contains the common and backends code
|
||||
PKG := "k8s.io/ingress"
|
||||
|
||||
# TODO: fix lint errors in gce controller
|
||||
GO_LIST_FILES=$(shell go list ${PKG}/... | grep -v vendor | grep -v -e "test/e2e" -e "controllers/gce/loadbalancers" -e "controllers/gce/controller")
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@go list -f '{{if len .TestGoFiles}}"gofmt -s -l {{.Dir}}"{{end}}' ${GO_LIST_FILES} | xargs -L 1 sh -c
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@go list -f '{{if len .TestGoFiles}}"golint {{.Dir}}/..."{{end}}' ${GO_LIST_FILES} | xargs -L 1 sh -c
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@go test -v -race -tags "$(BUILDTAGS) cgo" ${GO_LIST_FILES}
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e: ginkgo
|
||||
@go run hack/e2e.go -v --up --test --down
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
@go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' ${GO_LIST_FILES} | xargs -L 1 sh -c
|
||||
gover
|
||||
goveralls -coverprofile=gover.coverprofile -service travis-ci -repotoken ${COVERALLS_TOKEN}
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
@go vet ${GO_LIST_FILES}
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
make -C controllers/nginx clean
|
||||
|
||||
.PHONY: controllers
|
||||
controllers:
|
||||
make -C controllers/nginx build
|
||||
|
||||
.PHONY: controllers-images
|
||||
controllers-images:
|
||||
make -C controllers/nginx container
|
||||
|
||||
.PHONY: ginkgo
|
||||
ginkgo:
|
||||
go get github.com/onsi/ginkgo/ginkgo
|
|
@ -1,5 +1,7 @@
|
|||
// vim: ft=asciidoc
|
||||
|
||||
image:https://travis-ci.org/kubernetes/ingress.svg?branch=master["Build Status", link="https://travis-ci.org/kubernetes/ingress"]
|
||||
|
||||
= Ingress
|
||||
:toc: macro
|
||||
:toc-title:
|
||||
|
|
|
@ -26,10 +26,10 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/contrib/ingress/controllers/gce/instances"
|
||||
"k8s.io/contrib/ingress/controllers/gce/storage"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/ingress/controllers/gce/instances"
|
||||
"k8s.io/ingress/controllers/gce/storage"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
)
|
||||
|
||||
// Backends implements BackendPool.
|
||||
|
|
|
@ -20,10 +20,10 @@ import (
|
|||
"testing"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/contrib/ingress/controllers/gce/instances"
|
||||
"k8s.io/contrib/ingress/controllers/gce/storage"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/ingress/controllers/gce/instances"
|
||||
"k8s.io/ingress/controllers/gce/storage"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
|
|
|
@ -18,30 +18,42 @@ package backends
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
)
|
||||
|
||||
// NewFakeBackendServices creates a new fake backend services manager.
|
||||
func NewFakeBackendServices() *FakeBackendServices {
|
||||
return &FakeBackendServices{
|
||||
backendServices: []*compute.BackendService{},
|
||||
backendServices: cache.NewStore(func(obj interface{}) (string, error) {
|
||||
svc := obj.(*compute.BackendService)
|
||||
return svc.Name, nil
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// FakeBackendServices fakes out GCE backend services.
|
||||
type FakeBackendServices struct {
|
||||
backendServices []*compute.BackendService
|
||||
backendServices cache.Store
|
||||
calls []int
|
||||
}
|
||||
|
||||
// GetBackendService fakes getting a backend service from the cloud.
|
||||
func (f *FakeBackendServices) GetBackendService(name string) (*compute.BackendService, error) {
|
||||
f.calls = append(f.calls, utils.Get)
|
||||
for i := range f.backendServices {
|
||||
if name == f.backendServices[i].Name {
|
||||
return f.backendServices[i], nil
|
||||
obj, exists, err := f.backendServices.GetByKey(name)
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("Backend service %v not found", name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := obj.(*compute.BackendService)
|
||||
if name == svc.Name {
|
||||
return svc, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Backend service %v not found", name)
|
||||
}
|
||||
|
@ -50,37 +62,36 @@ func (f *FakeBackendServices) GetBackendService(name string) (*compute.BackendSe
|
|||
func (f *FakeBackendServices) CreateBackendService(be *compute.BackendService) error {
|
||||
f.calls = append(f.calls, utils.Create)
|
||||
be.SelfLink = be.Name
|
||||
f.backendServices = append(f.backendServices, be)
|
||||
return nil
|
||||
return f.backendServices.Update(be)
|
||||
}
|
||||
|
||||
// DeleteBackendService fakes backend service deletion.
|
||||
func (f *FakeBackendServices) DeleteBackendService(name string) error {
|
||||
f.calls = append(f.calls, utils.Delete)
|
||||
newBackends := []*compute.BackendService{}
|
||||
for i := range f.backendServices {
|
||||
if name != f.backendServices[i].Name {
|
||||
newBackends = append(newBackends, f.backendServices[i])
|
||||
svc, exists, err := f.backendServices.GetByKey(name)
|
||||
if !exists {
|
||||
return fmt.Errorf("Backend service %v not found", name)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.backendServices = newBackends
|
||||
return nil
|
||||
return f.backendServices.Delete(svc)
|
||||
}
|
||||
|
||||
// ListBackendServices fakes backend service listing.
|
||||
func (f *FakeBackendServices) ListBackendServices() (*compute.BackendServiceList, error) {
|
||||
return &compute.BackendServiceList{Items: f.backendServices}, nil
|
||||
var svcs []*compute.BackendService
|
||||
for _, s := range f.backendServices.List() {
|
||||
svc := s.(*compute.BackendService)
|
||||
svcs = append(svcs, svc)
|
||||
}
|
||||
return &compute.BackendServiceList{Items: svcs}, nil
|
||||
}
|
||||
|
||||
// UpdateBackendService fakes updating a backend service.
|
||||
func (f *FakeBackendServices) UpdateBackendService(be *compute.BackendService) error {
|
||||
f.calls = append(f.calls, utils.Update)
|
||||
for i := range f.backendServices {
|
||||
if f.backendServices[i].Name == be.Name {
|
||||
f.backendServices[i] = be
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return f.backendServices.Update(be)
|
||||
}
|
||||
|
||||
// GetHealth fakes getting backend service health.
|
||||
|
|
|
@ -22,12 +22,12 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/gce/backends"
|
||||
"k8s.io/contrib/ingress/controllers/gce/firewalls"
|
||||
"k8s.io/contrib/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/contrib/ingress/controllers/gce/instances"
|
||||
"k8s.io/contrib/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/backends"
|
||||
"k8s.io/ingress/controllers/gce/firewalls"
|
||||
"k8s.io/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/ingress/controllers/gce/instances"
|
||||
"k8s.io/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
gce "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
|
||||
|
||||
|
|
|
@ -23,14 +23,15 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
"k8s.io/kubernetes/pkg/client/record"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/controller/framework"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
|
@ -39,7 +40,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
keyFunc = framework.DeletionHandlingMetaNamespaceKeyFunc
|
||||
keyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc
|
||||
|
||||
// DefaultClusterUID is the uid to use for clusters resources created by an
|
||||
// L7 controller created without specifying the --cluster-uid flag.
|
||||
|
@ -52,11 +53,11 @@ var (
|
|||
// LoadBalancerController watches the kubernetes api and adds/removes services
|
||||
// from the loadbalancer, via loadBalancerConfig.
|
||||
type LoadBalancerController struct {
|
||||
client *client.Client
|
||||
ingController *framework.Controller
|
||||
nodeController *framework.Controller
|
||||
svcController *framework.Controller
|
||||
podController *framework.Controller
|
||||
client client.Interface
|
||||
ingController *cache.Controller
|
||||
nodeController *cache.Controller
|
||||
svcController *cache.Controller
|
||||
podController *cache.Controller
|
||||
ingLister StoreToIngressLister
|
||||
nodeLister cache.StoreToNodeLister
|
||||
svcLister cache.StoreToServiceLister
|
||||
|
@ -86,11 +87,12 @@ type LoadBalancerController struct {
|
|||
// - clusterManager: A ClusterManager capable of creating all cloud resources
|
||||
// required for L7 loadbalancing.
|
||||
// - resyncPeriod: Watchers relist from the Kubernetes API server this often.
|
||||
func NewLoadBalancerController(kubeClient *client.Client, clusterManager *ClusterManager, resyncPeriod time.Duration, namespace string) (*LoadBalancerController, error) {
|
||||
func NewLoadBalancerController(kubeClient client.Interface, clusterManager *ClusterManager, resyncPeriod time.Duration, namespace string) (*LoadBalancerController, error) {
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(glog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(kubeClient.Events(""))
|
||||
|
||||
eventBroadcaster.StartRecordingToSink(unversionedcore.EventSinkImpl{
|
||||
Interface: kubeClient.Core().Events(""),
|
||||
})
|
||||
lbc := LoadBalancerController{
|
||||
client: kubeClient,
|
||||
CloudClusterManager: clusterManager,
|
||||
|
@ -103,7 +105,7 @@ func NewLoadBalancerController(kubeClient *client.Client, clusterManager *Cluste
|
|||
lbc.hasSynced = lbc.storesSynced
|
||||
|
||||
// Ingress watch handlers
|
||||
pathHandlers := framework.ResourceEventHandlerFuncs{
|
||||
pathHandlers := cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
addIng := obj.(*extensions.Ingress)
|
||||
if !isGCEIngress(addIng) {
|
||||
|
@ -133,7 +135,7 @@ func NewLoadBalancerController(kubeClient *client.Client, clusterManager *Cluste
|
|||
lbc.ingQueue.enqueue(cur)
|
||||
},
|
||||
}
|
||||
lbc.ingLister.Store, lbc.ingController = framework.NewInformer(
|
||||
lbc.ingLister.Store, lbc.ingController = cache.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: ingressListFunc(lbc.client, namespace),
|
||||
WatchFunc: ingressWatchFunc(lbc.client, namespace),
|
||||
|
@ -141,7 +143,7 @@ func NewLoadBalancerController(kubeClient *client.Client, clusterManager *Cluste
|
|||
&extensions.Ingress{}, resyncPeriod, pathHandlers)
|
||||
|
||||
// Service watch handlers
|
||||
svcHandlers := framework.ResourceEventHandlerFuncs{
|
||||
svcHandlers := cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: lbc.enqueueIngressForService,
|
||||
UpdateFunc: func(old, cur interface{}) {
|
||||
if !reflect.DeepEqual(old, cur) {
|
||||
|
@ -151,36 +153,39 @@ func NewLoadBalancerController(kubeClient *client.Client, clusterManager *Cluste
|
|||
// Ingress deletes matter, service deletes don't.
|
||||
}
|
||||
|
||||
lbc.svcLister.Store, lbc.svcController = framework.NewInformer(
|
||||
cache.NewListWatchFromClient(
|
||||
lbc.client, "services", namespace, fields.Everything()),
|
||||
&api.Service{}, resyncPeriod, svcHandlers)
|
||||
|
||||
lbc.podLister.Indexer, lbc.podController = framework.NewIndexerInformer(
|
||||
cache.NewListWatchFromClient(lbc.client, "pods", namespace, fields.Everything()),
|
||||
&api.Pod{},
|
||||
lbc.svcLister.Indexer, lbc.svcController = cache.NewIndexerInformer(
|
||||
cache.NewListWatchFromClient(lbc.client.Core().RESTClient(), "services", namespace, fields.Everything()),
|
||||
&api.Service{},
|
||||
resyncPeriod,
|
||||
framework.ResourceEventHandlerFuncs{},
|
||||
svcHandlers,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
)
|
||||
|
||||
nodeHandlers := framework.ResourceEventHandlerFuncs{
|
||||
lbc.podLister.Indexer, lbc.podController = cache.NewIndexerInformer(
|
||||
cache.NewListWatchFromClient(lbc.client.Core().RESTClient(), "pods", namespace, fields.Everything()),
|
||||
&api.Pod{},
|
||||
resyncPeriod,
|
||||
cache.ResourceEventHandlerFuncs{},
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
)
|
||||
|
||||
nodeHandlers := cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: lbc.nodeQueue.enqueue,
|
||||
DeleteFunc: lbc.nodeQueue.enqueue,
|
||||
// Nodes are updated every 10s and we don't care, so no update handler.
|
||||
}
|
||||
// Node watch handlers
|
||||
lbc.nodeLister.Store, lbc.nodeController = framework.NewInformer(
|
||||
lbc.nodeLister.Store, lbc.nodeController = cache.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(opts api.ListOptions) (runtime.Object, error) {
|
||||
return lbc.client.Get().
|
||||
return lbc.client.Core().RESTClient().Get().
|
||||
Resource("nodes").
|
||||
FieldsSelectorParam(fields.Everything()).
|
||||
Do().
|
||||
Get()
|
||||
},
|
||||
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
|
||||
return lbc.client.Get().
|
||||
return lbc.client.Core().RESTClient().Get().
|
||||
Prefix("watch").
|
||||
Resource("nodes").
|
||||
FieldsSelectorParam(fields.Everything()).
|
||||
|
@ -196,15 +201,15 @@ func NewLoadBalancerController(kubeClient *client.Client, clusterManager *Cluste
|
|||
return &lbc, nil
|
||||
}
|
||||
|
||||
func ingressListFunc(c *client.Client, ns string) func(api.ListOptions) (runtime.Object, error) {
|
||||
func ingressListFunc(c client.Interface, ns string) func(api.ListOptions) (runtime.Object, error) {
|
||||
return func(opts api.ListOptions) (runtime.Object, error) {
|
||||
return c.Extensions().Ingress(ns).List(opts)
|
||||
return c.Extensions().Ingresses(ns).List(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func ingressWatchFunc(c *client.Client, ns string) func(options api.ListOptions) (watch.Interface, error) {
|
||||
func ingressWatchFunc(c client.Interface, ns string) func(options api.ListOptions) (watch.Interface, error) {
|
||||
return func(options api.ListOptions) (watch.Interface, error) {
|
||||
return c.Extensions().Ingress(ns).Watch(options)
|
||||
return c.Extensions().Ingresses(ns).Watch(options)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,7 +354,7 @@ func (lbc *LoadBalancerController) sync(key string) (err error) {
|
|||
}
|
||||
|
||||
ing := *obj.(*extensions.Ingress)
|
||||
if urlMap, err := lbc.tr.toUrlMap(&ing); err != nil {
|
||||
if urlMap, err := lbc.tr.toURLMap(&ing); err != nil {
|
||||
syncError = fmt.Errorf("%v, convert to url map error %v", syncError, err)
|
||||
} else if err := l7.UpdateUrlMap(urlMap); err != nil {
|
||||
lbc.recorder.Eventf(&ing, api.EventTypeWarning, "UrlMap", err.Error())
|
||||
|
@ -364,7 +369,7 @@ func (lbc *LoadBalancerController) sync(key string) (err error) {
|
|||
// updateIngressStatus updates the IP and annotations of a loadbalancer.
|
||||
// The annotations are parsed by kubectl describe.
|
||||
func (lbc *LoadBalancerController) updateIngressStatus(l7 *loadbalancers.L7, ing extensions.Ingress) error {
|
||||
ingClient := lbc.client.Extensions().Ingress(ing.Namespace)
|
||||
ingClient := lbc.client.Extensions().Ingresses(ing.Namespace)
|
||||
|
||||
// Update IP through update/status endpoint
|
||||
ip := l7.GetIP()
|
||||
|
|
|
@ -23,14 +23,16 @@ import (
|
|||
"time"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/firewalls"
|
||||
"k8s.io/contrib/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
|
||||
"k8s.io/ingress/controllers/gce/firewalls"
|
||||
"k8s.io/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/util/uuid"
|
||||
|
@ -49,8 +51,8 @@ func defaultBackendName(clusterName string) string {
|
|||
}
|
||||
|
||||
// newLoadBalancerController create a loadbalancer controller.
|
||||
func newLoadBalancerController(t *testing.T, cm *fakeClusterManager, masterUrl string) *LoadBalancerController {
|
||||
client := client.NewOrDie(&restclient.Config{Host: masterUrl, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}})
|
||||
func newLoadBalancerController(t *testing.T, cm *fakeClusterManager, masterURL string) *LoadBalancerController {
|
||||
client := client.NewForConfigOrDie(&restclient.Config{Host: masterURL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}})
|
||||
lb, err := NewLoadBalancerController(client, cm.ClusterManager, 1*time.Second, api.NamespaceAll)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
|
@ -191,7 +193,7 @@ func addIngress(lbc *LoadBalancerController, ing *extensions.Ingress, pm *nodePo
|
|||
}
|
||||
svcPort.NodePort = int32(pm.getNodePort(path.Backend.ServiceName))
|
||||
svc.Spec.Ports = []api.ServicePort{svcPort}
|
||||
lbc.svcLister.Store.Add(svc)
|
||||
lbc.svcLister.Indexer.Add(svc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,12 @@ import (
|
|||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/gce/backends"
|
||||
"k8s.io/contrib/ingress/controllers/gce/firewalls"
|
||||
"k8s.io/contrib/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/contrib/ingress/controllers/gce/instances"
|
||||
"k8s.io/contrib/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/backends"
|
||||
"k8s.io/ingress/controllers/gce/firewalls"
|
||||
"k8s.io/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/ingress/controllers/gce/instances"
|
||||
"k8s.io/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -19,10 +19,11 @@ package controller
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/ingress/controllers/gce/loadbalancers"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
@ -43,7 +44,7 @@ func (n *noOPValidator) validate(certs *loadbalancers.TLSCerts) error {
|
|||
// apiServerTLSLoader loads TLS certs from the apiserver.
|
||||
type apiServerTLSLoader struct {
|
||||
noOPValidator
|
||||
client *client.Client
|
||||
client client.Interface
|
||||
}
|
||||
|
||||
func (t *apiServerTLSLoader) load(ing *extensions.Ingress) (*loadbalancers.TLSCerts, error) {
|
||||
|
@ -58,7 +59,7 @@ func (t *apiServerTLSLoader) load(ing *extensions.Ingress) (*loadbalancers.TLSCe
|
|||
secretName := ing.Spec.TLS[0].SecretName
|
||||
// TODO: Replace this for a secret watcher.
|
||||
glog.V(3).Infof("Retrieving secret for ing %v with name %v", ing.Name, secretName)
|
||||
secret, err := t.client.Secrets(ing.Namespace).Get(secretName)
|
||||
secret, err := t.client.Core().Secrets(ing.Namespace).Get(secretName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ func addPods(lbc *LoadBalancerController, nodePortToHealthCheck map[int64]string
|
|||
}
|
||||
svc.Name = fmt.Sprintf("%d", np)
|
||||
svc.Namespace = ns
|
||||
lbc.svcLister.Store.Add(svc)
|
||||
lbc.svcLister.Indexer.Add(svc)
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
|
|
|
@ -23,8 +23,8 @@ import (
|
|||
"time"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
|
@ -226,8 +226,8 @@ type GCETranslator struct {
|
|||
*LoadBalancerController
|
||||
}
|
||||
|
||||
// toUrlMap converts an ingress to a map of subdomain: url-regex: gce backend.
|
||||
func (t *GCETranslator) toUrlMap(ing *extensions.Ingress) (utils.GCEURLMap, error) {
|
||||
// toURLMap converts an ingress to a map of subdomain: url-regex: gce backend.
|
||||
func (t *GCETranslator) toURLMap(ing *extensions.Ingress) (utils.GCEURLMap, error) {
|
||||
hostPathBackend := utils.GCEURLMap{}
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
if rule.HTTP == nil {
|
||||
|
@ -291,7 +291,7 @@ func (t *GCETranslator) toGCEBackend(be *extensions.IngressBackend, ns string) (
|
|||
// getServiceNodePort looks in the svc store for a matching service:port,
|
||||
// and returns the nodeport.
|
||||
func (t *GCETranslator) getServiceNodePort(be extensions.IngressBackend, namespace string) (int, error) {
|
||||
obj, exists, err := t.svcLister.Store.Get(
|
||||
obj, exists, err := t.svcLister.Indexer.Get(
|
||||
&api.Service{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: be.ServiceName,
|
||||
|
@ -457,15 +457,15 @@ func isSimpleHTTPProbe(probe *api.Probe) bool {
|
|||
// the request path, callers are responsible for swapping this out for the
|
||||
// appropriate default.
|
||||
func (t *GCETranslator) HealthCheck(port int64) (*compute.HttpHealthCheck, error) {
|
||||
sl, err := t.svcLister.List()
|
||||
sl, err := t.svcLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Find the label and target port of the one service with the given nodePort
|
||||
for _, s := range sl.Items {
|
||||
for _, s := range sl {
|
||||
for _, p := range s.Spec.Ports {
|
||||
if int32(port) == p.NodePort {
|
||||
rp, err := t.getHTTPProbe(s, p.TargetPort)
|
||||
rp, err := t.getHTTPProbe(*s, p.TargetPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
netset "k8s.io/kubernetes/pkg/util/net/sets"
|
||||
)
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
netset "k8s.io/kubernetes/pkg/util/net/sets"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
)
|
||||
|
||||
// NewFakeHealthChecks returns a new FakeHealthChecks.
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
compute "google.golang.org/api/compute/v1"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ import (
|
|||
"strings"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/storage"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/storage"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"testing"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ import (
|
|||
"strings"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/backends"
|
||||
"k8s.io/contrib/ingress/controllers/gce/storage"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/backends"
|
||||
"k8s.io/ingress/controllers/gce/storage"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
|
|
@ -21,10 +21,10 @@ import (
|
|||
"testing"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/backends"
|
||||
"k8s.io/contrib/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/contrib/ingress/controllers/gce/instances"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/backends"
|
||||
"k8s.io/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/ingress/controllers/gce/instances"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
|
|
|
@ -27,12 +27,13 @@ import (
|
|||
"time"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
"k8s.io/contrib/ingress/controllers/gce/controller"
|
||||
"k8s.io/contrib/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/contrib/ingress/controllers/gce/storage"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/ingress/controllers/gce/controller"
|
||||
"k8s.io/ingress/controllers/gce/loadbalancers"
|
||||
"k8s.io/ingress/controllers/gce/storage"
|
||||
"k8s.io/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
|
@ -159,7 +160,6 @@ func handleSigterm(lbc *controller.LoadBalancerController, deleteAll bool) {
|
|||
// main function for GLBC.
|
||||
func main() {
|
||||
// TODO: Add a healthz endpoint
|
||||
var kubeClient *client.Client
|
||||
var err error
|
||||
var clusterManager *controller.ClusterManager
|
||||
|
||||
|
@ -181,18 +181,24 @@ func main() {
|
|||
glog.Fatalf("Please specify --default-backend")
|
||||
}
|
||||
|
||||
var config *restclient.Config
|
||||
// Create kubeclient
|
||||
if *inCluster {
|
||||
if kubeClient, err = client.NewInCluster(); err != nil {
|
||||
glog.Fatalf("Failed to create client: %v.", err)
|
||||
if config, err = restclient.InClusterConfig(); err != nil {
|
||||
glog.Fatalf("error creating client configuration: %v", err)
|
||||
}
|
||||
} else {
|
||||
config, err := clientConfig.ClientConfig()
|
||||
config, err = clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
glog.Fatalf("error connecting to the client: %v", err)
|
||||
glog.Fatalf("error creating client configuration: %v", err)
|
||||
}
|
||||
kubeClient, err = client.New(config)
|
||||
}
|
||||
|
||||
kubeClient, err := client.NewForConfig(config)
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to create client: %v.", err)
|
||||
}
|
||||
|
||||
// Wait for the default backend Service. There's no pretty way to do this.
|
||||
parts := strings.Split(*defaultSvc, "/")
|
||||
if len(parts) != 2 {
|
||||
|
@ -239,7 +245,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func newNamer(kubeClient *client.Client, clusterName string) (*utils.Namer, error) {
|
||||
func newNamer(kubeClient client.Interface, clusterName string) (*utils.Namer, error) {
|
||||
name, err := getClusterUID(kubeClient, clusterName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -271,7 +277,7 @@ func newNamer(kubeClient *client.Client, clusterName string) (*utils.Namer, erro
|
|||
// else, check if there are any working Ingresses
|
||||
// - remember that "" is the cluster uid
|
||||
// else, allocate a new uid
|
||||
func getClusterUID(kubeClient *client.Client, name string) (string, error) {
|
||||
func getClusterUID(kubeClient client.Interface, name string) (string, error) {
|
||||
cfgVault := storage.NewConfigMapVault(kubeClient, api.NamespaceSystem, uidConfigMapName)
|
||||
if name != "" {
|
||||
glog.Infof("Using user provided cluster uid %v", name)
|
||||
|
@ -294,7 +300,7 @@ func getClusterUID(kubeClient *client.Client, name string) (string, error) {
|
|||
}
|
||||
|
||||
// Check if the cluster has an Ingress with ip
|
||||
ings, err := kubeClient.Extensions().Ingress(api.NamespaceAll).List(api.ListOptions{LabelSelector: labels.Everything()})
|
||||
ings, err := kubeClient.Extensions().Ingresses(api.NamespaceAll).List(api.ListOptions{LabelSelector: labels.Everything()})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -325,11 +331,11 @@ func getClusterUID(kubeClient *client.Client, name string) (string, error) {
|
|||
}
|
||||
|
||||
// getNodePort waits for the Service, and returns it's first node port.
|
||||
func getNodePort(client *client.Client, ns, name string) (nodePort int64, err error) {
|
||||
func getNodePort(client client.Interface, ns, name string) (nodePort int64, err error) {
|
||||
var svc *api.Service
|
||||
glog.V(3).Infof("Waiting for %v/%v", ns, name)
|
||||
wait.Poll(1*time.Second, 5*time.Minute, func() (bool, error) {
|
||||
svc, err = client.Services(ns).Get(name)
|
||||
svc, err = client.Core().Services(ns).Get(name)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
)
|
||||
|
||||
// UIDVault stores UIDs.
|
||||
|
@ -107,7 +107,7 @@ func (c *ConfigMapVault) Delete() error {
|
|||
// NewConfigMapVault creates a config map client.
|
||||
// This client is essentially meant to abstract out the details of
|
||||
// configmaps and the API, and just store/retrieve a single value, the cluster uid.
|
||||
func NewConfigMapVault(c *client.Client, uidNs, uidConfigMapName string) *ConfigMapVault {
|
||||
func NewConfigMapVault(c client.Interface, uidNs, uidConfigMapName string) *ConfigMapVault {
|
||||
return &ConfigMapVault{NewConfigMapStore(c), uidNs, uidConfigMapName}
|
||||
}
|
||||
|
||||
|
@ -123,43 +123,43 @@ type ConfigMapStore interface {
|
|||
cache.Store
|
||||
}
|
||||
|
||||
// ApiServerConfigMapStore only services Add and GetByKey from apiserver.
|
||||
// APIServerConfigMapStore only services Add and GetByKey from apiserver.
|
||||
// TODO: Implement all the other store methods and make this a write
|
||||
// through cache.
|
||||
type ApiServerConfigMapStore struct {
|
||||
type APIServerConfigMapStore struct {
|
||||
ConfigMapStore
|
||||
client *client.Client
|
||||
client client.Interface
|
||||
}
|
||||
|
||||
// Add adds the given config map to the apiserver's store.
|
||||
func (a *ApiServerConfigMapStore) Add(obj interface{}) error {
|
||||
func (a *APIServerConfigMapStore) Add(obj interface{}) error {
|
||||
cfg := obj.(*api.ConfigMap)
|
||||
_, err := a.client.ConfigMaps(cfg.Namespace).Create(cfg)
|
||||
_, err := a.client.Core().ConfigMaps(cfg.Namespace).Create(cfg)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update updates the existing config map object.
|
||||
func (a *ApiServerConfigMapStore) Update(obj interface{}) error {
|
||||
func (a *APIServerConfigMapStore) Update(obj interface{}) error {
|
||||
cfg := obj.(*api.ConfigMap)
|
||||
_, err := a.client.ConfigMaps(cfg.Namespace).Update(cfg)
|
||||
_, err := a.client.Core().ConfigMaps(cfg.Namespace).Update(cfg)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete deletes the existing config map object.
|
||||
func (a *ApiServerConfigMapStore) Delete(obj interface{}) error {
|
||||
func (a *APIServerConfigMapStore) Delete(obj interface{}) error {
|
||||
cfg := obj.(*api.ConfigMap)
|
||||
return a.client.ConfigMaps(cfg.Namespace).Delete(cfg.Name)
|
||||
return a.client.Core().ConfigMaps(cfg.Namespace).Delete(cfg.Name, &api.DeleteOptions{})
|
||||
}
|
||||
|
||||
// GetByKey returns the config map for a given key.
|
||||
// The key must take the form namespace/name.
|
||||
func (a *ApiServerConfigMapStore) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
func (a *APIServerConfigMapStore) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
nsName := strings.Split(key, "/")
|
||||
if len(nsName) != 2 {
|
||||
return nil, false, fmt.Errorf("Failed to get key %v, unexpecte format, expecting ns/name", key)
|
||||
}
|
||||
ns, name := nsName[0], nsName[1]
|
||||
cfg, err := a.client.ConfigMaps(ns).Get(name)
|
||||
cfg, err := a.client.Core().ConfigMaps(ns).Get(name)
|
||||
if err != nil {
|
||||
// Translate not found errors to found=false, err=nil
|
||||
if errors.IsNotFound(err) {
|
||||
|
@ -172,6 +172,6 @@ func (a *ApiServerConfigMapStore) GetByKey(key string) (item interface{}, exists
|
|||
|
||||
// NewConfigMapStore returns a config map store capable of persisting updates
|
||||
// to apiserver.
|
||||
func NewConfigMapStore(c *client.Client) ConfigMapStore {
|
||||
return &ApiServerConfigMapStore{ConfigMapStore: cache.NewStore(cache.MetaNamespaceKeyFunc), client: c}
|
||||
func NewConfigMapStore(c client.Interface) ConfigMapStore {
|
||||
return &APIServerConfigMapStore{ConfigMapStore: cache.NewStore(cache.MetaNamespaceKeyFunc), client: c}
|
||||
}
|
||||
|
|
3
controllers/nginx/.gitignore
vendored
3
controllers/nginx/.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
nginx-ingress-controller
|
||||
rootfs/nginx-ingress-controller
|
||||
*/**/.coverprofile
|
|
@ -1,5 +1,33 @@
|
|||
Changelog
|
||||
|
||||
### 0.9
|
||||
|
||||
- [X] [#1498](https://github.com/kubernetes/contrib/pull/1498) Refactoring of template handling
|
||||
- [X] [#1571](https://github.com/kubernetes/contrib/pull/1571) use POD_NAMESPACE as a namespace in cli parameters
|
||||
- [X] [#1591](https://github.com/kubernetes/contrib/pull/1591) Always listen on port 443, even without ingress rules
|
||||
- [X] [#1596](https://github.com/kubernetes/contrib/pull/1596) Adapt nginx hash sizes to the number of ingress
|
||||
- [X] [#1653](https://github.com/kubernetes/contrib/pull/1653) Update image version
|
||||
- [X] [#1672](https://github.com/kubernetes/contrib/pull/1672) Add firewall rules and ing class clarifications
|
||||
- [X] [#1711](https://github.com/kubernetes/contrib/pull/1711) Add function helpers to nginx template
|
||||
- [X] [#1743](https://github.com/kubernetes/contrib/pull/1743) Allow customisation of the nginx proxy_buffer_size directive via ConfigMap
|
||||
- [X] [#1749](https://github.com/kubernetes/contrib/pull/1749) Readiness probe that works behind a CP lb
|
||||
- [X] [#1751](https://github.com/kubernetes/contrib/pull/1751) Add the name of the upstream in the log
|
||||
- [X] [#1758](https://github.com/kubernetes/contrib/pull/1758) Update nginx to 1.11.4
|
||||
- [X] [#1759](https://github.com/kubernetes/contrib/pull/1759) Add support for default backend in Ingress rule
|
||||
- [X] [#1762](https://github.com/kubernetes/contrib/pull/1762) Add cloud detection
|
||||
- [X] [#1766](https://github.com/kubernetes/contrib/pull/1766) Clarify the controller uses endpoints and not services
|
||||
- [X] [#1767](https://github.com/kubernetes/contrib/pull/1767) Update godeps
|
||||
- [X] [#1772](https://github.com/kubernetes/contrib/pull/1772) Avoid replacing nginx.conf file if the new configuration is invalid
|
||||
- [X] [#1773](https://github.com/kubernetes/contrib/pull/1773) Add annotation to add CORS support
|
||||
- [X] [#1786](https://github.com/kubernetes/contrib/pull/1786) Add docs about go template
|
||||
- [X] [#1796](https://github.com/kubernetes/contrib/pull/1796) Add external authentication support using auth_request
|
||||
- [X] [#1802](https://github.com/kubernetes/contrib/pull/1802) Initialize proxy_upstream_name variable
|
||||
- [X] [#1806](https://github.com/kubernetes/contrib/pull/1806) Add docs about the log format
|
||||
- [X] [#1808](https://github.com/kubernetes/contrib/pull/1808) WebSocket documentation
|
||||
- [X] [#1847](https://github.com/kubernetes/contrib/pull/1847) Change structure of packages
|
||||
- [X] Add annotation for custom upstream timeouts
|
||||
- [X] Mutual TLS auth (https://github.com/kubernetes/contrib/issues/1870)
|
||||
|
||||
### 0.8.3
|
||||
|
||||
- [X] [#1450](https://github.com/kubernetes/contrib/pull/1450) Check for errors in nginx template
|
||||
|
|
|
@ -1,25 +1,52 @@
|
|||
all: push
|
||||
|
||||
BUILDTAGS=
|
||||
|
||||
# 0.0 shouldn't clobber any release builds
|
||||
TAG = 0.8.3
|
||||
PREFIX = gcr.io/google_containers/nginx-ingress-controller
|
||||
RELEASE?=0.0
|
||||
PREFIX?=gcr.io/google_containers/nginx-ingress-controller
|
||||
GOOS?=linux
|
||||
|
||||
REPO_INFO=$(shell git config --get remote.origin.url)
|
||||
|
||||
ifndef VERSION
|
||||
VERSION := git-$(shell git rev-parse --short HEAD)
|
||||
ifndef COMMIT
|
||||
COMMIT := git-$(shell git rev-parse --short HEAD)
|
||||
endif
|
||||
|
||||
controller: controller.go clean
|
||||
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags \
|
||||
"-s -w -X main.version=${VERSION} -X main.gitRepo=${REPO_INFO}" \
|
||||
-o nginx-ingress-controller
|
||||
PKG=k8s.io/ingress/controllers/nginx
|
||||
|
||||
container: controller
|
||||
docker build -t $(PREFIX):$(TAG) .
|
||||
build: clean
|
||||
CGO_ENABLED=0 GOOS=${GOOS} go build -a -installsuffix cgo \
|
||||
-ldflags "-s -w -X ${PKG}/pkg/version.RELEASE=${RELEASE} -X ${PKG}/pkg/version.COMMIT=${COMMIT} -X ${PKG}/pkg/version.REPO=${REPO_INFO}" \
|
||||
-o rootfs/nginx-ingress-controller ${PKG}/pkg/cmd/controller
|
||||
|
||||
push: container
|
||||
gcloud docker push $(PREFIX):$(TAG)
|
||||
container:
|
||||
docker build -t $(PREFIX):$(RELEASE) rootfs
|
||||
|
||||
push:
|
||||
gcloud docker push $(PREFIX):$(RELEASE)
|
||||
|
||||
fmt:
|
||||
@echo "+ $@"
|
||||
@go list -f '{{if len .TestGoFiles}}"gofmt -s -l {{.Dir}}"{{end}}' $(shell go list ${PKG}/... | grep -v vendor) | xargs -L 1 sh -c
|
||||
|
||||
lint:
|
||||
@echo "+ $@"
|
||||
@go list -f '{{if len .TestGoFiles}}"golint {{.Dir}}/..."{{end}}' $(shell go list ${PKG}/... | grep -v vendor) | xargs -L 1 sh -c
|
||||
|
||||
test: fmt lint vet
|
||||
@echo "+ $@"
|
||||
@go test -v -race -tags "$(BUILDTAGS) cgo" $(shell go list ${PKG}/... | grep -v vendor)
|
||||
|
||||
cover:
|
||||
@echo "+ $@"
|
||||
@go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(shell go list ${PKG}/... | grep -v vendor) | xargs -L 1 sh -c
|
||||
gover
|
||||
goveralls -coverprofile=gover.coverprofile -service travis-ci -repotoken ${COVERALLS_TOKEN}
|
||||
|
||||
vet:
|
||||
@echo "+ $@"
|
||||
@go vet $(shell go list ${PKG}/... | grep -v vendor)
|
||||
|
||||
clean:
|
||||
rm -f nginx-ingress-controller
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
|
||||
[](https://travis-ci.org/aledbf/ingress-controller)
|
||||
[](https://coveralls.io/github/aledbf/ingress-controller?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/aledbf/ingress-controller)
|
||||
|
||||
# Nginx Ingress Controller
|
||||
|
||||
This is an nginx Ingress controller that uses [ConfigMap](https://github.com/kubernetes/kubernetes/blob/master/docs/design/configmap.md) to store the nginx configuration. See [Ingress controller documentation](../README.md) for details on how it works.
|
||||
|
||||
## Contents
|
||||
* [Recent changes](#recent-changes)
|
||||
* [Conventions](#conventions)
|
||||
* [Requirements](#what-it-provides)
|
||||
* [Dry running](#dry-running-the-ingress-controller)
|
||||
* [Deployment](#deployment)
|
||||
* [Health checks](#health-checks)
|
||||
* [HTTP](#http)
|
||||
* [HTTPS](#https)
|
||||
* [Default SSL Certificate](#default-ssl-certificate)
|
||||
|
@ -16,6 +22,7 @@ This is an nginx Ingress controller that uses [ConfigMap](https://github.com/kub
|
|||
* [TCP Services](#exposing-tcp-services)
|
||||
* [UDP Services](#exposing-udp-services)
|
||||
* [Proxy Protocol](#proxy-protocol)
|
||||
* [Service Integration](#service-integration)
|
||||
* [NGINX customization](configuration.md)
|
||||
* [NGINX status page](#nginx-status-page)
|
||||
* [Running multiple ingress controllers](#running-multiple-ingress-controllers)
|
||||
|
@ -25,9 +32,15 @@ This is an nginx Ingress controller that uses [ConfigMap](https://github.com/kub
|
|||
* [Local cluster](#local-cluster)
|
||||
* [Debug & Troubleshooting](#troubleshooting)
|
||||
* [Why endpoints and not services?](#why-endpoints-and-not-services)
|
||||
* [Metrics](#metrics)
|
||||
* [Limitations](#limitations)
|
||||
* [NGINX Notes](#nginx-notes)
|
||||
|
||||
## Recent changes
|
||||
|
||||
Change history is available in [CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
|
||||
## Conventions
|
||||
|
||||
Anytime we reference a tls secret, we mean (x509, pem encoded, RSA 2048, etc). You can generate such a certificate with:
|
||||
|
@ -35,21 +48,10 @@ Anytime we reference a tls secret, we mean (x509, pem encoded, RSA 2048, etc). Y
|
|||
and create the secret via `kubectl create secret tls --key file --cert file`
|
||||
|
||||
|
||||
|
||||
## Requirements
|
||||
- Default backend [404-server](https://github.com/kubernetes/contrib/tree/master/404-server)
|
||||
|
||||
|
||||
## Dry running the Ingress controller
|
||||
|
||||
Before deploying the controller to production you might want to run it outside the cluster and observe it.
|
||||
|
||||
```console
|
||||
$ make controller
|
||||
$ mkdir /etc/nginx-ssl
|
||||
$ ./nginx-ingress-controller --running-in-cluster=false --default-backend-service=kube-system/default-http-backend
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
First create a default backend:
|
||||
|
@ -64,6 +66,15 @@ Loadbalancers are created via a ReplicationController or Daemonset:
|
|||
$ kubectl create -f examples/default/rc-default.yaml
|
||||
```
|
||||
|
||||
## Health checks
|
||||
|
||||
The proveded examples in the Ingress controller use a `readiness` and `liveness` probe. By default the URL is `/healthz` and the port `18080`.
|
||||
Using the flag `--health-check-path` is possible to specify a custom path.
|
||||
In some environments only port 80 is allowed to enable health checks. For this reason the Ingress controller exposes this path in the default server.
|
||||
|
||||
If PROXY protocol is enabled the health check must use the default port `18080`. This is required because Kubernetes probes do not understand PROXY protocol.
|
||||
|
||||
|
||||
## HTTP
|
||||
|
||||
First we need to deploy some application to publish. To keep this simple we will use the [echoheaders app](https://github.com/kubernetes/contrib/blob/master/ingress/echoheaders/echo-app.yaml) that just returns information about the http request as output
|
||||
|
@ -141,6 +152,7 @@ Please follow [test.sh](https://github.com/bprashanth/Ingress/blob/master/exampl
|
|||
|
||||
Check the [example](examples/tls/README.md)
|
||||
|
||||
|
||||
### Default SSL Certificate
|
||||
|
||||
NGINX provides the option [server name](http://nginx.org/en/docs/http/server_names.html) as a catch-all in case of requests that do not match one of the configured server names. This configuration works without issues for HTTP traffic. In case of HTTPS NGINX requires a certificate. For this reason the Ingress controller provides the flag `--default-ssl-certificate`. The secret behind this flag contains the default certificate to be used in the mentioned case.
|
||||
|
@ -325,6 +337,12 @@ Amongst others [ELBs in AWS](http://docs.aws.amazon.com/ElasticLoadBalancing/lat
|
|||
Please check the [proxy-protocol](examples/proxy-protocol/) example
|
||||
|
||||
|
||||
## Service Integration
|
||||
|
||||
On clouds like AWS or GCE, using a service with `Type=LoadBalancer` allows the default kubernetes integration, which can save a lot of work.
|
||||
By passing the `--publish-service` argument to the controller, the ingress status will be updated with the load balancer configuration of the service, rather than the IP/s of the node/s.
|
||||
|
||||
|
||||
### Custom errors
|
||||
|
||||
In case of an error in a request the body of the response is obtained from the `default backend`. Each request to the default backend includes two headers:
|
||||
|
@ -427,14 +445,12 @@ I0316 12:24:37.610073 1 command.go:69] change in configuration detected. R
|
|||
- `--v=3` shows details about the service, Ingress rule, endpoint changes and it dumps the nginx configuration in JSON format
|
||||
- `--v=5` configures NGINX in [debug mode](http://nginx.org/en/docs/debugging_log.html)
|
||||
|
||||
### Metrics
|
||||
|
||||
|
||||
*These issues were encountered in past versions of Kubernetes:*
|
||||
|
||||
[1.2.0-alpha7 deployment](https://github.com/kubernetes/kubernetes/blob/master/docs/getting-started-guides/docker.md):
|
||||
|
||||
* make setup-files.sh file in hypercube does not provide 10.0.0.1 IP to make-ca-certs, resulting in CA certs that are issued to the external cluster IP address rather then 10.0.0.1 -> this results in nginx-third-party-lb appearing to get stuck at "Utils.go:177 - Waiting for default/default-http-backend" in the docker logs. Kubernetes will eventually kill the container before nginx-third-party-lb times out with a message indicating that the CA certificate issuer is invalid (wrong ip), to verify this add zeros to the end of initialDelaySeconds and timeoutSeconds and reload the RC, and docker will log this error before kubernetes kills the container.
|
||||
* To fix the above, setup-files.sh must be patched before the cluster is inited (refer to https://github.com/kubernetes/kubernetes/pull/21504)
|
||||
Using the doc [Instrumenting Kubernetes with a new metric](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/instrumentation.md#instrumenting-kubernetes-with-a-new-metric) the Ingress controller
|
||||
exposes the registered metrics via HTTP. Besides the default metrics provided by Prometheus is possible to get the number of reloads `reload_operations` and reloads with error `reload_operations_errors`,
|
||||
ie error in validation in the configuration file before the reload. The metrics are exposed in port `10254` and path `/metrics`.
|
||||
Using curl: `curl -v <pod ip>:10254/metrics`
|
||||
|
||||
|
||||
### Limitations
|
||||
|
@ -452,11 +468,3 @@ The NGINX ingress controller does not uses [Services](http://kubernetes.io/docs/
|
|||
Since `gcr.io/google_containers/nginx-slim:0.8` NGINX contains the next patches:
|
||||
- Dynamic TLS record size [nginx__dynamic_tls_records.patch](https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency/)
|
||||
NGINX provides the parameter `ssl_buffer_size` to adjust the size of the buffer. Default value in NGINX is 16KB. The ingress controller changes the default to 4KB. This improves the [TLS Time To First Byte (TTTFB)](https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/) but the size is fixed. This patches adapts the size of the buffer to the content is being served helping to improve the perceived latency.
|
||||
|
||||
- Add SPDY support back to Nginx with HTTP/2 [nginx_1_9_15_http2_spdy.patch](https://github.com/cloudflare/sslconfig/pull/36)
|
||||
At the same NGINX introduced HTTP/2 support for SPDY was removed. This patch add support for SPDY without compromising HTTP/2 support using the Application-Layer Protocol Negotiation (ALPN) or Next Protocol Negotiation (NPN) Transport Layer Security (TLS) extension to negotiate what protocol the server and client support
|
||||
```
|
||||
openssl s_client -servername www.my-site.com -connect www.my-site.com:443 -nextprotoneg ''
|
||||
CONNECTED(00000003)
|
||||
Protocols advertised by server: h2, spdy/3.1, http/1.1
|
||||
```
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,33 +0,0 @@
|
|||
package = "lua-resty-http"
|
||||
version = "0.07-0"
|
||||
source = {
|
||||
url = "git://github.com/pintsized/lua-resty-http",
|
||||
tag = "v0.07"
|
||||
}
|
||||
description = {
|
||||
summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.",
|
||||
detailed = [[
|
||||
Features an HTTP 1.0 and 1.1 streaming interface to reading
|
||||
bodies using coroutines, for predictable memory usage in Lua
|
||||
land. Alternative simple interface for singleshot requests
|
||||
without manual connection step. Supports chunked transfer
|
||||
encoding, keepalive, pipelining, and trailers. Headers are
|
||||
treated case insensitively. Probably production ready in most
|
||||
cases, though not yet proven in the wild.
|
||||
Recommended by the OpenResty maintainer as a long-term
|
||||
replacement for internal requests through ngx.location.capture.
|
||||
]],
|
||||
homepage = "https://github.com/pintsized/lua-resty-http",
|
||||
license = "2-clause BSD",
|
||||
maintainer = "James Hurst <james@pintsized.co.uk>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.http"] = "lib/resty/http.lua",
|
||||
["resty.http_headers"] = "lib/resty/http_headers.lua"
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/healthz"
|
||||
kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
)
|
||||
|
||||
const (
|
||||
healthPort = 10254
|
||||
)
|
||||
|
||||
var (
|
||||
// value overwritten during build. This can be used to resolve issues.
|
||||
version = "0.8.3"
|
||||
gitRepo = "https://github.com/kubernetes/contrib"
|
||||
|
||||
flags = pflag.NewFlagSet("", pflag.ExitOnError)
|
||||
|
||||
defaultSvc = flags.String("default-backend-service", "",
|
||||
`Service used to serve a 404 page for the default backend. Takes the form
|
||||
namespace/name. The controller uses the first node port of this Service for
|
||||
the default backend.`)
|
||||
|
||||
nxgConfigMap = flags.String("nginx-configmap", "",
|
||||
`Name of the ConfigMap that containes the custom nginx configuration to use`)
|
||||
|
||||
tcpConfigMapName = flags.String("tcp-services-configmap", "",
|
||||
`Name of the ConfigMap that containes the definition of the TCP services to expose.
|
||||
The key in the map indicates the external port to be used. The value is the name of the
|
||||
service with the format namespace/serviceName and the port of the service could be a number of the
|
||||
name of the port.
|
||||
The ports 80 and 443 are not allowed as external ports. This ports are reserved for nginx`)
|
||||
|
||||
udpConfigMapName = flags.String("udp-services-configmap", "",
|
||||
`Name of the ConfigMap that containes the definition of the UDP services to expose.
|
||||
The key in the map indicates the external port to be used. The value is the name of the
|
||||
service with the format namespace/serviceName and the port of the service could be a number of the
|
||||
name of the port.`)
|
||||
|
||||
resyncPeriod = flags.Duration("sync-period", 30*time.Second,
|
||||
`Relist and confirm cloud resources this often.`)
|
||||
|
||||
watchNamespace = flags.String("watch-namespace", api.NamespaceAll,
|
||||
`Namespace to watch for Ingress. Default is to watch all namespaces`)
|
||||
|
||||
healthzPort = flags.Int("healthz-port", healthPort, "port for healthz endpoint.")
|
||||
|
||||
profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
|
||||
|
||||
defSSLCertificate = flags.String("default-ssl-certificate", "", `Name of the secret that contains a SSL
|
||||
certificate to be used as default for a HTTPS catch-all server`)
|
||||
|
||||
defHealthzURL = flags.String("health-check-path", "/ingress-controller-healthz", `Defines the URL to
|
||||
be used as health check inside in the default server in NGINX.`)
|
||||
)
|
||||
|
||||
func main() {
|
||||
flags.AddGoFlagSet(flag.CommandLine)
|
||||
flags.Parse(os.Args)
|
||||
clientConfig := kubectl_util.DefaultClientConfig(flags)
|
||||
|
||||
glog.Infof("Using build: %v - %v", gitRepo, version)
|
||||
|
||||
if *defaultSvc == "" {
|
||||
glog.Fatalf("Please specify --default-backend-service")
|
||||
}
|
||||
|
||||
kubeClient, err := unversioned.NewInCluster()
|
||||
if err != nil {
|
||||
config, err := clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
glog.Fatalf("error configuring the client: %v", err)
|
||||
}
|
||||
kubeClient, err = unversioned.New(config)
|
||||
if err != nil {
|
||||
glog.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
runtimePodInfo, err := getPodDetails(kubeClient)
|
||||
if err != nil {
|
||||
runtimePodInfo = &podInfo{NodeIP: "127.0.0.1"}
|
||||
glog.Warningf("unexpected error getting runtime information: %v", err)
|
||||
}
|
||||
if err := isValidService(kubeClient, *defaultSvc); err != nil {
|
||||
glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
|
||||
}
|
||||
glog.Infof("Validated %v as the default backend", *defaultSvc)
|
||||
|
||||
if *nxgConfigMap != "" {
|
||||
_, _, err = parseNsName(*nxgConfigMap)
|
||||
if err != nil {
|
||||
glog.Fatalf("configmap error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod,
|
||||
*defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName,
|
||||
*udpConfigMapName, *defSSLCertificate, *defHealthzURL, runtimePodInfo)
|
||||
if err != nil {
|
||||
glog.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
go registerHandlers(lbc)
|
||||
go handleSigterm(lbc)
|
||||
|
||||
lbc.Run()
|
||||
|
||||
for {
|
||||
glog.Infof("Handled quit, awaiting pod deletion")
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// podInfo contains runtime information about the pod
|
||||
type podInfo struct {
|
||||
PodName string
|
||||
PodNamespace string
|
||||
NodeIP string
|
||||
}
|
||||
|
||||
func registerHandlers(lbc *loadBalancerController) {
|
||||
mux := http.NewServeMux()
|
||||
healthz.InstallHandler(mux, lbc.nginx)
|
||||
|
||||
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "build: %v - %v", gitRepo, version)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
|
||||
lbc.Stop()
|
||||
})
|
||||
|
||||
if *profiling {
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%v", *healthzPort),
|
||||
Handler: mux,
|
||||
}
|
||||
glog.Fatal(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func handleSigterm(lbc *loadBalancerController) {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, syscall.SIGTERM)
|
||||
<-signalChan
|
||||
glog.Infof("Received SIGTERM, shutting down")
|
||||
|
||||
exitCode := 0
|
||||
if err := lbc.Stop(); err != nil {
|
||||
glog.Infof("Error during shutdown %v", err)
|
||||
exitCode = 1
|
||||
}
|
||||
|
||||
glog.Infof("Exiting with %v", exitCode)
|
||||
os.Exit(exitCode)
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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 nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/healthz"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/ingress"
|
||||
)
|
||||
|
||||
// Start starts a nginx (master process) and waits. If the process ends
|
||||
// we need to kill the controller process and return the reason.
|
||||
func (ngx *Manager) Start() {
|
||||
glog.Info("Starting NGINX process...")
|
||||
cmd := exec.Command("nginx")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
glog.Errorf("nginx error: %v", err)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
glog.Errorf("nginx error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckAndReload verify if the nginx configuration changed and sends a reload
|
||||
//
|
||||
// the master process receives the signal to reload configuration, it checks
|
||||
// the syntax validity of the new configuration file and tries to apply the
|
||||
// configuration provided in it. If this is a success, the master process starts
|
||||
// new worker processes and sends messages to old worker processes, requesting them
|
||||
// to shut down. Otherwise, the master process rolls back the changes and continues
|
||||
// to work with the old configuration. Old worker processes, receiving a command to
|
||||
// shut down, stop accepting new connections and continue to service current requests
|
||||
// until all such requests are serviced. After that, the old worker processes exit.
|
||||
// http://nginx.org/en/docs/beginners_guide.html#control
|
||||
func (ngx *Manager) CheckAndReload(cfg config.Configuration, ingressCfg ingress.Configuration) error {
|
||||
ngx.reloadRateLimiter.Accept()
|
||||
|
||||
ngx.reloadLock.Lock()
|
||||
defer ngx.reloadLock.Unlock()
|
||||
|
||||
newCfg, err := ngx.template.Write(cfg, ingressCfg, ngx.testTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write new nginx configuration. Avoiding reload: %v", err)
|
||||
}
|
||||
|
||||
changed, err := ngx.needsReload(newCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err := ngx.shellOut("nginx -s reload"); err != nil {
|
||||
return fmt.Errorf("error reloading nginx: %v", err)
|
||||
}
|
||||
|
||||
glog.Info("change in configuration detected. Reloading...")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shellOut executes a command and returns its combined standard output and standard
|
||||
// error in case of an error in the execution
|
||||
func (ngx *Manager) shellOut(cmd string) error {
|
||||
out, err := exec.Command("sh", "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
glog.Errorf("failed to execute %v: %v", cmd, string(out))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check to verify Manager implements HealthzChecker interface
|
||||
var _ healthz.HealthzChecker = Manager{}
|
||||
|
||||
// Name returns the healthcheck name
|
||||
func (ngx Manager) Name() string {
|
||||
return "NGINX"
|
||||
}
|
||||
|
||||
// Check returns if the nginx healthz endpoint is returning ok (status code 200)
|
||||
func (ngx Manager) Check(_ *http.Request) error {
|
||||
res, err := http.Get("http://127.0.0.1:18080/healthz")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("NGINX is unhealthy")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// testTemplate checks if the NGINX configuration inside the byte array is valid
|
||||
// running the command "nginx -t" using a temporal file.
|
||||
func (ngx Manager) testTemplate(cfg []byte) error {
|
||||
tmpfile, err := ioutil.TempFile("", "nginx-cfg")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
ioutil.WriteFile(tmpfile.Name(), cfg, 0644)
|
||||
if err := ngx.shellOut(fmt.Sprintf("nginx -t -c %v", tmpfile.Name())); err != nil {
|
||||
return fmt.Errorf("invalid nginx configuration: %v", err)
|
||||
}
|
||||
// in case of error do not remove temporal file
|
||||
defer os.Remove(tmpfile.Name())
|
||||
return nil
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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/contrib/ingress/controllers/nginx/nginx/auth"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/authreq"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/ipwhitelist"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/ratelimit"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
|
||||
)
|
||||
|
||||
// Configuration describes an NGINX configuration
|
||||
type Configuration struct {
|
||||
Upstreams []*Upstream
|
||||
Servers []*Server
|
||||
TCPUpstreams []*Location
|
||||
UDPUpstreams []*Location
|
||||
}
|
||||
|
||||
// Upstream describes an NGINX upstream
|
||||
type Upstream struct {
|
||||
Name string
|
||||
Backends []UpstreamServer
|
||||
Secure bool
|
||||
}
|
||||
|
||||
// UpstreamByNameServers sorts upstreams by name
|
||||
type UpstreamByNameServers []*Upstream
|
||||
|
||||
func (c UpstreamByNameServers) Len() int { return len(c) }
|
||||
func (c UpstreamByNameServers) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c UpstreamByNameServers) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
}
|
||||
|
||||
// UpstreamServer describes a server in an NGINX upstream
|
||||
type UpstreamServer struct {
|
||||
Address string
|
||||
Port string
|
||||
MaxFails int
|
||||
FailTimeout int
|
||||
}
|
||||
|
||||
// UpstreamServerByAddrPort sorts upstream servers by address and port
|
||||
type UpstreamServerByAddrPort []UpstreamServer
|
||||
|
||||
func (c UpstreamServerByAddrPort) Len() int { return len(c) }
|
||||
func (c UpstreamServerByAddrPort) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c UpstreamServerByAddrPort) 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
|
||||
}
|
||||
|
||||
// Server describes an NGINX server
|
||||
type Server struct {
|
||||
Name string
|
||||
Locations []*Location
|
||||
SSL bool
|
||||
SSLCertificate string
|
||||
SSLCertificateKey string
|
||||
SSLPemChecksum string
|
||||
}
|
||||
|
||||
// ServerByName sorts server 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].Name < c[j].Name
|
||||
}
|
||||
|
||||
// Location describes an NGINX location
|
||||
type Location struct {
|
||||
Path string
|
||||
IsDefBackend bool
|
||||
Upstream Upstream
|
||||
Auth auth.Nginx
|
||||
RateLimit ratelimit.RateLimit
|
||||
Redirect rewrite.Redirect
|
||||
SecureUpstream bool
|
||||
Whitelist ipwhitelist.SourceRange
|
||||
EnableCORS bool
|
||||
ExternalAuthURL authreq.Auth
|
||||
}
|
||||
|
||||
// LocationByPath sorts location by path
|
||||
// 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 NGINX
|
||||
type SSLCert struct {
|
||||
CertFileName string
|
||||
KeyFileName string
|
||||
// PemFileName contains the path to the file with the certificate and key concatenated
|
||||
PemFileName string
|
||||
// PemSHA contains the sha1 of the pem file.
|
||||
// This is used to detect changes in the secret that contains the certificates
|
||||
PemSHA string
|
||||
// CN contains all the common names defined in the SSL certificate
|
||||
CN []string
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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 nginx
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util/flowcontrol"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/ingress"
|
||||
ngx_template "k8s.io/contrib/ingress/controllers/nginx/nginx/template"
|
||||
)
|
||||
|
||||
var (
|
||||
tmplPath = "/etc/nginx/template/nginx.tmpl"
|
||||
)
|
||||
|
||||
// Manager ...
|
||||
type Manager struct {
|
||||
ConfigFile string
|
||||
|
||||
defCfg config.Configuration
|
||||
|
||||
defResolver string
|
||||
|
||||
sslDHParam string
|
||||
|
||||
reloadRateLimiter flowcontrol.RateLimiter
|
||||
|
||||
// template loaded ready to be used to generate the nginx configuration file
|
||||
template *ngx_template.Template
|
||||
|
||||
reloadLock *sync.Mutex
|
||||
}
|
||||
|
||||
// NewDefaultServer return an UpstreamServer to be use as default server that returns 503.
|
||||
func NewDefaultServer() ingress.UpstreamServer {
|
||||
return ingress.UpstreamServer{Address: "127.0.0.1", Port: "8181"}
|
||||
}
|
||||
|
||||
// NewUpstream creates an upstream without servers.
|
||||
func NewUpstream(name string) *ingress.Upstream {
|
||||
return &ingress.Upstream{
|
||||
Name: name,
|
||||
Backends: []ingress.UpstreamServer{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewManager ...
|
||||
func NewManager(kubeClient *client.Client) *Manager {
|
||||
ngx := &Manager{
|
||||
ConfigFile: "/etc/nginx/nginx.conf",
|
||||
defCfg: config.NewDefault(),
|
||||
reloadLock: &sync.Mutex{},
|
||||
reloadRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.1, 1),
|
||||
}
|
||||
|
||||
res, err := getDNSServers()
|
||||
if err != nil {
|
||||
glog.Warningf("error reading nameservers: %v", err)
|
||||
}
|
||||
ngx.defResolver = strings.Join(res, " ")
|
||||
|
||||
ngx.createCertsDir(config.SSLDirectory)
|
||||
|
||||
ngx.sslDHParam = ngx.SearchDHParamFile(config.SSLDirectory)
|
||||
|
||||
var onChange func()
|
||||
onChange = func() {
|
||||
template, err := ngx_template.NewTemplate(tmplPath, onChange)
|
||||
if err != nil {
|
||||
glog.Warningf("invalid NGINX template: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ngx.template.Close()
|
||||
ngx.template = template
|
||||
glog.Info("new NGINX template loaded")
|
||||
}
|
||||
|
||||
template, err := ngx_template.NewTemplate(tmplPath, onChange)
|
||||
if err != nil {
|
||||
glog.Fatalf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
||||
ngx.template = template
|
||||
return ngx
|
||||
}
|
||||
|
||||
func (nginx *Manager) createCertsDir(base string) {
|
||||
if err := os.Mkdir(base, os.ModeDir); err != nil {
|
||||
if os.IsExist(err) {
|
||||
glog.Infof("%v already exists", err)
|
||||
return
|
||||
}
|
||||
glog.Fatalf("Couldn't create directory %v: %v", base, err)
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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 nginx
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/ingress"
|
||||
)
|
||||
|
||||
// AddOrUpdateCertAndKey creates a .pem file wth the cert and the key with the specified name
|
||||
func (nginx *Manager) AddOrUpdateCertAndKey(name string, cert string, key string) (ingress.SSLCert, error) {
|
||||
temporaryPemFileName := fmt.Sprintf("%v.pem", name)
|
||||
pemFileName := fmt.Sprintf("%v/%v.pem", config.SSLDirectory, name)
|
||||
|
||||
temporaryPemFile, err := ioutil.TempFile("", temporaryPemFileName)
|
||||
if err != nil {
|
||||
return ingress.SSLCert{}, fmt.Errorf("Couldn't create temp pem file %v: %v", temporaryPemFile.Name(), err)
|
||||
}
|
||||
|
||||
_, err = temporaryPemFile.WriteString(fmt.Sprintf("%v\n%v", cert, key))
|
||||
if err != nil {
|
||||
return ingress.SSLCert{}, fmt.Errorf("Couldn't write to pem file %v: %v", temporaryPemFile.Name(), err)
|
||||
}
|
||||
|
||||
err = temporaryPemFile.Close()
|
||||
if err != nil {
|
||||
return ingress.SSLCert{}, fmt.Errorf("Couldn't close temp pem file %v: %v", temporaryPemFile.Name(), err)
|
||||
}
|
||||
|
||||
cn, err := nginx.commonNames(temporaryPemFile.Name())
|
||||
if err != nil {
|
||||
os.Remove(temporaryPemFile.Name())
|
||||
return ingress.SSLCert{}, err
|
||||
}
|
||||
|
||||
err = os.Rename(temporaryPemFile.Name(), pemFileName)
|
||||
if err != nil {
|
||||
os.Remove(temporaryPemFile.Name())
|
||||
return ingress.SSLCert{}, fmt.Errorf("Couldn't move temp pem file %v to destination %v: %v", temporaryPemFile.Name(), pemFileName, err)
|
||||
}
|
||||
|
||||
return ingress.SSLCert{
|
||||
CertFileName: cert,
|
||||
KeyFileName: key,
|
||||
PemFileName: pemFileName,
|
||||
PemSHA: nginx.pemSHA1(pemFileName),
|
||||
CN: cn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// commonNames checks if the certificate and key file are valid
|
||||
// returning the result of the validation and the list of hostnames
|
||||
// contained in the common name/s
|
||||
func (nginx *Manager) commonNames(pemFileName string) ([]string, error) {
|
||||
pemCerts, err := ioutil.ReadFile(pemFileName)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pemCerts)
|
||||
if block == nil {
|
||||
return []string{}, fmt.Errorf("No valid PEM formatted block found")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
cn := []string{cert.Subject.CommonName}
|
||||
if len(cert.DNSNames) > 0 {
|
||||
cn = append(cn, cert.DNSNames...)
|
||||
}
|
||||
|
||||
glog.V(3).Infof("found %v common names: %v\n", cn, len(cn))
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
// SearchDHParamFile iterates all the secrets mounted inside the /etc/nginx-ssl directory
|
||||
// in order to find a file with the name dhparam.pem. If such file exists it will
|
||||
// returns the path. If not it just returns an empty string
|
||||
func (nginx *Manager) SearchDHParamFile(baseDir string) string {
|
||||
files, _ := ioutil.ReadDir(baseDir)
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
dhPath := fmt.Sprintf("%v/%v/dhparam.pem", baseDir, file.Name())
|
||||
if _, err := os.Stat(dhPath); err == nil {
|
||||
glog.Infof("using file '%v' for parameter ssl_dhparam", dhPath)
|
||||
return dhPath
|
||||
}
|
||||
}
|
||||
|
||||
glog.Warning("no file dhparam.pem found in secrets")
|
||||
return ""
|
||||
}
|
||||
|
||||
// pemSHA1 returns the SHA1 of a pem file. This is used to
|
||||
// reload NGINX in case a secret with a SSL certificate changed.
|
||||
func (nginx *Manager) pemSHA1(filename string) string {
|
||||
hasher := sha1.New()
|
||||
s, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
hasher.Write(s)
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
|
@ -1,224 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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 nginx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
)
|
||||
|
||||
const (
|
||||
customHTTPErrors = "custom-http-errors"
|
||||
skipAccessLogUrls = "skip-access-log-urls"
|
||||
)
|
||||
|
||||
// getDNSServers returns the list of nameservers located in the file /etc/resolv.conf
|
||||
func getDNSServers() ([]string, error) {
|
||||
var nameservers []string
|
||||
file, err := ioutil.ReadFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return nameservers, err
|
||||
}
|
||||
|
||||
// Lines of the form "nameserver 1.2.3.4" accumulate.
|
||||
lines := strings.Split(string(file), "\n")
|
||||
for l := range lines {
|
||||
trimmed := strings.TrimSpace(lines[l])
|
||||
if strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(trimmed)
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
if fields[0] == "nameserver" {
|
||||
nameservers = append(nameservers, fields[1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(3).Infof("nameservers to use: %v", nameservers)
|
||||
return nameservers, nil
|
||||
}
|
||||
|
||||
// getConfigKeyToStructKeyMap returns a map with the ConfigMapKey as key and the StructName as value.
|
||||
func getConfigKeyToStructKeyMap() map[string]string {
|
||||
keyMap := map[string]string{}
|
||||
n := &config.Configuration{}
|
||||
val := reflect.Indirect(reflect.ValueOf(n))
|
||||
for i := 0; i < val.Type().NumField(); i++ {
|
||||
fieldSt := val.Type().Field(i)
|
||||
configMapKey := strings.Split(fieldSt.Tag.Get("structs"), ",")[0]
|
||||
structKey := fieldSt.Name
|
||||
keyMap[configMapKey] = structKey
|
||||
}
|
||||
return keyMap
|
||||
}
|
||||
|
||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||
func (ngx *Manager) ReadConfig(conf *api.ConfigMap) config.Configuration {
|
||||
if len(conf.Data) == 0 {
|
||||
return config.NewDefault()
|
||||
}
|
||||
|
||||
cfgCM := config.Configuration{}
|
||||
cfgDefault := config.NewDefault()
|
||||
|
||||
metadata := &mapstructure.Metadata{}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
TagName: "structs",
|
||||
Result: &cfgCM,
|
||||
WeaklyTypedInput: true,
|
||||
Metadata: metadata,
|
||||
})
|
||||
|
||||
cErrors := make([]int, 0)
|
||||
if val, ok := conf.Data[customHTTPErrors]; ok {
|
||||
delete(conf.Data, customHTTPErrors)
|
||||
for _, i := range strings.Split(val, ",") {
|
||||
j, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
glog.Warningf("%v is not a valid http code: %v", i, err)
|
||||
} else {
|
||||
cErrors = append(cErrors, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cSkipUrls := make([]string, 0)
|
||||
if val, ok := conf.Data[skipAccessLogUrls]; ok {
|
||||
delete(conf.Data, skipAccessLogUrls)
|
||||
cSkipUrls = strings.Split(val, ",")
|
||||
}
|
||||
|
||||
err = decoder.Decode(conf.Data)
|
||||
if err != nil {
|
||||
glog.Infof("%v", err)
|
||||
}
|
||||
|
||||
keyMap := getConfigKeyToStructKeyMap()
|
||||
|
||||
valCM := reflect.Indirect(reflect.ValueOf(cfgCM))
|
||||
|
||||
for _, key := range metadata.Keys {
|
||||
fieldName, ok := keyMap[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
valDefault := reflect.ValueOf(&cfgDefault).Elem().FieldByName(fieldName)
|
||||
|
||||
fieldCM := valCM.FieldByName(fieldName)
|
||||
|
||||
if valDefault.IsValid() {
|
||||
valDefault.Set(fieldCM)
|
||||
}
|
||||
}
|
||||
|
||||
cfgDefault.CustomHTTPErrors = ngx.filterErrors(cErrors)
|
||||
cfgDefault.SkipAccessLogURLs = cSkipUrls
|
||||
// no custom resolver means use the system resolver
|
||||
if cfgDefault.Resolver == "" {
|
||||
cfgDefault.Resolver = ngx.defResolver
|
||||
}
|
||||
return cfgDefault
|
||||
}
|
||||
|
||||
func (ngx *Manager) filterErrors(errCodes []int) []int {
|
||||
fa := make([]int, 0)
|
||||
for _, errCode := range errCodes {
|
||||
if errCode > 299 && errCode < 600 {
|
||||
fa = append(fa, errCode)
|
||||
} else {
|
||||
glog.Warningf("error code %v is not valid for custom error pages", errCode)
|
||||
}
|
||||
}
|
||||
|
||||
return fa
|
||||
}
|
||||
|
||||
func (ngx *Manager) needsReload(data []byte) (bool, error) {
|
||||
filename := ngx.ConfigFile
|
||||
in, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
src, err := ioutil.ReadAll(in)
|
||||
in.Close()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, data) {
|
||||
err = ioutil.WriteFile(filename, data, 0644)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
dData, err := diff(src, data)
|
||||
if err != nil {
|
||||
glog.Errorf("error computing diff: %s", err)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if glog.V(2) {
|
||||
glog.Infof("NGINX configuration diff a/%s b/%s\n", filename, filename)
|
||||
glog.Infof("%v", string(dData))
|
||||
}
|
||||
|
||||
return len(dData) > 0, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func diff(b1, b2 []byte) (data []byte, err error) {
|
||||
f1, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
defer f1.Close()
|
||||
|
||||
f2, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f2.Name())
|
||||
defer f2.Close()
|
||||
|
||||
f1.Write(b1)
|
||||
f2.Write(b2)
|
||||
|
||||
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
||||
if len(data) > 0 {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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 nginx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
)
|
||||
|
||||
func getConfigNginxBool(data map[string]string) config.Configuration {
|
||||
manager := &Manager{}
|
||||
configMap := &api.ConfigMap{
|
||||
Data: data,
|
||||
}
|
||||
return manager.ReadConfig(configMap)
|
||||
}
|
||||
|
||||
func TestManagerReadConfigBoolFalse(t *testing.T) {
|
||||
configNginx := getConfigNginxBool(map[string]string{
|
||||
"hsts-include-subdomains": "false",
|
||||
"use-proxy-protocol": "false",
|
||||
})
|
||||
if configNginx.HSTSIncludeSubdomains {
|
||||
t.Error("Failed to config boolean value (default true) to false")
|
||||
}
|
||||
if configNginx.UseProxyProtocol {
|
||||
t.Error("Failed to config boolean value (default false) to false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerReadConfigBoolTrue(t *testing.T) {
|
||||
configNginx := getConfigNginxBool(map[string]string{
|
||||
"hsts-include-subdomains": "true",
|
||||
"use-proxy-protocol": "true",
|
||||
})
|
||||
if !configNginx.HSTSIncludeSubdomains {
|
||||
t.Error("Failed to config boolean value (default true) to true")
|
||||
}
|
||||
if !configNginx.UseProxyProtocol {
|
||||
t.Error("Failed to config boolean value (default false) to true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerReadConfigBoolNothing(t *testing.T) {
|
||||
configNginx := getConfigNginxBool(map[string]string{
|
||||
"invaild-key": "true",
|
||||
})
|
||||
if !configNginx.HSTSIncludeSubdomains {
|
||||
t.Error("Failed to get default boolean value true")
|
||||
}
|
||||
if configNginx.UseProxyProtocol {
|
||||
t.Error("Failed to get default boolean value false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerReadConfigStringSet(t *testing.T) {
|
||||
configNginx := getConfigNginxBool(map[string]string{
|
||||
"ssl-protocols": "TLSv1.2",
|
||||
})
|
||||
exp := "TLSv1.2"
|
||||
if configNginx.SSLProtocols != exp {
|
||||
t.Errorf("Failed to set string value true actual='%s' expected='%s'", configNginx.SSLProtocols, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerReadConfigStringNothing(t *testing.T) {
|
||||
configNginx := getConfigNginxBool(map[string]string{
|
||||
"not-existing": "TLSv1.2",
|
||||
})
|
||||
exp := "10m"
|
||||
if configNginx.SSLSessionTimeout != exp {
|
||||
t.Errorf("Failed to set string value true actual='%s' expected='%s'", configNginx.SSLSessionTimeout, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDNSServers(t *testing.T) {
|
||||
s, err := getDNSServers()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error reading /etc/resolv.conf file: %v", err)
|
||||
}
|
||||
if len(s) < 1 {
|
||||
t.Error("expected at least 1 nameserver in /etc/resolv.conf")
|
||||
}
|
||||
}
|
60
controllers/nginx/pkg/cmd/controller/main.go
Normal file
60
controllers/nginx/pkg/cmd/controller/main.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Copyright 2015 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 main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/controller"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// start a new nginx controller
|
||||
ngx := newNGINXController()
|
||||
// create a custom Ingress controller using NGINX as backend
|
||||
ic := controller.NewIngressController(ngx)
|
||||
go handleSigterm(ic)
|
||||
// start the controller
|
||||
ic.Start()
|
||||
// wait
|
||||
glog.Infof("shutting down Ingress controller...")
|
||||
for {
|
||||
glog.Infof("Handled quit, awaiting pod deletion")
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSigterm(ic controller.Interface) {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, syscall.SIGTERM)
|
||||
<-signalChan
|
||||
glog.Infof("Received SIGTERM, shutting down")
|
||||
|
||||
exitCode := 0
|
||||
if err := ic.Stop(); err != nil {
|
||||
glog.Infof("Error during shutdown %v", err)
|
||||
exitCode = 1
|
||||
}
|
||||
|
||||
glog.Infof("Exiting with %v", exitCode)
|
||||
os.Exit(exitCode)
|
||||
}
|
266
controllers/nginx/pkg/cmd/controller/nginx.go
Normal file
266
controllers/nginx/pkg/cmd/controller/nginx.go
Normal file
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
Copyright 2015 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
|
||||
"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 (
|
||||
tmplPath = "/etc/nginx/template/nginx.tmpl"
|
||||
cfgPath = "/etc/nginx/nginx.conf"
|
||||
binary = "/usr/sbin/nginx"
|
||||
)
|
||||
|
||||
// newNGINXController creates a new NGINX Ingress controller.
|
||||
// If the environment variable NGINX_BINARY exists it will be used
|
||||
// as source for nginx commands
|
||||
func newNGINXController() ingress.Controller {
|
||||
ngx := os.Getenv("NGINX_BINARY")
|
||||
if ngx == "" {
|
||||
ngx = binary
|
||||
}
|
||||
n := NGINXController{binary: ngx}
|
||||
|
||||
var onChange func()
|
||||
onChange = func() {
|
||||
template, err := ngx_template.NewTemplate(tmplPath, onChange)
|
||||
if err != nil {
|
||||
// this error is different from the rest because it must be clear why nginx is not working
|
||||
glog.Errorf(`
|
||||
-------------------------------------------------------------------------------
|
||||
Error loading new template : %v
|
||||
-------------------------------------------------------------------------------
|
||||
`, err)
|
||||
return
|
||||
}
|
||||
|
||||
n.t.Close()
|
||||
n.t = template
|
||||
glog.Info("new NGINX template loaded")
|
||||
}
|
||||
|
||||
ngxTpl, err := ngx_template.NewTemplate(tmplPath, onChange)
|
||||
if err != nil {
|
||||
glog.Fatalf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
||||
n.t = ngxTpl
|
||||
return n
|
||||
}
|
||||
|
||||
// NGINXController ...
|
||||
type NGINXController struct {
|
||||
t *ngx_template.Template
|
||||
|
||||
binary string
|
||||
}
|
||||
|
||||
// Start ...
|
||||
func (n NGINXController) Start() {
|
||||
glog.Info("starting NGINX process...")
|
||||
cmd := exec.Command(n.binary, "-c", cfgPath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
glog.Fatalf("nginx error: %v", err)
|
||||
}
|
||||
if err := cmd.Wait(); err != nil {
|
||||
glog.Errorf("nginx error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop ...
|
||||
func (n NGINXController) Stop() error {
|
||||
n.t.Close()
|
||||
return exec.Command(n.binary, "-s", "stop").Run()
|
||||
}
|
||||
|
||||
// Restart ...
|
||||
func (n NGINXController) Restart(data []byte) ([]byte, error) {
|
||||
err := ioutil.WriteFile(cfgPath, data, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exec.Command(n.binary, "-s", "reload").CombinedOutput()
|
||||
}
|
||||
|
||||
// Test checks is a file contains a valid NGINX configuration
|
||||
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 {
|
||||
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 {
|
||||
in, err := os.Open(cfgPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
src, err := ioutil.ReadAll(in)
|
||||
in.Close()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, data) {
|
||||
tmpfile, err := ioutil.TempFile("", "nginx-cfg-diff")
|
||||
if err != nil {
|
||||
glog.Errorf("error creating temporal file: %s", err)
|
||||
return false
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
err = ioutil.WriteFile(tmpfile.Name(), data, 0644)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
diffOutput, err := diff(src, data)
|
||||
if err != nil {
|
||||
glog.Errorf("error computing diff: %s", err)
|
||||
return true
|
||||
}
|
||||
|
||||
if glog.V(2) {
|
||||
glog.Infof("NGINX configuration diff\n")
|
||||
glog.Infof("%v", string(diffOutput))
|
||||
}
|
||||
return len(diffOutput) > 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// testTemplate checks if the NGINX configuration inside the byte array is valid
|
||||
// running the command "nginx -t" using a temporal file.
|
||||
func (n NGINXController) testTemplate(cfg []byte) error {
|
||||
tmpfile, err := ioutil.TempFile("", "nginx-cfg")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
ioutil.WriteFile(tmpfile.Name(), cfg, 0644)
|
||||
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(`
|
||||
-------------------------------------------------------------------------------
|
||||
Error: %v
|
||||
%v
|
||||
-------------------------------------------------------------------------------
|
||||
`, err, string(out))
|
||||
}
|
||||
|
||||
os.Remove(tmpfile.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnUpdate is called by syncQueue in https://github.com/aledbf/ingress-controller/blob/master/pkg/ingress/controller/controller.go#L82
|
||||
// periodically to keep the configuration in sync.
|
||||
//
|
||||
// convert configmap to custom configuration object (different in each implementation)
|
||||
// write the custom template (the complexity depends on the implementation)
|
||||
// write the configuration file
|
||||
// returning nill implies the backend will be reloaded.
|
||||
// if an error is returned means requeue the update
|
||||
func (n NGINXController) OnUpdate(cmap *api.ConfigMap, ingressCfg ingress.Configuration) ([]byte, error) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := ngx_template.ReadConfig(cmap)
|
||||
|
||||
// NGINX cannot resize the has tables used to store server names.
|
||||
// For this reason we check if the defined size defined is correct
|
||||
// for the FQDN defined in the ingress rules adjusting the value
|
||||
// if is required.
|
||||
// https://trac.nginx.org/nginx/ticket/352
|
||||
// https://trac.nginx.org/nginx/ticket/631
|
||||
nameHashBucketSize := nextPowerOf2(longestName)
|
||||
if nameHashBucketSize > cfg.ServerNameHashBucketSize {
|
||||
glog.V(3).Infof("adjusting ServerNameHashBucketSize variable from %v to %v",
|
||||
cfg.ServerNameHashBucketSize, nameHashBucketSize)
|
||||
cfg.ServerNameHashBucketSize = nameHashBucketSize
|
||||
}
|
||||
serverNameHashMaxSize := nextPowerOf2(serverNames)
|
||||
if serverNameHashMaxSize > cfg.ServerNameHashMaxSize {
|
||||
glog.V(3).Infof("adjusting ServerNameHashMaxSize variable from %v to %v",
|
||||
cfg.ServerNameHashMaxSize, serverNameHashMaxSize)
|
||||
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.TCPUpstreams
|
||||
conf["udpUpstreams"] = ingressCfg.UDPUpstreams
|
||||
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)
|
||||
}
|
||||
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
// https://play.golang.org/p/TVSyCcdxUh
|
||||
func nextPowerOf2(v int) int {
|
||||
v--
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
v++
|
||||
|
||||
return v
|
||||
}
|
62
controllers/nginx/pkg/cmd/controller/utils.go
Normal file
62
controllers/nginx/pkg/cmd/controller/utils.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2015 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 main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/sysctl"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// sysctlSomaxconn returns the value of net.core.somaxconn, i.e.
|
||||
// maximum number of connections that can be queued for acceptance
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
|
||||
func sysctlSomaxconn() int {
|
||||
maxConns, err := sysctl.New().GetSysctl("net/core/somaxconn")
|
||||
if err != nil || maxConns < 512 {
|
||||
glog.V(3).Infof("system net.core.somaxconn=%v (using system default)", maxConns)
|
||||
return 511
|
||||
}
|
||||
|
||||
return maxConns
|
||||
}
|
||||
|
||||
func diff(b1, b2 []byte) ([]byte, error) {
|
||||
f1, err := ioutil.TempFile("", "a")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f1.Close()
|
||||
defer os.Remove(f1.Name())
|
||||
|
||||
f2, err := ioutil.TempFile("", "b")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f2.Close()
|
||||
defer os.Remove(f2.Name())
|
||||
|
||||
f1.Write(b1)
|
||||
f2.Write(b2)
|
||||
|
||||
out, _ := exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
||||
return out, nil
|
||||
}
|
41
controllers/nginx/pkg/cmd/controller/utils_test.go
Normal file
41
controllers/nginx/pkg/cmd/controller/utils_test.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2015 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 main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDiff(t *testing.T) {
|
||||
tests := []struct {
|
||||
a []byte
|
||||
b []byte
|
||||
empty bool
|
||||
}{
|
||||
{[]byte(""), []byte(""), true},
|
||||
{[]byte("a"), []byte("a"), true},
|
||||
{[]byte("a"), []byte("b"), false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
b, err := diff(test.a, test.b)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error returned: %v", err)
|
||||
}
|
||||
if len(b) == 0 && !test.empty {
|
||||
t.Fatalf("expected empty but returned %s", b)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,8 @@ package config
|
|||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
@ -42,7 +43,7 @@ const (
|
|||
// If UseProxyProtocol is enabled defIPCIDR defines the default the IP/network address of your external load balancer
|
||||
defIPCIDR = "0.0.0.0/0"
|
||||
|
||||
gzipTypes = "application/atom+xml application/javascript aplication/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component"
|
||||
gzipTypes = "application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component"
|
||||
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
||||
// Sets the size of the buffer used for sending data.
|
||||
|
@ -69,18 +70,17 @@ const (
|
|||
|
||||
var (
|
||||
// SSLDirectory contains the mounted secrets with SSL certificates, keys and
|
||||
SSLDirectory = "/etc/nginx-ssl"
|
||||
SSLDirectory = "/etc/ingress-controller/ssl"
|
||||
)
|
||||
|
||||
// Configuration represents the content of nginx.conf file
|
||||
type Configuration struct {
|
||||
defaults.Backend
|
||||
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
||||
// Sets the maximum allowed size of the client request body
|
||||
BodySize string `structs:"body-size,omitempty"`
|
||||
|
||||
// HealthzURL defines the URL should be used in probes
|
||||
HealthzURL string
|
||||
|
||||
// EnableDynamicTLSRecords enables dynamic TLS record sizes
|
||||
// https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency
|
||||
// By default this is enabled
|
||||
|
@ -126,12 +126,6 @@ type Configuration struct {
|
|||
// accessed using HTTPS.
|
||||
HSTSMaxAge string `structs:"hsts-max-age,omitempty"`
|
||||
|
||||
// enables which HTTP codes should be passed for processing with the error_page directive
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#error_page
|
||||
// By default this is disabled
|
||||
CustomHTTPErrors []int `structs:"custom-http-errors,-"`
|
||||
|
||||
// Time during which a keep-alive client connection will stay open on the server side.
|
||||
// The zero value disables keep-alive client connections
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout
|
||||
|
@ -146,34 +140,10 @@ type Configuration struct {
|
|||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#map_hash_bucket_size
|
||||
MapHashBucketSize int `structs:"map-hash-bucket-size,omitempty"`
|
||||
|
||||
// Defines a timeout for establishing a connection with a proxied server.
|
||||
// It should be noted that this timeout cannot usually exceed 75 seconds.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_connect_timeout
|
||||
ProxyConnectTimeout int `structs:"proxy-connect-timeout,omitempty"`
|
||||
|
||||
// If UseProxyProtocol is enabled ProxyRealIPCIDR defines the default the IP/network address
|
||||
// of your external load balancer
|
||||
ProxyRealIPCIDR string `structs:"proxy-real-ip-cidr,omitempty"`
|
||||
|
||||
// Timeout in seconds for reading a response from the proxied server. The timeout is set only between
|
||||
// two successive read operations, not for the transmission of the whole response
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout
|
||||
ProxyReadTimeout int `structs:"proxy-read-timeout,omitempty"`
|
||||
|
||||
// Timeout in seconds for transmitting a request to the proxied server. The timeout is set only between
|
||||
// two successive write operations, not for the transmission of the whole request.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_send_timeout
|
||||
ProxySendTimeout int `structs:"proxy-send-timeout,omitempty"`
|
||||
|
||||
// Sets the size of the buffer used for reading the first part of the response received from the
|
||||
// proxied server. This part usually contains a small response header.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size)
|
||||
ProxyBufferSize string `structs:"proxy-buffer-size,omitempty"`
|
||||
|
||||
// Configures name servers used to resolve names of upstream servers into addresses
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver
|
||||
Resolver string `structs:"resolver,omitempty"`
|
||||
|
||||
// Maximum size of the server names hash tables used in server names, map directive’s values,
|
||||
// MIME types, names of request header strings, etcd.
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
|
@ -185,20 +155,6 @@ type Configuration struct {
|
|||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_bucket_size
|
||||
ServerNameHashBucketSize int `structs:"server-name-hash-bucket-size,omitempty"`
|
||||
|
||||
// SkipAccessLogURLs sets a list of URLs that should not appear in the NGINX access log
|
||||
// This is useful with urls like `/health` or `health-check` that make "complex" reading the logs
|
||||
// By default this list is empty
|
||||
SkipAccessLogURLs []string `structs:"skip-access-log-urls,-"`
|
||||
|
||||
// Enables or disables the redirect (301) to the HTTPS port
|
||||
SSLRedirect bool `structs:"ssl-redirect,omitempty"`
|
||||
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
||||
// Sets the size of the buffer used for sending data.
|
||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
||||
SSLBufferSize string `structs:"ssl-buffer-size,omitempty"`
|
||||
|
||||
// Enabled ciphers list to enabled. The ciphers are specified in the format understood by
|
||||
// the OpenSSL library
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers
|
||||
|
@ -230,17 +186,11 @@ type Configuration struct {
|
|||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
|
||||
SSLSessionTimeout string `structs:"ssl-session-timeout,omitempty"`
|
||||
|
||||
// Number of unsuccessful attempts to communicate with the server that should happen in the
|
||||
// duration set by the fail_timeout parameter to consider the server unavailable
|
||||
// http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream
|
||||
// Default: 0, ie use platform liveness probe
|
||||
UpstreamMaxFails int `structs:"upstream-max-fails,omitempty"`
|
||||
|
||||
// Time during which the specified number of unsuccessful attempts to communicate with
|
||||
// the server should happen to consider the server unavailable
|
||||
// http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream
|
||||
// Default: 0, ie use platform liveness probe
|
||||
UpstreamFailTimeout int `structs:"upstream-fail-timeout,omitempty"`
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
||||
// Sets the size of the buffer used for sending data.
|
||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
||||
SSLBufferSize string `structs:"ssl-buffer-size,omitempty"`
|
||||
|
||||
// Enables or disables the use of the PROXY protocol to receive client connection
|
||||
// (real IP address) information passed through proxy servers and load balancers
|
||||
|
@ -261,13 +211,9 @@ type Configuration struct {
|
|||
// Responses with the “text/html” type are always compressed if UseGzip is enabled
|
||||
GzipTypes string `structs:"gzip-types,omitempty"`
|
||||
|
||||
// WhitelistSourceRange allows limiting access to certain client addresses
|
||||
// http://nginx.org/en/docs/http/ngx_http_access_module.html
|
||||
WhitelistSourceRange []string `structs:"whitelist-source-range,omitempty"`
|
||||
|
||||
// Defines the number of worker processes. By default auto means number of available CPU cores
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_processes
|
||||
WorkerProcesses string `structs:"worker-processes,omitempty"`
|
||||
WorkerProcesses int `structs:"worker-processes,omitempty"`
|
||||
}
|
||||
|
||||
// NewDefault returns the default configuration contained
|
||||
|
@ -276,7 +222,7 @@ func NewDefault() Configuration {
|
|||
cfg := Configuration{
|
||||
BodySize: bodySize,
|
||||
EnableDynamicTLSRecords: true,
|
||||
EnableSPDY: true,
|
||||
EnableSPDY: false,
|
||||
ErrorLogLevel: errorLevel,
|
||||
HSTS: true,
|
||||
HSTSIncludeSubdomains: true,
|
||||
|
@ -285,14 +231,9 @@ func NewDefault() Configuration {
|
|||
KeepAlive: 75,
|
||||
MaxWorkerConnections: 16384,
|
||||
MapHashBucketSize: 64,
|
||||
ProxyConnectTimeout: 5,
|
||||
ProxyRealIPCIDR: defIPCIDR,
|
||||
ProxyReadTimeout: 60,
|
||||
ProxySendTimeout: 60,
|
||||
ProxyBufferSize: "4k",
|
||||
ServerNameHashMaxSize: 512,
|
||||
ServerNameHashBucketSize: 64,
|
||||
SSLRedirect: true,
|
||||
SSLBufferSize: sslBufferSize,
|
||||
SSLCiphers: sslCiphers,
|
||||
SSLProtocols: sslProtocols,
|
||||
|
@ -302,12 +243,19 @@ func NewDefault() Configuration {
|
|||
SSLSessionTimeout: sslSessionTimeout,
|
||||
UseProxyProtocol: false,
|
||||
UseGzip: true,
|
||||
WorkerProcesses: strconv.Itoa(runtime.NumCPU()),
|
||||
WorkerProcesses: runtime.NumCPU(),
|
||||
VtsStatusZoneSize: "10m",
|
||||
UseHTTP2: true,
|
||||
CustomHTTPErrors: make([]int, 0),
|
||||
WhitelistSourceRange: make([]string, 0),
|
||||
SkipAccessLogURLs: make([]string, 0),
|
||||
Backend: defaults.Backend{
|
||||
ProxyConnectTimeout: 5,
|
||||
ProxyReadTimeout: 60,
|
||||
ProxySendTimeout: 60,
|
||||
ProxyBufferSize: "4k",
|
||||
SSLRedirect: true,
|
||||
CustomHTTPErrors: []int{},
|
||||
WhitelistSourceRange: []string{},
|
||||
SkipAccessLogURLs: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
if glog.V(5) {
|
122
controllers/nginx/pkg/template/configmap.go
Normal file
122
controllers/nginx/pkg/template/configmap.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
Copyright 2015 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 template
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
go_camelcase "github.com/segmentio/go-camelcase"
|
||||
|
||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
const (
|
||||
customHTTPErrors = "custom-http-errors"
|
||||
skipAccessLogUrls = "skip-access-log-urls"
|
||||
whitelistSourceRange = "whitelist-source-range"
|
||||
)
|
||||
|
||||
// StandarizeKeyNames ...
|
||||
func StandarizeKeyNames(data interface{}) map[string]interface{} {
|
||||
return fixKeyNames(structs.Map(data))
|
||||
}
|
||||
|
||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||
func ReadConfig(conf *api.ConfigMap) config.Configuration {
|
||||
if len(conf.Data) == 0 {
|
||||
return config.NewDefault()
|
||||
}
|
||||
|
||||
var errors []int
|
||||
var skipUrls []string
|
||||
var whitelist []string
|
||||
|
||||
if val, ok := conf.Data[customHTTPErrors]; ok {
|
||||
delete(conf.Data, customHTTPErrors)
|
||||
for _, i := range strings.Split(val, ",") {
|
||||
j, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
glog.Warningf("%v is not a valid http code: %v", i, err)
|
||||
} else {
|
||||
errors = append(errors, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
if val, ok := conf.Data[skipAccessLogUrls]; ok {
|
||||
delete(conf.Data, skipAccessLogUrls)
|
||||
skipUrls = strings.Split(val, ",")
|
||||
}
|
||||
if val, ok := conf.Data[whitelistSourceRange]; ok {
|
||||
delete(conf.Data, whitelistSourceRange)
|
||||
whitelist = append(whitelist, strings.Split(val, ",")...)
|
||||
}
|
||||
|
||||
to := config.Configuration{}
|
||||
to.Backend = defaults.Backend{
|
||||
CustomHTTPErrors: filterErrors(errors),
|
||||
SkipAccessLogURLs: skipUrls,
|
||||
WhitelistSourceRange: whitelist,
|
||||
}
|
||||
def := config.NewDefault()
|
||||
if err := mergo.Merge(&to, def); err != nil {
|
||||
glog.Warningf("unexpected error merging defaults: %v", err)
|
||||
}
|
||||
|
||||
metadata := &mapstructure.Metadata{}
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
TagName: "structs",
|
||||
Result: &to,
|
||||
WeaklyTypedInput: true,
|
||||
Metadata: metadata,
|
||||
})
|
||||
|
||||
err = decoder.Decode(conf.Data)
|
||||
if err != nil {
|
||||
glog.Infof("%v", err)
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
func filterErrors(codes []int) []int {
|
||||
var fa []int
|
||||
for _, code := range codes {
|
||||
if code > 299 && code < 600 {
|
||||
fa = append(fa, code)
|
||||
} else {
|
||||
glog.Warningf("error code %v is not valid for custom error pages", code)
|
||||
}
|
||||
}
|
||||
|
||||
return fa
|
||||
}
|
||||
|
||||
func fixKeyNames(data map[string]interface{}) map[string]interface{} {
|
||||
fixed := make(map[string]interface{})
|
||||
for k, v := range data {
|
||||
fixed[go_camelcase.Camelcase(k)] = v
|
||||
}
|
||||
return fixed
|
||||
}
|
83
controllers/nginx/pkg/template/configmap_test.go
Normal file
83
controllers/nginx/pkg/template/configmap_test.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright 2015 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 template
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||
)
|
||||
|
||||
func TestStandarizeKeyNames(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilterErrors(t *testing.T) {
|
||||
e := filterErrors([]int{200, 300, 345, 500, 555, 999})
|
||||
if len(e) != 4 {
|
||||
t.Errorf("expected 4 elements but %v returned", len(e))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixKeyNames(t *testing.T) {
|
||||
d := map[string]interface{}{
|
||||
"one": "one",
|
||||
"one-example": "oneExample",
|
||||
"aMore-complex_example": "aMoreComplexExample",
|
||||
"a": "a",
|
||||
}
|
||||
|
||||
fixed := fixKeyNames(d)
|
||||
for k, v := range fixed {
|
||||
if k != v {
|
||||
t.Errorf("expected %v but retuned %v", v, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testStruct struct {
|
||||
ProxyReadTimeout int `structs:"proxy-read-timeout,omitempty"`
|
||||
ProxySendTimeout int `structs:"proxy-send-timeout,omitempty"`
|
||||
CustomHTTPErrors []int `structs:"custom-http-errors"`
|
||||
SkipAccessLogURLs []string `structs:"skip-access-log-urls,-"`
|
||||
NoTag string
|
||||
}
|
||||
|
||||
var decodedData = &testStruct{
|
||||
1,
|
||||
2,
|
||||
[]int{300, 400},
|
||||
[]string{"/log"},
|
||||
"",
|
||||
}
|
||||
|
||||
func TestMergeConfigMapToStruct(t *testing.T) {
|
||||
/*conf := &api.ConfigMap{
|
||||
Data: map[string]string{
|
||||
"custom-http-errors": "300,400",
|
||||
"proxy-read-timeout": "1",
|
||||
"proxy-send-timeout": "2",
|
||||
"skip-access-log-urls": "/log",
|
||||
},
|
||||
}*/
|
||||
def := config.NewDefault()
|
||||
def.CustomHTTPErrors = []int{300, 400}
|
||||
def.SkipAccessLogURLs = []string{"/log"}
|
||||
//to := ReadConfig(conf)
|
||||
//if !reflect.DeepEqual(def, to) {
|
||||
// t.Errorf("expected %v but retuned %v", def, to)
|
||||
//}
|
||||
}
|
|
@ -21,51 +21,28 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"os/exec"
|
||||
"strings"
|
||||
text_template "text/template"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/ingress"
|
||||
"k8s.io/kubernetes/pkg/util/sysctl"
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/watch"
|
||||
)
|
||||
|
||||
const (
|
||||
slash = "/"
|
||||
)
|
||||
|
||||
var (
|
||||
camelRegexp = regexp.MustCompile("[0-9A-Za-z]+")
|
||||
|
||||
funcMap = text_template.FuncMap{
|
||||
"empty": func(input interface{}) bool {
|
||||
check, ok := input.(string)
|
||||
if ok {
|
||||
return len(check) == 0
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
"buildLocation": buildLocation,
|
||||
"buildAuthLocation": buildAuthLocation,
|
||||
"buildProxyPass": buildProxyPass,
|
||||
"buildRateLimitZones": buildRateLimitZones,
|
||||
"buildRateLimit": buildRateLimit,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"toUpper": strings.ToUpper,
|
||||
"toLower": strings.ToLower,
|
||||
}
|
||||
defBufferSize = 65535
|
||||
)
|
||||
|
||||
// Template ...
|
||||
type Template struct {
|
||||
tmpl *text_template.Template
|
||||
fw fileWatcher
|
||||
fw watch.FileWatcher
|
||||
s int
|
||||
tmplBuf *bytes.Buffer
|
||||
outCmdBuf *bytes.Buffer
|
||||
}
|
||||
|
||||
//NewTemplate returns a new Template instance or an
|
||||
|
@ -75,7 +52,7 @@ func NewTemplate(file string, onChange func()) (*Template, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fw, err := newFileWatcher(file, onChange)
|
||||
fw, err := watch.NewFileWatcher(file, onChange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -83,58 +60,24 @@ func NewTemplate(file string, onChange func()) (*Template, error) {
|
|||
return &Template{
|
||||
tmpl: tmpl,
|
||||
fw: fw,
|
||||
s: defBufferSize,
|
||||
tmplBuf: bytes.NewBuffer(make([]byte, 0, defBufferSize)),
|
||||
outCmdBuf: bytes.NewBuffer(make([]byte, 0, defBufferSize)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close removes the file watcher
|
||||
func (t *Template) Close() {
|
||||
t.fw.close()
|
||||
t.fw.Close()
|
||||
}
|
||||
|
||||
// Write populates a buffer using a template with NGINX configuration
|
||||
// and the servers and upstreams created by Ingress rules
|
||||
func (t *Template) Write(
|
||||
cfg config.Configuration,
|
||||
ingressCfg ingress.Configuration,
|
||||
func (t *Template) Write(conf map[string]interface{},
|
||||
isValidTemplate func([]byte) error) ([]byte, error) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// NGINX cannot resize the has tables used to store server names.
|
||||
// For this reason we check if the defined size defined is correct
|
||||
// for the FQDN defined in the ingress rules adjusting the value
|
||||
// if is required.
|
||||
// https://trac.nginx.org/nginx/ticket/352
|
||||
// https://trac.nginx.org/nginx/ticket/631
|
||||
nameHashBucketSize := nextPowerOf2(longestName)
|
||||
if nameHashBucketSize > cfg.ServerNameHashBucketSize {
|
||||
glog.V(3).Infof("adjusting ServerNameHashBucketSize variable from %v to %v",
|
||||
cfg.ServerNameHashBucketSize, nameHashBucketSize)
|
||||
cfg.ServerNameHashBucketSize = nameHashBucketSize
|
||||
}
|
||||
serverNameHashMaxSize := nextPowerOf2(serverNames)
|
||||
if serverNameHashMaxSize > cfg.ServerNameHashMaxSize {
|
||||
glog.V(3).Infof("adjusting ServerNameHashMaxSize variable from %v to %v",
|
||||
cfg.ServerNameHashMaxSize, serverNameHashMaxSize)
|
||||
cfg.ServerNameHashMaxSize = serverNameHashMaxSize
|
||||
}
|
||||
|
||||
conf := make(map[string]interface{})
|
||||
conf["backlogSize"] = sysctlSomaxconn()
|
||||
conf["upstreams"] = ingressCfg.Upstreams
|
||||
conf["servers"] = ingressCfg.Servers
|
||||
conf["tcpUpstreams"] = ingressCfg.TCPUpstreams
|
||||
conf["udpUpstreams"] = ingressCfg.UDPUpstreams
|
||||
conf["defResolver"] = cfg.Resolver
|
||||
conf["sslDHParam"] = cfg.SSLDHParam
|
||||
conf["customErrors"] = len(cfg.CustomHTTPErrors) > 0
|
||||
conf["cfg"] = fixKeyNames(structs.Map(cfg))
|
||||
defer t.tmplBuf.Reset()
|
||||
defer t.outCmdBuf.Reset()
|
||||
|
||||
if glog.V(3) {
|
||||
b, err := json.Marshal(conf)
|
||||
|
@ -144,39 +87,65 @@ func (t *Template) Write(
|
|||
glog.Infof("NGINX configuration: %v", string(b))
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
err := t.tmpl.Execute(buffer, conf)
|
||||
if err != nil {
|
||||
glog.V(3).Infof("%v", string(buffer.Bytes()))
|
||||
return nil, err
|
||||
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()))
|
||||
}
|
||||
|
||||
err = isValidTemplate(buffer.Bytes())
|
||||
// squeezes multiple adjacent empty lines to be single
|
||||
// spaced this is to avoid the use of regular expressions
|
||||
cmd := exec.Command("/ingress-controller/clean-nginx-conf.sh")
|
||||
cmd.Stdin = t.tmplBuf
|
||||
cmd.Stdout = t.outCmdBuf
|
||||
if err := cmd.Run(); err != nil {
|
||||
glog.Warningf("unexpected error cleaning template: %v", err)
|
||||
return t.tmplBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
content := t.outCmdBuf.Bytes()
|
||||
err = isValidTemplate(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func fixKeyNames(data map[string]interface{}) map[string]interface{} {
|
||||
fixed := make(map[string]interface{})
|
||||
for k, v := range data {
|
||||
fixed[toCamelCase(k)] = v
|
||||
var (
|
||||
funcMap = text_template.FuncMap{
|
||||
"empty": func(input interface{}) bool {
|
||||
check, ok := input.(string)
|
||||
if ok {
|
||||
return len(check) == 0
|
||||
}
|
||||
return true
|
||||
},
|
||||
"buildLocation": buildLocation,
|
||||
"buildAuthLocation": buildAuthLocation,
|
||||
"buildProxyPass": buildProxyPass,
|
||||
"buildRateLimitZones": buildRateLimitZones,
|
||||
"buildRateLimit": buildRateLimit,
|
||||
"getSSPassthroughUpstream": getSSPassthroughUpstream,
|
||||
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"toUpper": strings.ToUpper,
|
||||
"toLower": strings.ToLower,
|
||||
}
|
||||
)
|
||||
|
||||
func getSSPassthroughUpstream(input interface{}) string {
|
||||
s, ok := input.(*ingress.Server)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fixed
|
||||
}
|
||||
|
||||
func toCamelCase(src string) string {
|
||||
byteSrc := []byte(src)
|
||||
chunks := camelRegexp.FindAll(byteSrc, -1)
|
||||
for idx, val := range chunks {
|
||||
if idx > 0 {
|
||||
chunks[idx] = bytes.Title(val)
|
||||
}
|
||||
}
|
||||
return string(bytes.Join(chunks, nil))
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// buildLocation produces the location string, if the ingress has redirects
|
||||
|
@ -201,7 +170,7 @@ func buildAuthLocation(input interface{}) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
if location.ExternalAuthURL.URL == "" {
|
||||
if location.ExternalAuth.URL == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
@ -329,30 +298,3 @@ func buildRateLimit(input interface{}) []string {
|
|||
|
||||
return limits
|
||||
}
|
||||
|
||||
// sysctlSomaxconn returns the value of net.core.somaxconn, i.e.
|
||||
// maximum number of connections that can be queued for acceptance
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
|
||||
func sysctlSomaxconn() int {
|
||||
maxConns, err := sysctl.New().GetSysctl("net/core/somaxconn")
|
||||
if err != nil || maxConns < 512 {
|
||||
glog.Warningf("system net.core.somaxconn=%v. Using NGINX default (511)", maxConns)
|
||||
return 511
|
||||
}
|
||||
|
||||
return maxConns
|
||||
}
|
||||
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
// https://play.golang.org/p/TVSyCcdxUh
|
||||
func nextPowerOf2(v int) int {
|
||||
v--
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
v++
|
||||
|
||||
return v
|
||||
}
|
|
@ -20,8 +20,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/ingress"
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||
)
|
||||
|
||||
var (
|
|
@ -14,8 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/api
|
||||
package version
|
||||
|
||||
// Package v1 is the v1 version of the API.
|
||||
package v1
|
||||
var (
|
||||
// RELEASE returns the release version
|
||||
RELEASE = "UNKNOWN"
|
||||
// REPO returns the git repository URL
|
||||
REPO = "UNKNOWN"
|
||||
// COMMIT returns the short sha from git
|
||||
COMMIT = "UNKNOWN"
|
||||
)
|
|
@ -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.9
|
||||
FROM gcr.io/google_containers/nginx-slim:0.10
|
||||
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
|
||||
diffutils \
|
||||
|
@ -21,13 +21,6 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
|
|||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& make-ssl-cert generate-default-snakeoil --force-overwrite
|
||||
|
||||
COPY nginx-ingress-controller /
|
||||
COPY nginx.tmpl /etc/nginx/template/nginx.tmpl
|
||||
COPY nginx.tmpl /etc/nginx/nginx.tmpl
|
||||
COPY default.conf /etc/nginx/nginx.conf
|
||||
|
||||
COPY lua /etc/nginx/lua/
|
||||
|
||||
WORKDIR /
|
||||
COPY . /
|
||||
|
||||
CMD ["/nginx-ingress-controller"]
|
|
@ -1,21 +1,21 @@
|
|||
# lua-resty-http
|
||||
|
||||
Lua HTTP client cosocket driver for [OpenResty](http://openresty.org/) / [ngx_lua](https://github.com/chaoslawful/lua-nginx-module).
|
||||
Lua HTTP client cosocket driver for [OpenResty](http://openresty.org/) / [ngx_lua](https://github.com/openresty/lua-nginx-module).
|
||||
|
||||
# Status
|
||||
|
||||
Ready for testing. Probably production ready in most cases, though not yet proven in the wild. Please check the issues list and let me know if you have any problems / questions.
|
||||
Production ready.
|
||||
|
||||
# Features
|
||||
|
||||
* HTTP 1.0 and 1.1
|
||||
* Streaming interface to reading bodies using coroutines, for predictable memory usage in Lua land.
|
||||
* Alternative simple interface for singleshot requests without manual connection step.
|
||||
* Headers treated case insensitively.
|
||||
* Chunked transfer encoding.
|
||||
* Keepalive.
|
||||
* Pipelining.
|
||||
* Trailers.
|
||||
* SSL
|
||||
* Streaming interface to the response body, for predictable memory usage
|
||||
* Alternative simple interface for singleshot requests without manual connection step
|
||||
* Chunked and non-chunked transfer encodings
|
||||
* Keepalive
|
||||
* Pipelining
|
||||
* Trailers
|
||||
|
||||
|
||||
# API
|
||||
|
@ -222,6 +222,7 @@ The `params` table accepts the following fields:
|
|||
When the request is successful, `res` will contain the following fields:
|
||||
|
||||
* `status` The status code.
|
||||
* `reason` The status reason phrase.
|
||||
* `headers` A table of headers. Multiple headers with the same field name will be presented as a table of values.
|
||||
* `has_body` A boolean flag indicating if there is a body to be read.
|
||||
* `body_reader` An iterator function for reading the body in a streaming fashion.
|
||||
|
@ -410,7 +411,7 @@ Originally started life based on https://github.com/bakins/lua-resty-http-simple
|
|||
|
||||
This module is licensed under the 2-clause BSD license.
|
||||
|
||||
Copyright (c) 2013, James Hurst <james@pintsized.co.uk>
|
||||
Copyright (c) 2013-2016, James Hurst <james@pintsized.co.uk>
|
||||
|
||||
All rights reserved.
|
||||
|
|
@ -67,7 +67,7 @@ end
|
|||
|
||||
|
||||
local _M = {
|
||||
_VERSION = '0.07',
|
||||
_VERSION = '0.09',
|
||||
}
|
||||
_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version
|
||||
|
||||
|
@ -111,6 +111,8 @@ function _M.ssl_handshake(self, ...)
|
|||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
self.ssl = true
|
||||
|
||||
return sock:sslhandshake(...)
|
||||
end
|
||||
|
||||
|
@ -122,6 +124,13 @@ function _M.connect(self, ...)
|
|||
end
|
||||
|
||||
self.host = select(1, ...)
|
||||
self.port = select(2, ...)
|
||||
|
||||
-- If port is not a number, this is likely a unix domain socket connection.
|
||||
if type(self.port) ~= "number" then
|
||||
self.port = nil
|
||||
end
|
||||
|
||||
self.keepalive = true
|
||||
|
||||
return sock:connect(...)
|
||||
|
@ -179,17 +188,19 @@ end
|
|||
|
||||
|
||||
function _M.parse_uri(self, uri)
|
||||
local m, err = ngx_re_match(uri, [[^(http[s]*)://([^:/]+)(?::(\d+))?(.*)]],
|
||||
local m, err = ngx_re_match(uri, [[^(http[s]?)://([^:/]+)(?::(\d+))?(.*)]],
|
||||
"jo")
|
||||
|
||||
if not m then
|
||||
if err then
|
||||
return nil, "failed to match the uri: " .. err
|
||||
return nil, "failed to match the uri: " .. uri .. ", " .. err
|
||||
end
|
||||
|
||||
return nil, "bad uri"
|
||||
return nil, "bad uri: " .. uri
|
||||
else
|
||||
if m[3] then
|
||||
m[3] = tonumber(m[3])
|
||||
else
|
||||
if not m[3] then
|
||||
if m[1] == "https" then
|
||||
m[3] = 443
|
||||
else
|
||||
|
@ -250,10 +261,10 @@ end
|
|||
local function _receive_status(sock)
|
||||
local line, err = sock:receive("*l")
|
||||
if not line then
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
end
|
||||
|
||||
return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8))
|
||||
return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)
|
||||
end
|
||||
|
||||
|
||||
|
@ -487,16 +498,16 @@ end
|
|||
|
||||
|
||||
local function _handle_continue(sock, body)
|
||||
local status, version, err = _receive_status(sock)
|
||||
local status, version, reason, err = _receive_status(sock)
|
||||
if not status then
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
end
|
||||
|
||||
-- Only send body if we receive a 100 Continue
|
||||
if status == 100 then
|
||||
local ok, err = sock:receive("*l") -- Read carriage return
|
||||
if not ok then
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
end
|
||||
_send_body(sock, body)
|
||||
end
|
||||
|
@ -526,8 +537,23 @@ function _M.send_request(self, params)
|
|||
headers["Content-Length"] = #body
|
||||
end
|
||||
if not headers["Host"] then
|
||||
if (str_sub(self.host, 1, 5) == "unix:") then
|
||||
return nil, "Unable to generate a useful Host header for a unix domain socket. Please provide one."
|
||||
end
|
||||
-- If we have a port (i.e. not connected to a unix domain socket), and this
|
||||
-- port is non-standard, append it to the Host heaer.
|
||||
if self.port then
|
||||
if self.ssl and self.port ~= 443 then
|
||||
headers["Host"] = self.host .. ":" .. self.port
|
||||
elseif not self.ssl and self.port ~= 80 then
|
||||
headers["Host"] = self.host .. ":" .. self.port
|
||||
else
|
||||
headers["Host"] = self.host
|
||||
end
|
||||
else
|
||||
headers["Host"] = self.host
|
||||
end
|
||||
end
|
||||
if not headers["User-Agent"] then
|
||||
headers["User-Agent"] = _M._USER_AGENT
|
||||
end
|
||||
|
@ -562,7 +588,7 @@ end
|
|||
function _M.read_response(self, params)
|
||||
local sock = self.sock
|
||||
|
||||
local status, version, err
|
||||
local status, version, reason, err
|
||||
|
||||
-- If we expect: continue, we need to handle this, sending the body if allowed.
|
||||
-- If we don't get 100 back, then status is the actual status.
|
||||
|
@ -577,7 +603,7 @@ function _M.read_response(self, params)
|
|||
|
||||
-- Just read the status as normal.
|
||||
if not status then
|
||||
status, version, err = _receive_status(sock)
|
||||
status, version, reason, err = _receive_status(sock)
|
||||
if not status then
|
||||
return nil, err
|
||||
end
|
||||
|
@ -589,13 +615,18 @@ function _M.read_response(self, params)
|
|||
return nil, err
|
||||
end
|
||||
|
||||
-- Determine if we should keepalive or not.
|
||||
-- keepalive is true by default. Determine if this is correct or not.
|
||||
local ok, connection = pcall(str_lower, res_headers["Connection"])
|
||||
if ok then
|
||||
if (version == 1.1 and connection == "close") or
|
||||
(version == 1.0 and connection ~= "keep-alive") then
|
||||
self.keepalive = false
|
||||
end
|
||||
else
|
||||
-- no connection header
|
||||
if version == 1.0 then
|
||||
self.keepalive = false
|
||||
end
|
||||
end
|
||||
|
||||
local body_reader = _no_body_reader
|
||||
|
@ -627,6 +658,7 @@ function _M.read_response(self, params)
|
|||
else
|
||||
return {
|
||||
status = status,
|
||||
reason = reason,
|
||||
headers = res_headers,
|
||||
has_body = has_body,
|
||||
body_reader = body_reader,
|
22
controllers/nginx/rootfs/etc/nginx/lua/vendor/lua-resty-http/lua-resty-http-0.09-0.rockspec
vendored
Normal file
22
controllers/nginx/rootfs/etc/nginx/lua/vendor/lua-resty-http/lua-resty-http-0.09-0.rockspec
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
package = "lua-resty-http"
|
||||
version = "0.09-0"
|
||||
source = {
|
||||
url = "git://github.com/pintsized/lua-resty-http",
|
||||
tag = "v0.09"
|
||||
}
|
||||
description = {
|
||||
summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.",
|
||||
homepage = "https://github.com/pintsized/lua-resty-http",
|
||||
license = "2-clause BSD",
|
||||
maintainer = "James Hurst <james@pintsized.co.uk>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.http"] = "lib/resty/http.lua",
|
||||
["resty.http_headers"] = "lib/resty/http_headers.lua"
|
||||
}
|
||||
}
|
|
@ -83,7 +83,7 @@ OK
|
|||
[warn]
|
||||
|
||||
|
||||
=== TEST 3: Status code
|
||||
=== TEST 3: Status code and reason phrase
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
|
@ -97,6 +97,7 @@ OK
|
|||
}
|
||||
|
||||
ngx.status = res.status
|
||||
ngx.say(res.reason)
|
||||
ngx.print(res:read_body())
|
||||
|
||||
httpc:close()
|
||||
|
@ -111,6 +112,7 @@ OK
|
|||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
Not Found
|
||||
OK
|
||||
--- error_code: 404
|
||||
--- no_error_log
|
|
@ -180,3 +180,61 @@ connection must be closed
|
|||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 5: Generic interface, HTTP 1.0, no connection header. Test we don't try to keepalive, but also that subsequent connections can keepalive.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", 12345)
|
||||
|
||||
local res, err = httpc:request{
|
||||
version = 1.0,
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
ngx.print(body)
|
||||
|
||||
ngx.say(res.headers["Connection"])
|
||||
|
||||
local r, e = httpc:set_keepalive()
|
||||
ngx.say(r)
|
||||
ngx.say(e)
|
||||
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
ngx.say(httpc:get_reused_times())
|
||||
|
||||
httpc:set_keepalive()
|
||||
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
ngx.say(httpc:get_reused_times())
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- tcp_listen: 12345
|
||||
--- tcp_reply
|
||||
HTTP/1.0 200 OK
|
||||
Date: Fri, 08 Aug 2016 08:12:31 GMT
|
||||
Server: OpenResty
|
||||
|
||||
OK
|
||||
--- response_body
|
||||
OK
|
||||
nil
|
||||
2
|
||||
connection must be closed
|
||||
0
|
||||
1
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
161
controllers/nginx/rootfs/etc/nginx/lua/vendor/lua-resty-http/t/14-host-header.t
vendored
Normal file
161
controllers/nginx/rootfs/etc/nginx/lua/vendor/lua-resty-http/t/14-host-header.t
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 3);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
resolver 8.8.8.8;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
$ENV{TEST_NGINX_PWD} ||= $pwd;
|
||||
|
||||
sub read_file {
|
||||
my $infile = shift;
|
||||
open my $in, $infile
|
||||
or die "cannot open $infile for reading: $!";
|
||||
my $cert = do { local $/; <$in> };
|
||||
close $in;
|
||||
$cert;
|
||||
}
|
||||
|
||||
our $TestCertificate = read_file("t/cert/test.crt");
|
||||
our $TestCertificateKey = read_file("t/cert/test.key");
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Default HTTP port is not added to Host header
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /lua {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local res, err = httpc:request_uri("http://www.google.com")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /lua
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- error_log
|
||||
Host: www.google.com
|
||||
|
||||
|
||||
=== TEST 2: Default HTTPS port is not added to Host header
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /lua {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local res, err = httpc:request_uri("https://www.google.com:443", { ssl_verify = false })
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /lua
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- error_log
|
||||
Host: www.google.com
|
||||
|
||||
|
||||
=== TEST 3: Non-default HTTP port is added to Host header
|
||||
--- http_config
|
||||
lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
resolver 8.8.8.8;
|
||||
server {
|
||||
listen *:8080;
|
||||
}
|
||||
--- config
|
||||
location /lua {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local res, err = httpc:request_uri("http://127.0.0.1:8080")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /lua
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- error_log
|
||||
Host: 127.0.0.1:8080
|
||||
|
||||
|
||||
=== TEST 4: Non-default HTTPS port is added to Host header
|
||||
--- http_config
|
||||
lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
resolver 8.8.8.8;
|
||||
server {
|
||||
listen *:8080;
|
||||
listen *:8081 ssl;
|
||||
ssl_certificate ../html/test.crt;
|
||||
ssl_certificate_key ../html/test.key;
|
||||
}
|
||||
--- config
|
||||
location /lua {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local res, err = httpc:request_uri("https://127.0.0.1:8081", { ssl_verify = false })
|
||||
';
|
||||
}
|
||||
--- user_files eval
|
||||
">>> test.key
|
||||
$::TestCertificateKey
|
||||
>>> test.crt
|
||||
$::TestCertificate"
|
||||
--- request
|
||||
GET /lua
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- error_log
|
||||
Host: 127.0.0.1:8081
|
||||
|
||||
|
||||
=== TEST 5: No host header on a unix domain socket returns a useful error.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /a {
|
||||
content_by_lua_block {
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local res, err = httpc:connect("unix:test.sock")
|
||||
if not res then
|
||||
ngx.log(ngx.ERR, err)
|
||||
end
|
||||
|
||||
local res, err = httpc:request({ path = "/" })
|
||||
if not res then
|
||||
ngx.say(err)
|
||||
else
|
||||
ngx.say(res:read_body())
|
||||
end
|
||||
}
|
||||
}
|
||||
--- tcp_listen: test.sock
|
||||
--- tcp_reply: OK
|
||||
--- request
|
||||
GET /a
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- response_body
|
||||
Unable to generate a useful Host header for a unix domain socket. Please provide one.
|
24
controllers/nginx/rootfs/etc/nginx/lua/vendor/lua-resty-http/t/cert/test.crt
vendored
Normal file
24
controllers/nginx/rootfs/etc/nginx/lua/vendor/lua-resty-http/t/cert/test.crt
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIID8DCCAtigAwIBAgIJALL9eJPZ6neGMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
|
||||
BAYTAkdCMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
|
||||
ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwR0ZXN0MB4XDTE1MTAyMTE2MjQ1
|
||||
NloXDTE1MTEyMDE2MjQ1NlowWDELMAkGA1UEBhMCR0IxDTALBgNVBAgTBFRlc3Qx
|
||||
DTALBgNVBAcTBFRlc3QxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxDTAL
|
||||
BgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDz/AoE
|
||||
c+TPdm+Aqcchq8fLNWksFQZqbsCBGnq8rUG1b6MsVlAOkDUQGRlNPs9v0/+pzgX7
|
||||
IYXPCFcV7YONNsTUfvBYTq43mfOycmAdb3SX6kBygxdhYsDRZR+vCAIkjoRmRB20
|
||||
meh1motqM58spq3IcT8VADTRJl1OI48VTnxmXdCtmkOymU948DcauMoxm03eL/hU
|
||||
6eniNEujbnbB305noNG0W5c3h6iz9CvqUAD1kwyjick+f1atB2YYn1bymA+db6YN
|
||||
3iTo0v2raWmIc7D+qqpkNaCRxgMb2HN6X3/SfkijtNJidjqHMbs2ftlKJ5/lODPZ
|
||||
rCPQOcYK6TT8MIZ1AgMBAAGjgbwwgbkwHQYDVR0OBBYEFFUC1GrAhUp7IvJH5iyf
|
||||
+fJQliEIMIGJBgNVHSMEgYEwf4AUVQLUasCFSnsi8kfmLJ/58lCWIQihXKRaMFgx
|
||||
CzAJBgNVBAYTAkdCMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYD
|
||||
VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwR0ZXN0ggkAsv14k9nq
|
||||
d4YwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAtaUQOr3Qn87KXmmP
|
||||
GbSvCLSl+bScE09VYZsYaB6iq0pGN9y+Vh4/HjBUUsFexopw1dY25MEEJXEVi1xV
|
||||
2krLYAsfKCM6c1QBVmdqfVuxUvxpXwr+CNRNAlzz6PhjkeY/Ds/j4sg7EqN8hMmT
|
||||
gu8GuogX7+ZCgrzRSMMclWej+W8D1xSIuCC+rqv4w9SZdtVb3XGpCyizpTNsQAuV
|
||||
ACXvq9KXkEEj+XNvKrNdWd4zG715RdMnVm+WM53d9PLp63P+4/kwhwHULYhXygQ3
|
||||
DzzVPaojBBdw3VaHbbPHnv73FtAzOb7ky6zJ01DlmEPxEahCFpklMkY9T2uCdpj9
|
||||
oOzaNA==
|
||||
-----END CERTIFICATE-----
|
27
controllers/nginx/rootfs/etc/nginx/lua/vendor/lua-resty-http/t/cert/test.key
vendored
Normal file
27
controllers/nginx/rootfs/etc/nginx/lua/vendor/lua-resty-http/t/cert/test.key
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA8/wKBHPkz3ZvgKnHIavHyzVpLBUGam7AgRp6vK1BtW+jLFZQ
|
||||
DpA1EBkZTT7Pb9P/qc4F+yGFzwhXFe2DjTbE1H7wWE6uN5nzsnJgHW90l+pAcoMX
|
||||
YWLA0WUfrwgCJI6EZkQdtJnodZqLajOfLKatyHE/FQA00SZdTiOPFU58Zl3QrZpD
|
||||
splPePA3GrjKMZtN3i/4VOnp4jRLo252wd9OZ6DRtFuXN4eos/Qr6lAA9ZMMo4nJ
|
||||
Pn9WrQdmGJ9W8pgPnW+mDd4k6NL9q2lpiHOw/qqqZDWgkcYDG9hzel9/0n5Io7TS
|
||||
YnY6hzG7Nn7ZSief5Tgz2awj0DnGCuk0/DCGdQIDAQABAoIBAGjKc7L94+SHRdTJ
|
||||
FtILacCJrCZW0W6dKulIajbnYzV+QWMlnzTiEyha31ciBw5My54u8rqt5z7Ioj60
|
||||
yK+6OkfaTXhgMsuGv/iAz29VE4q7/fow+7XEKHTHLhiLJAB3hb42u1t6TzFTs1Vl
|
||||
3pPa8wEIQsPOVuENzT1mYGoST7PW+LBIMr9ScMnRHfC0MNdV/ntQiXideOAd5PkA
|
||||
4O7fNgYZ8CTAZ8rOLYTMFF76/c/jLiqfeghqbIhqMykk36kd7Lud//FRykVsn1aJ
|
||||
REUva/SjVEth5kITot1hpMC4SIElWpha2YxiiZFoSXSaUbtHpymiUGV01cYtMWk0
|
||||
MZ5HN3ECgYEA/74U8DpwPxd4up9syKyNqOqrCrYnhEEC/tdU/W5wECi4y5kppjdd
|
||||
88lZzICVPzk2fezYXlCO9HiSHU1UfcEsY3u16qNCvylK7Qz1OqXV/Ncj59891Q5Z
|
||||
K0UBcbnrv+YD6muZuhlHEbyDPqYO091G9Gf/BbL5JIBDzg1qFO9Dh9cCgYEA9Drt
|
||||
O9PJ5Sjz3mXQVtVHpwyhOVnd7CUv8a1zkUQCK5uQeaiF5kal1FIo7pLOr3KAvG0C
|
||||
pXbm/TobwlfAfcERQN88aPN8Z/l1CB0oKV6ipBMD2/XLzDRtx8lpTeh/BB8jIhrz
|
||||
+FDJY54HCzLfW0P5kT+Cyw51ofjziPnFdO/Z6pMCgYEAon17gEchGnUnWCwDSl2Y
|
||||
hELV+jBSW02TQag/b+bDfQDiqTnfpKR5JXRBghYQveL0JH5f200EB4C0FboUfPJH
|
||||
6c2ogDTLK/poiMU66tCDbeqj/adx+fTr4votOL0QdRUIV+GWAxAcf8BvA1cvBJ4L
|
||||
fy60ckKM2gxFCJ6tUC/VkHECgYBoMDNAUItSnXPbrmeAg5/7naGxy6qmsP6RBUPF
|
||||
9tNOMyEhJUlqAT2BJEOd8zcFFb3hpEd6uwyzfnSVJcZSX2iy2gj1ZNnvqTXJ7lZR
|
||||
v7N2dz4wOd1lEgC7OCsaN1LoOThNtl3Z0uz2+FVc66jpUEhJNGThpxt7q66JArS/
|
||||
vAqkzQKBgFkzqA6QpnH5KhOCoZcuLQ4MtvnNHOx1xSm2B0gKDVJzGkHexTmOJvwM
|
||||
ZhHXRl9txS4icejS+AGUXNBzCWEusfhDaZpZqS6zt6UxEjMsLj/Te7z++2KQn4t/
|
||||
aI77jClydW1pJvICtqm5v+sukVZvQTTJza9ujta6fj7u2s671np9
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -1,14 +1,10 @@
|
|||
{{ $cfg := .cfg }}
|
||||
{{ $cfg := .cfg }}{{ $healthzURL := .healthzURL }}
|
||||
daemon off;
|
||||
|
||||
worker_processes {{ $cfg.workerProcesses }};
|
||||
|
||||
pid /run/nginx.pid;
|
||||
|
||||
worker_rlimit_nofile 131072;
|
||||
|
||||
pcre_jit on;
|
||||
|
||||
events {
|
||||
multi_accept on;
|
||||
worker_connections {{ $cfg.maxWorkerConnections }};
|
||||
|
@ -17,13 +13,13 @@ events {
|
|||
|
||||
http {
|
||||
{{/* 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 }};
|
||||
real_ip_header proxy_protocol;
|
||||
{{ else }}
|
||||
real_ip_header X-Forwarded-For;
|
||||
set_real_ip_from 0.0.0.0/0;
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
|
||||
real_ip_recursive on;
|
||||
|
||||
|
@ -34,10 +30,10 @@ http {
|
|||
geoip_city /etc/nginx/GeoLiteCity.dat;
|
||||
geoip_proxy_recursive on;
|
||||
|
||||
{{- if $cfg.enableVtsStatus }}
|
||||
{{ 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 -}}
|
||||
{{ end }}
|
||||
|
||||
# lua section to return proper error codes when custom pages are used
|
||||
lua_package_path '.?.lua;./etc/nginx/lua/?.lua;/etc/nginx/lua/vendor/lua-resty-http/lib/?.lua;';
|
||||
|
@ -63,25 +59,25 @@ http {
|
|||
|
||||
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_proxied any;
|
||||
{{- end }}
|
||||
{{ end }}
|
||||
|
||||
client_max_body_size "{{ $cfg.bodySize }}";
|
||||
|
||||
log_format upstreaminfo '{{ if $cfg.useProxyProtocol }}$proxy_protocol_addr{{ else }}$remote_addr{{ end }} $host '
|
||||
'[$proxy_add_x_forwarded_for] $server_port $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" '
|
||||
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;
|
||||
}
|
||||
|
@ -155,16 +151,16 @@ http {
|
|||
ssl_dhparam {{ .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
|
||||
|
@ -172,11 +168,11 @@ http {
|
|||
|
||||
{{range $name, $upstream := .upstreams}}
|
||||
upstream {{$upstream.Name}} {
|
||||
{{ if $cfg.enableStickySessions -}}
|
||||
{{ if $cfg.enableStickySessions }}
|
||||
sticky hash=sha1 httponly;
|
||||
{{ else -}}
|
||||
{{ else }}
|
||||
least_conn;
|
||||
{{- end }}
|
||||
{{ end }}
|
||||
{{ range $server := $upstream.Backends }}server {{ $server.Address }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }};
|
||||
{{ end }}
|
||||
}
|
||||
|
@ -184,7 +180,7 @@ 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 }}
|
||||
|
||||
|
@ -192,74 +188,84 @@ http {
|
|||
server {
|
||||
server_name {{ $server.Name }};
|
||||
listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};
|
||||
{{ if $server.SSL }}listen 443 {{ if $cfg.useProxyProtocol }}proxy_protocol{{ end }} ssl {{ if $cfg.enableSpdy }}spdy{{ end }} {{ if $cfg.useHttp2 }}http2{{ end }};
|
||||
{{ if $server.SSL }}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.SSLCertificateKey }};
|
||||
{{- end }}
|
||||
ssl_certificate_key {{ $server.SSLCertificate }};
|
||||
{{ end }}
|
||||
|
||||
{{- if (and $server.SSL $cfg.hsts) }}
|
||||
{{ if (and $server.SSL $cfg.hsts) }}
|
||||
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 }}
|
||||
{{ $authPath := buildAuthLocation $location }}
|
||||
{{- if not (empty $authPath) }}
|
||||
|
||||
{{ if not (empty $location.CertificateAuth.CertFileName) }}
|
||||
# PEM sha: {{ $location.CertificateAuth.PemSHA }}
|
||||
ssl_client_certificate {{ $location.CertificateAuth.CAFileName }};
|
||||
ssl_verify_client on;
|
||||
{{ end }}
|
||||
|
||||
{{ if not (empty $authPath) }}
|
||||
location = {{ $authPath }} {
|
||||
internal;
|
||||
{{ if not $location.ExternalAuthURL.SendBody }}
|
||||
{{ if not $location.ExternalAuth.SendBody }}
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
{{ end -}}
|
||||
{{ if not (empty $location.ExternalAuthURL.Method) }}
|
||||
proxy_method {{ $location.ExternalAuthURL.Method }};
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
{{ if not (empty $location.ExternalAuth.Method) }}
|
||||
proxy_method {{ $location.ExternalAuth.Method }};
|
||||
{{ end }}
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass_request_headers on;
|
||||
proxy_pass {{ $location.ExternalAuthURL.URL }};
|
||||
set $target {{ $location.ExternalAuth.URL }};
|
||||
proxy_pass $target;
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
location {{ $path }} {
|
||||
{{- if gt (len $location.Whitelist.CIDR) 0 }}
|
||||
{{- range $ip := $location.Whitelist.CIDR }}
|
||||
{{ if gt (len $location.Whitelist.CIDR) 0 }}
|
||||
{{ range $ip := $location.Whitelist.CIDR }}
|
||||
allow {{ $ip }};{{ end }}
|
||||
deny all;
|
||||
{{ end -}}
|
||||
{{- if not (empty $authPath) }}
|
||||
{{ end }}
|
||||
|
||||
{{ if not (empty $authPath) }}
|
||||
# this location requires authentication
|
||||
auth_request {{ $authPath }};
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
|
||||
{{- if (and $server.SSL $location.Redirect.SSLRedirect) }}
|
||||
{{ if (and $server.SSL $location.Redirect.SSLRedirect) }}
|
||||
# enforce ssl on server side
|
||||
if ($scheme = http) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
{{/* if the location contains a rate limit annotation, create one */}}
|
||||
{{ $limits := buildRateLimit $location }}
|
||||
{{- range $limit := $limits }}
|
||||
{{ range $limit := $limits }}
|
||||
{{ $limit }}{{ end }}
|
||||
|
||||
{{- if $location.Auth.Secured }}
|
||||
{{- if eq $location.Auth.Type "basic" }}
|
||||
auth_basic "{{ $location.Auth.Realm }}";
|
||||
auth_basic_user_file {{ $location.Auth.File }};
|
||||
{{ if $location.BasicDigestAuth.Secured }}
|
||||
{{ if eq $location.BasicDigestAuth.Type "basic" }}
|
||||
auth_basic "{{ $location.BasicDigestAuth.Realm }}";
|
||||
auth_basic_user_file {{ $location.BasicDigestAuth.File }};
|
||||
{{ else }}
|
||||
#TODO: add nginx-http-auth-digest module
|
||||
auth_digest "{{ $location.Auth.Realm }}";
|
||||
auth_digest_user_file {{ $location.Auth.File }};
|
||||
{{ end -}}
|
||||
auth_digest "{{ $location.BasicDigestAuth.Realm }}";
|
||||
auth_digest_user_file {{ $location.BasicDigestAuth.File }};
|
||||
{{ end }}
|
||||
proxy_set_header Authorization "";
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
|
||||
{{- if $location.EnableCORS }}
|
||||
{{ if $location.EnableCORS }}
|
||||
{{ template "CORS" }}
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
|
||||
proxy_set_header Host $host;
|
||||
|
||||
|
@ -279,29 +285,29 @@ http {
|
|||
# https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
|
||||
proxy_set_header Proxy "";
|
||||
|
||||
proxy_connect_timeout {{ $cfg.proxyConnectTimeout }}s;
|
||||
proxy_send_timeout {{ $cfg.proxySendTimeout }}s;
|
||||
proxy_read_timeout {{ $cfg.proxyReadTimeout }}s;
|
||||
proxy_connect_timeout {{ $location.Proxy.ConnectTimeout }}s;
|
||||
proxy_send_timeout {{ $location.Proxy.SendTimeout }}s;
|
||||
proxy_read_timeout {{ $location.Proxy.ReadTimeout }}s;
|
||||
|
||||
proxy_redirect off;
|
||||
proxy_buffering off;
|
||||
proxy_buffer_size {{ $cfg.proxyBufferSize }};
|
||||
proxy_buffer_size "{{ $location.Proxy.BufferSize }}";
|
||||
|
||||
proxy_http_version 1.1;
|
||||
|
||||
{{/* rewrite only works if the content is not compressed */}}
|
||||
{{- if $location.Redirect.AddBaseURL }}
|
||||
{{ if $location.Redirect.AddBaseURL }}
|
||||
proxy_set_header Accept-Encoding "";
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
|
||||
set $proxy_upstream_name "{{ $location.Upstream.Name }}";
|
||||
{{ buildProxyPass $location }}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{- if eq $server.Name "_" }}
|
||||
{{ if eq $server.Name "_" }}
|
||||
# health checks in cloud providers require the use of port 80
|
||||
location {{ $cfg.HealthzURL }} {
|
||||
location {{ $healthzURL }} {
|
||||
access_log off;
|
||||
return 200;
|
||||
}
|
||||
|
@ -315,9 +321,10 @@ http {
|
|||
access_log off;
|
||||
stub_status on;
|
||||
}
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
{{ template "CUSTOM_ERRORS" $cfg }}
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
|
||||
# default server, used for NGINX healthcheck and access to nginx stats
|
||||
|
@ -327,26 +334,26 @@ http {
|
|||
# https://github.com/kubernetes/contrib/blob/master/ingress/controllers/nginx/nginx/command.go#L104
|
||||
listen 18080 default_server reuseport backlog={{ .backlogSize }};
|
||||
|
||||
location /healthz {
|
||||
location {{ $healthzURL }} {
|
||||
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 }}
|
||||
access_log off;
|
||||
stub_status on;
|
||||
{{- end }}
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
location / {
|
||||
set $proxy_upstream_name "upstream-default-backend";
|
||||
proxy_pass http://upstream-default-backend;
|
||||
}
|
||||
{{- template "CUSTOM_ERRORS" $cfg }}
|
||||
{{ template "CUSTOM_ERRORS" $cfg }}
|
||||
}
|
||||
|
||||
# default server for services without endpoints
|
||||
|
@ -367,6 +374,42 @@ 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;
|
||||
{{ 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';
|
||||
|
||||
access_log /var/log/nginx/access.log log_stream;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
# configure default backend for SSL
|
||||
upstream nginx-ssl-backend {
|
||||
server 127.0.0.1:442;
|
||||
}
|
||||
|
||||
{{ range $i, $passthrough := .passthroughUpstreams }}
|
||||
upstream {{ $passthrough.Name }}-{{ $i }}-pt {
|
||||
{{ range $server := $passthrough.Backends }}server {{ $server.Address }}:{{ $server.Port }};
|
||||
{{ end }}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
|
||||
{{ 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 }} {
|
||||
|
@ -376,8 +419,8 @@ stream {
|
|||
|
||||
server {
|
||||
listen {{ $tcpServer.Path }};
|
||||
proxy_connect_timeout {{ $cfg.proxyConnectTimeout }};
|
||||
proxy_timeout {{ $cfg.proxyReadTimeout }};
|
||||
proxy_connect_timeout {{ $tcpServer.Proxy.ConnectTimeout }}s;
|
||||
proxy_timeout {{ $tcpServer.Proxy.ReadTimeout }}s;
|
||||
proxy_pass tcp-{{ $tcpServer.Upstream.Name }};
|
||||
}
|
||||
{{ end }}
|
8
controllers/nginx/rootfs/ingress-controller/clean-nginx-conf.sh
Executable file
8
controllers/nginx/rootfs/ingress-controller/clean-nginx-conf.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script removes consecutive empty lines in nginx.conf
|
||||
# Using sed is more simple than using a go regex
|
||||
|
||||
# first sed removes empty lines
|
||||
# second sed command replaces the empty lines
|
||||
sed -e 's/^ *$/\'$'\n/g' | sed -e '/^$/{N;/^\n$/d;}'
|
|
@ -1,301 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/util/workqueue"
|
||||
)
|
||||
|
||||
// StoreToIngressLister makes a Store that lists Ingress.
|
||||
type StoreToIngressLister struct {
|
||||
cache.Store
|
||||
}
|
||||
|
||||
// StoreToSecretsLister makes a Store that lists Secrets.
|
||||
type StoreToSecretsLister struct {
|
||||
cache.Store
|
||||
}
|
||||
|
||||
// StoreToConfigmapLister makes a Store that lists Configmap.
|
||||
type StoreToConfigmapLister struct {
|
||||
cache.Store
|
||||
}
|
||||
|
||||
// taskQueue manages a work queue through an independent worker that
|
||||
// invokes the given sync function for every work item inserted.
|
||||
type taskQueue struct {
|
||||
// queue is the work queue the worker polls
|
||||
queue workqueue.RateLimitingInterface
|
||||
// sync is called for each item in the queue
|
||||
sync func(string) error
|
||||
// workerDone is closed when the worker exits
|
||||
workerDone chan struct{}
|
||||
}
|
||||
|
||||
func (t *taskQueue) run(period time.Duration, stopCh <-chan struct{}) {
|
||||
wait.Until(t.worker, period, stopCh)
|
||||
}
|
||||
|
||||
// enqueue enqueues ns/name of the given api object in the task queue.
|
||||
func (t *taskQueue) enqueue(obj interface{}) {
|
||||
key, err := keyFunc(obj)
|
||||
if err != nil {
|
||||
glog.Infof("could not get key for object %+v: %v", obj, err)
|
||||
return
|
||||
}
|
||||
t.queue.Add(key)
|
||||
}
|
||||
|
||||
func (t *taskQueue) requeue(key string) {
|
||||
t.queue.AddRateLimited(key)
|
||||
}
|
||||
|
||||
// worker processes work in the queue through sync.
|
||||
func (t *taskQueue) worker() {
|
||||
for {
|
||||
key, quit := t.queue.Get()
|
||||
if quit {
|
||||
close(t.workerDone)
|
||||
return
|
||||
}
|
||||
glog.V(3).Infof("syncing %v", key)
|
||||
if err := t.sync(key.(string)); err != nil {
|
||||
glog.Warningf("requeuing %v, err %v", key, err)
|
||||
t.requeue(key.(string))
|
||||
} else {
|
||||
t.queue.Forget(key)
|
||||
}
|
||||
|
||||
t.queue.Done(key)
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown shuts down the work queue and waits for the worker to ACK
|
||||
func (t *taskQueue) shutdown() {
|
||||
t.queue.ShutDown()
|
||||
<-t.workerDone
|
||||
}
|
||||
|
||||
// NewTaskQueue creates a new task queue with the given sync function.
|
||||
// The sync function is called for every element inserted into the queue.
|
||||
func NewTaskQueue(syncFn func(string) error) *taskQueue {
|
||||
return &taskQueue{
|
||||
queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
|
||||
sync: syncFn,
|
||||
workerDone: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// getPodDetails returns runtime information about the pod: name, namespace and IP of the node
|
||||
func getPodDetails(kubeClient *unversioned.Client) (*podInfo, error) {
|
||||
podName := os.Getenv("POD_NAME")
|
||||
podNs := os.Getenv("POD_NAMESPACE")
|
||||
|
||||
if podName == "" && podNs == "" {
|
||||
return nil, fmt.Errorf("unable to get POD information (missing POD_NAME or POD_NAMESPACE environment variable")
|
||||
}
|
||||
|
||||
err := waitForPodRunning(kubeClient, podNs, podName, time.Millisecond*200, time.Second*30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pod, _ := kubeClient.Pods(podNs).Get(podName)
|
||||
if pod == nil {
|
||||
return nil, fmt.Errorf("unable to get POD information")
|
||||
}
|
||||
|
||||
node, err := kubeClient.Nodes().Get(pod.Spec.NodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var externalIP string
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == api.NodeExternalIP {
|
||||
if address.Address != "" {
|
||||
externalIP = address.Address
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if externalIP == "" && address.Type == api.NodeLegacyHostIP {
|
||||
externalIP = address.Address
|
||||
}
|
||||
}
|
||||
|
||||
return &podInfo{
|
||||
PodName: podName,
|
||||
PodNamespace: podNs,
|
||||
NodeIP: externalIP,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isValidService(kubeClient *unversioned.Client, name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("empty string is not a valid service name")
|
||||
}
|
||||
|
||||
parts := strings.Split(name, "/")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid name format (namespace/name) in service '%v'", name)
|
||||
}
|
||||
|
||||
_, err := kubeClient.Services(parts[0]).Get(parts[1])
|
||||
return err
|
||||
}
|
||||
|
||||
func isHostValid(host string, cns []string) bool {
|
||||
for _, cn := range cns {
|
||||
if matchHostnames(cn, host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func matchHostnames(pattern, host string) bool {
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
pattern = strings.TrimSuffix(pattern, ".")
|
||||
|
||||
if len(pattern) == 0 || len(host) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
patternParts := strings.Split(pattern, ".")
|
||||
hostParts := strings.Split(host, ".")
|
||||
|
||||
if len(patternParts) != len(hostParts) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, patternPart := range patternParts {
|
||||
if i == 0 && patternPart == "*" {
|
||||
continue
|
||||
}
|
||||
if patternPart != hostParts[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func parseNsName(input string) (string, string, error) {
|
||||
nsName := strings.Split(input, "/")
|
||||
if len(nsName) != 2 {
|
||||
return "", "", fmt.Errorf("invalid format (namespace/name) found in '%v'", input)
|
||||
}
|
||||
|
||||
return nsName[0], nsName[1], nil
|
||||
}
|
||||
|
||||
func waitForPodRunning(kubeClient *unversioned.Client, ns, podName string, interval, timeout time.Duration) error {
|
||||
condition := func(pod *api.Pod) (bool, error) {
|
||||
if pod.Status.Phase == api.PodRunning {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return waitForPodCondition(kubeClient, ns, podName, condition, interval, timeout)
|
||||
}
|
||||
|
||||
// waitForPodCondition waits for a pod in state defined by a condition (func)
|
||||
func waitForPodCondition(kubeClient *unversioned.Client, ns, podName string, condition func(pod *api.Pod) (bool, error),
|
||||
interval, timeout time.Duration) error {
|
||||
return wait.PollImmediate(interval, timeout, func() (bool, error) {
|
||||
pod, err := kubeClient.Pods(ns).Get(podName)
|
||||
if err != nil {
|
||||
if apierrs.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
done, err := condition(pod)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if done {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ingAnnotations represents Ingress annotations.
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
const (
|
||||
// ingressClassKey picks a specific "class" for the Ingress. The controller
|
||||
// only processes Ingresses with this annotation either unset, or set
|
||||
// to either nginxIngressClass or the empty string.
|
||||
ingressClassKey = "kubernetes.io/ingress.class"
|
||||
nginxIngressClass = "nginx"
|
||||
)
|
||||
|
||||
func (ing ingAnnotations) ingressClass() string {
|
||||
val, ok := ing[ingressClassKey]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// isNGINXIngress returns true if the given Ingress either doesn't specify the
|
||||
// ingress.class annotation, or it's set to "nginx".
|
||||
func isNGINXIngress(ing *extensions.Ingress) bool {
|
||||
class := ingAnnotations(ing.ObjectMeta.Annotations).ingressClass()
|
||||
return class == "" || class == nginxIngressClass
|
||||
}
|
||||
|
||||
const (
|
||||
snakeOilPem = "/etc/ssl/certs/ssl-cert-snakeoil.pem"
|
||||
snakeOilKey = "/etc/ssl/private/ssl-cert-snakeoil.key"
|
||||
)
|
||||
|
||||
// getFakeSSLCert returns the snake oil ssl certificate created by the command
|
||||
// make-ssl-cert generate-default-snakeoil --force-overwrite
|
||||
func getFakeSSLCert() (string, string) {
|
||||
cert, err := ioutil.ReadFile(snakeOilPem)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
key, err := ioutil.ReadFile(snakeOilKey)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return string(cert), string(key)
|
||||
}
|
|
@ -14,5 +14,21 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package protobuf provides a Kubernetes serializer for the protobuf format.
|
||||
package protobuf
|
||||
package cache
|
||||
|
||||
import "k8s.io/kubernetes/pkg/client/cache"
|
||||
|
||||
// StoreToIngressLister makes a Store that lists Ingress.
|
||||
type StoreToIngressLister struct {
|
||||
cache.Store
|
||||
}
|
||||
|
||||
// StoreToSecretsLister makes a Store that lists Secrets.
|
||||
type StoreToSecretsLister struct {
|
||||
cache.Store
|
||||
}
|
||||
|
||||
// StoreToConfigmapLister makes a Store that lists Configmap.
|
||||
type StoreToConfigmapLister struct {
|
||||
cache.Store
|
||||
}
|
|
@ -23,11 +23,10 @@ import (
|
|||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -35,11 +34,9 @@ const (
|
|||
authSecret = "ingress.kubernetes.io/auth-secret"
|
||||
authRealm = "ingress.kubernetes.io/auth-realm"
|
||||
|
||||
defAuthRealm = "Authentication Required"
|
||||
|
||||
// DefAuthDirectory default directory used to store files
|
||||
// to authenticate request in NGINX
|
||||
DefAuthDirectory = "/etc/nginx/auth"
|
||||
// to authenticate request
|
||||
DefAuthDirectory = "/etc/ingress-controller/auth"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -53,95 +50,58 @@ var (
|
|||
// ErrInvalidAuthType is return in case of unsupported authentication type
|
||||
ErrInvalidAuthType = errors.New("invalid authentication type")
|
||||
|
||||
// ErrMissingAuthType is return when the annotation for authentication is missing
|
||||
ErrMissingAuthType = errors.New("authentication type is missing")
|
||||
|
||||
// ErrMissingSecretName is returned when the name of the secret is missing
|
||||
ErrMissingSecretName = errors.New("secret name is missing")
|
||||
|
||||
// ErrMissingAuthInSecret is returned when there is no auth key in secret data
|
||||
ErrMissingAuthInSecret = errors.New("the secret does not contains the auth key")
|
||||
|
||||
// ErrMissingAnnotations is returned when the ingress rule
|
||||
// does not contains annotations related with authentication
|
||||
ErrMissingAnnotations = errors.New("missing authentication annotations")
|
||||
)
|
||||
|
||||
// Nginx returns authentication configuration for an Ingress rule
|
||||
type Nginx struct {
|
||||
// BasicDigest returns authentication configuration for an Ingress rule
|
||||
type BasicDigest struct {
|
||||
Type string
|
||||
Realm string
|
||||
File string
|
||||
Secured bool
|
||||
}
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
func (a ingAnnotations) authType() (string, error) {
|
||||
val, ok := a[authType]
|
||||
if !ok {
|
||||
return "", ErrMissingAuthType
|
||||
}
|
||||
|
||||
if !authTypeRegex.MatchString(val) {
|
||||
glog.Warningf("%v is not a valid authentication type", val)
|
||||
return "", ErrInvalidAuthType
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (a ingAnnotations) realm() string {
|
||||
val, ok := a[authRealm]
|
||||
if !ok {
|
||||
return defAuthRealm
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (a ingAnnotations) secretName() (string, error) {
|
||||
val, ok := a[authSecret]
|
||||
if !ok {
|
||||
return "", ErrMissingSecretName
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to add authentication in the paths defined in the rule
|
||||
// and generated an htpasswd compatible file to be used as source
|
||||
// during the authentication process
|
||||
func ParseAnnotations(kubeClient client.Interface, ing *extensions.Ingress, authDir string) (*Nginx, error) {
|
||||
func ParseAnnotations(ing *extensions.Ingress, authDir string, fn func(string) (*api.Secret, error)) (*BasicDigest, error) {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return &Nginx{}, ErrMissingAnnotations
|
||||
return &BasicDigest{}, parser.ErrMissingAnnotations
|
||||
}
|
||||
|
||||
at, err := ingAnnotations(ing.GetAnnotations()).authType()
|
||||
at, err := parser.GetStringAnnotation(authType, ing)
|
||||
if err != nil {
|
||||
return &Nginx{}, err
|
||||
return &BasicDigest{}, err
|
||||
}
|
||||
|
||||
s, err := ingAnnotations(ing.GetAnnotations()).secretName()
|
||||
if !authTypeRegex.MatchString(at) {
|
||||
return &BasicDigest{}, ErrInvalidAuthType
|
||||
}
|
||||
|
||||
s, err := parser.GetStringAnnotation(authSecret, ing)
|
||||
if err != nil {
|
||||
return &Nginx{}, err
|
||||
return &BasicDigest{}, err
|
||||
}
|
||||
|
||||
secret, err := kubeClient.Secrets(ing.Namespace).Get(s)
|
||||
secret, err := fn(fmt.Sprintf("%v/%v", ing.Namespace, s))
|
||||
if err != nil {
|
||||
return &Nginx{}, err
|
||||
return &BasicDigest{}, err
|
||||
}
|
||||
|
||||
realm := ingAnnotations(ing.GetAnnotations()).realm()
|
||||
realm, _ := parser.GetStringAnnotation(authRealm, ing)
|
||||
|
||||
passFile := fmt.Sprintf("%v/%v-%v.passwd", authDir, ing.GetNamespace(), ing.GetName())
|
||||
err = dumpSecret(passFile, secret)
|
||||
if err != nil {
|
||||
return &Nginx{}, err
|
||||
return &BasicDigest{}, err
|
||||
}
|
||||
|
||||
return &Nginx{
|
||||
return &BasicDigest{
|
||||
Type: at,
|
||||
Realm: realm,
|
||||
File: passFile,
|
|
@ -25,8 +25,6 @@ import (
|
|||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
)
|
||||
|
||||
|
@ -65,79 +63,21 @@ func buildIngress() *extensions.Ingress {
|
|||
}
|
||||
}
|
||||
|
||||
type secretsClient struct {
|
||||
unversioned.Interface
|
||||
}
|
||||
|
||||
// dummySecret generates a secret with one user inside the auth key
|
||||
// foo:md5(bar)
|
||||
func dummySecret() *api.Secret {
|
||||
func mockSecret(name string) (*api.Secret, error) {
|
||||
return &api.Secret{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: api.NamespaceDefault,
|
||||
Name: "demo-secret",
|
||||
},
|
||||
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mockClient() *testclient.Fake {
|
||||
return testclient.NewSimpleFake(dummySecret())
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
_, err := ingAnnotations(ing.GetAnnotations()).authType()
|
||||
if err == nil {
|
||||
t.Error("Expected a validation error")
|
||||
}
|
||||
realm := ingAnnotations(ing.GetAnnotations()).realm()
|
||||
if realm != defAuthRealm {
|
||||
t.Error("Expected default realm")
|
||||
}
|
||||
|
||||
_, err = ingAnnotations(ing.GetAnnotations()).secretName()
|
||||
if err == nil {
|
||||
t.Error("Expected a validation error")
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
data[authType] = "demo"
|
||||
data[authSecret] = "demo-secret"
|
||||
data[authRealm] = "demo"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, err = ingAnnotations(ing.GetAnnotations()).authType()
|
||||
if err == nil {
|
||||
t.Error("Expected a validation error")
|
||||
}
|
||||
|
||||
realm = ingAnnotations(ing.GetAnnotations()).realm()
|
||||
if realm != "demo" {
|
||||
t.Errorf("Expected demo as realm but returned %s", realm)
|
||||
}
|
||||
|
||||
secret, err := ingAnnotations(ing.GetAnnotations()).secretName()
|
||||
if err != nil {
|
||||
t.Error("Unexpec error %v", err)
|
||||
}
|
||||
if secret != "demo-secret" {
|
||||
t.Errorf("Expected demo-secret as realm but returned %s", secret)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressWithoutAuth(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
client := mockClient()
|
||||
_, err := ParseAnnotations(client, ing, "")
|
||||
_, err := ParseAnnotations(ing, "", mockSecret)
|
||||
if err == nil {
|
||||
t.Error("Expected error with ingress without annotations")
|
||||
}
|
||||
|
||||
if err == ErrMissingAuthType {
|
||||
t.Errorf("Expected MissingAuthType error but returned %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressAuth(t *testing.T) {
|
||||
|
@ -152,20 +92,19 @@ func TestIngressAuth(t *testing.T) {
|
|||
_, dir, _ := dummySecretContent(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
client := mockClient()
|
||||
nginxAuth, err := ParseAnnotations(client, ing, dir)
|
||||
auth, err := ParseAnnotations(ing, dir, mockSecret)
|
||||
if err != nil {
|
||||
t.Errorf("Uxpected error with ingress: %v", err)
|
||||
}
|
||||
|
||||
if nginxAuth.Type != "basic" {
|
||||
t.Errorf("Expected basic as auth type but returned %s", nginxAuth.Type)
|
||||
if auth.Type != "basic" {
|
||||
t.Errorf("Expected basic as auth type but returned %s", auth.Type)
|
||||
}
|
||||
if nginxAuth.Realm != "-realm-" {
|
||||
t.Errorf("Expected -realm- as realm but returned %s", nginxAuth.Realm)
|
||||
if auth.Realm != "-realm-" {
|
||||
t.Errorf("Expected -realm- as realm but returned %s", auth.Realm)
|
||||
}
|
||||
if !nginxAuth.Secured {
|
||||
t.Errorf("Expected true as secured but returned %v", nginxAuth.Secured)
|
||||
if !auth.Secured {
|
||||
t.Errorf("Expected true as secured but returned %v", auth.Secured)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,9 +119,7 @@ func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
|
|||
t.Error(err)
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
|
||||
s := dummySecret()
|
||||
|
||||
s, _ := mockSecret("demo")
|
||||
return tmpfile.Name(), dir, s
|
||||
}
|
||||
|
|
@ -17,12 +17,11 @@ limitations under the License.
|
|||
package authreq
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
||||
|
@ -33,49 +32,13 @@ const (
|
|||
authBody = "ingress.kubernetes.io/auth-send-body"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMissingAnnotations is returned when the ingress rule
|
||||
// does not contain annotations related with authentication
|
||||
ErrMissingAnnotations = errors.New("missing authentication annotations")
|
||||
)
|
||||
|
||||
// Auth returns external authentication configuration for an Ingress rule
|
||||
type Auth struct {
|
||||
// External returns external authentication configuration for an Ingress rule
|
||||
type External struct {
|
||||
URL string
|
||||
Method string
|
||||
SendBody bool
|
||||
}
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
func (a ingAnnotations) url() (string, error) {
|
||||
val, ok := a[authURL]
|
||||
if !ok {
|
||||
return "", ErrMissingAnnotations
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (a ingAnnotations) method() string {
|
||||
val, ok := a[authMethod]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (a ingAnnotations) sendBody() bool {
|
||||
val, ok := a[authBody]
|
||||
if ok {
|
||||
if b, err := strconv.ParseBool(val); err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
|
||||
)
|
||||
|
@ -95,42 +58,42 @@ func validMethod(method string) bool {
|
|||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to use an external URL as source for authentication
|
||||
func ParseAnnotations(ing *extensions.Ingress) (Auth, error) {
|
||||
func ParseAnnotations(ing *extensions.Ingress) (External, error) {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return Auth{}, ErrMissingAnnotations
|
||||
return External{}, parser.ErrMissingAnnotations
|
||||
}
|
||||
|
||||
str, err := ingAnnotations(ing.GetAnnotations()).url()
|
||||
str, err := parser.GetStringAnnotation(authURL, ing)
|
||||
if err != nil {
|
||||
return Auth{}, err
|
||||
return External{}, err
|
||||
}
|
||||
if str == "" {
|
||||
return Auth{}, fmt.Errorf("an empty string is not a valid URL")
|
||||
return External{}, fmt.Errorf("an empty string is not a valid URL")
|
||||
}
|
||||
|
||||
ur, err := url.Parse(str)
|
||||
if err != nil {
|
||||
return Auth{}, err
|
||||
return External{}, err
|
||||
}
|
||||
if ur.Scheme == "" {
|
||||
return Auth{}, fmt.Errorf("url scheme is empty")
|
||||
return External{}, fmt.Errorf("url scheme is empty")
|
||||
}
|
||||
if ur.Host == "" {
|
||||
return Auth{}, fmt.Errorf("url host is empty")
|
||||
return External{}, fmt.Errorf("url host is empty")
|
||||
}
|
||||
|
||||
if strings.Index(ur.Host, "..") != -1 {
|
||||
return Auth{}, fmt.Errorf("invalid url host")
|
||||
if strings.Contains(ur.Host, "..") {
|
||||
return External{}, fmt.Errorf("invalid url host")
|
||||
}
|
||||
|
||||
m := ingAnnotations(ing.GetAnnotations()).method()
|
||||
m, _ := parser.GetStringAnnotation(authMethod, ing)
|
||||
if len(m) != 0 && !validMethod(m) {
|
||||
return Auth{}, fmt.Errorf("invalid HTTP method")
|
||||
return External{}, fmt.Errorf("invalid HTTP method")
|
||||
}
|
||||
|
||||
sb := ingAnnotations(ing.GetAnnotations()).sendBody()
|
||||
sb, _ := parser.GetBoolAnnotation(authBody, ing)
|
||||
|
||||
return Auth{
|
||||
return External{
|
||||
URL: str,
|
||||
Method: m,
|
||||
SendBody: sb,
|
|
@ -63,11 +63,6 @@ func buildIngress() *extensions.Ingress {
|
|||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
_, err := ingAnnotations(ing.GetAnnotations()).url()
|
||||
if err == nil {
|
||||
t.Error("Expected a validation error")
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
ing.SetAnnotations(data)
|
||||
|
65
core/pkg/ingress/annotations/authtls/main.go
Normal file
65
core/pkg/ingress/annotations/authtls/main.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2015 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 authtls
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
"k8s.io/ingress/core/pkg/k8s"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
||||
const (
|
||||
// name of the secret
|
||||
authTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||
)
|
||||
|
||||
// SSLCert returns external authentication configuration for an Ingress rule
|
||||
type SSLCert struct {
|
||||
Secret string
|
||||
CertFileName string
|
||||
KeyFileName string
|
||||
CAFileName string
|
||||
PemSHA string
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to use an external URL as source for authentication
|
||||
func ParseAnnotations(ing *extensions.Ingress,
|
||||
fn func(secret string) (*SSLCert, error)) (*SSLCert, error) {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return &SSLCert{}, parser.ErrMissingAnnotations
|
||||
}
|
||||
|
||||
str, err := parser.GetStringAnnotation(authTLSSecret, ing)
|
||||
if err != nil {
|
||||
return &SSLCert{}, err
|
||||
}
|
||||
|
||||
if str == "" {
|
||||
return &SSLCert{}, fmt.Errorf("an empty string is not a valid secret name")
|
||||
}
|
||||
|
||||
_, _, err = k8s.ParseNameNS(str)
|
||||
if err != nil {
|
||||
return &SSLCert{}, err
|
||||
}
|
||||
|
||||
return fn(str)
|
||||
}
|
107
core/pkg/ingress/annotations/authtls/main_test.go
Normal file
107
core/pkg/ingress/annotations/authtls/main_test.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
Copyright 2015 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 authtls
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func buildIngress() *extensions.Ingress {
|
||||
defaultBackend := extensions.IngressBackend{
|
||||
ServiceName: "default-backend",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
}
|
||||
|
||||
return &extensions.Ingress{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: extensions.IngressSpec{
|
||||
Backend: &extensions.IngressBackend{
|
||||
ServiceName: "default-backend",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
},
|
||||
Rules: []extensions.IngressRule{
|
||||
{
|
||||
Host: "foo.bar.com",
|
||||
IngressRuleValue: extensions.IngressRuleValue{
|
||||
HTTP: &extensions.HTTPIngressRuleValue{
|
||||
Paths: []extensions.HTTPIngressPath{
|
||||
{
|
||||
Path: "/foo",
|
||||
Backend: defaultBackend,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
ing.SetAnnotations(data)
|
||||
/*
|
||||
tests := []struct {
|
||||
title string
|
||||
url string
|
||||
method string
|
||||
sendBody bool
|
||||
expErr bool
|
||||
}{
|
||||
{"empty", "", "", false, true},
|
||||
{"no scheme", "bar", "", false, true},
|
||||
{"invalid host", "http://", "", false, true},
|
||||
{"invalid host (multiple dots)", "http://foo..bar.com", "", false, true},
|
||||
{"valid URL", "http://bar.foo.com/external-auth", "", false, false},
|
||||
{"valid URL - send body", "http://foo.com/external-auth", "POST", true, false},
|
||||
{"valid URL - send body", "http://foo.com/external-auth", "GET", true, false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
data[authTLSSecret] = ""
|
||||
test.title
|
||||
|
||||
u, err := ParseAnnotations(ing)
|
||||
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected error but retuned nil", test.title)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if u.URL != test.url {
|
||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL)
|
||||
}
|
||||
if u.Method != test.method {
|
||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.method, u.Method)
|
||||
}
|
||||
if u.SendBody != test.sendBody {
|
||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.sendBody, u.SendBody)
|
||||
}
|
||||
}*/
|
||||
}
|
|
@ -17,8 +17,7 @@ limitations under the License.
|
|||
package cors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
@ -27,24 +26,8 @@ const (
|
|||
cors = "ingress.kubernetes.io/enable-cors"
|
||||
)
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
func (a ingAnnotations) cors() bool {
|
||||
val, ok := a[cors]
|
||||
if ok {
|
||||
if b, err := strconv.ParseBool(val); err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to indicate if the location/s should allows CORS
|
||||
func ParseAnnotations(ing *extensions.Ingress) (bool, error) {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return false, errors.New("no annotations present")
|
||||
}
|
||||
|
||||
return ingAnnotations(ing.GetAnnotations()).cors(), nil
|
||||
return parser.GetBoolAnnotation(cors, ing)
|
||||
}
|
|
@ -17,12 +17,10 @@ limitations under the License.
|
|||
package healthcheck
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -30,19 +28,6 @@ const (
|
|||
upsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMissingMaxFails returned error when the ingress does not contains the
|
||||
// max-fails annotation
|
||||
ErrMissingMaxFails = errors.New("max-fails annotations is missing")
|
||||
|
||||
// ErrMissingFailTimeout returned error when the ingress does not contains
|
||||
// the fail-timeout annotation
|
||||
ErrMissingFailTimeout = errors.New("fail-timeout annotations is missing")
|
||||
|
||||
// ErrInvalidNumber returned
|
||||
ErrInvalidNumber = errors.New("the annotation does not contains a number")
|
||||
)
|
||||
|
||||
// Upstream returns the URL and method to use check the status of
|
||||
// the upstream server/s
|
||||
type Upstream struct {
|
||||
|
@ -50,49 +35,19 @@ type Upstream struct {
|
|||
FailTimeout int
|
||||
}
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
func (a ingAnnotations) maxFails() (int, error) {
|
||||
val, ok := a[upsMaxFails]
|
||||
if !ok {
|
||||
return 0, ErrMissingMaxFails
|
||||
}
|
||||
|
||||
mf, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return 0, ErrInvalidNumber
|
||||
}
|
||||
|
||||
return mf, nil
|
||||
}
|
||||
|
||||
func (a ingAnnotations) failTimeout() (int, error) {
|
||||
val, ok := a[upsFailTimeout]
|
||||
if !ok {
|
||||
return 0, ErrMissingFailTimeout
|
||||
}
|
||||
|
||||
ft, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return 0, ErrInvalidNumber
|
||||
}
|
||||
|
||||
return ft, nil
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to configure upstream check parameters
|
||||
func ParseAnnotations(cfg config.Configuration, ing *extensions.Ingress) *Upstream {
|
||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) *Upstream {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return &Upstream{cfg.UpstreamMaxFails, cfg.UpstreamFailTimeout}
|
||||
}
|
||||
|
||||
mf, err := ingAnnotations(ing.GetAnnotations()).maxFails()
|
||||
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
|
||||
if err != nil {
|
||||
mf = cfg.UpstreamMaxFails
|
||||
}
|
||||
|
||||
ft, err := ingAnnotations(ing.GetAnnotations()).failTimeout()
|
||||
ft, err := parser.GetIntAnnotation(upsFailTimeout, ing)
|
||||
if err != nil {
|
||||
ft = cfg.UpstreamFailTimeout
|
||||
}
|
|
@ -23,7 +23,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
)
|
||||
|
||||
func buildIngress() *extensions.Ingress {
|
||||
|
@ -61,41 +61,6 @@ func buildIngress() *extensions.Ingress {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
_, err := ingAnnotations(ing.GetAnnotations()).maxFails()
|
||||
if err == nil {
|
||||
t.Error("Expected a validation error")
|
||||
}
|
||||
|
||||
_, err = ingAnnotations(ing.GetAnnotations()).failTimeout()
|
||||
if err == nil {
|
||||
t.Error("Expected a validation error")
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
data[upsMaxFails] = "1"
|
||||
data[upsFailTimeout] = "1"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
mf, err := ingAnnotations(ing.GetAnnotations()).maxFails()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if mf != 1 {
|
||||
t.Errorf("Expected 1 but returned %v", mf)
|
||||
}
|
||||
|
||||
ft, err := ingAnnotations(ing.GetAnnotations()).failTimeout()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if ft != 1 {
|
||||
t.Errorf("Expected 1 but returned %v", ft)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressHealthCheck(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
|
@ -103,8 +68,7 @@ func TestIngressHealthCheck(t *testing.T) {
|
|||
data[upsMaxFails] = "2"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
cfg := config.Configuration{}
|
||||
cfg.UpstreamFailTimeout = 1
|
||||
cfg := defaults.Backend{UpstreamFailTimeout: 1}
|
||||
|
||||
nginxHz := ParseAnnotations(cfg, ing)
|
||||
|
|
@ -20,6 +20,9 @@ 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"
|
||||
)
|
||||
|
@ -29,10 +32,6 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
// ErrMissingWhitelist returned error when the ingress does not contains the
|
||||
// whitelist annotation
|
||||
ErrMissingWhitelist = errors.New("whitelist annotation is missing")
|
||||
|
||||
// ErrInvalidCIDR returned error when the whitelist annotation does not
|
||||
// contains a valid IP or network address
|
||||
ErrInvalidCIDR = errors.New("the annotation does not contains a valid IP address or network")
|
||||
|
@ -43,41 +42,31 @@ type SourceRange struct {
|
|||
CIDR []string
|
||||
}
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to limit access to certain client addresses or networks.
|
||||
// Multiple ranges can specified using commas as separator
|
||||
// e.g. `18.0.0.0/8,56.0.0.0/8`
|
||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (*SourceRange, error) {
|
||||
cidrs := []string{}
|
||||
|
||||
func (a ingAnnotations) whitelist() ([]string, error) {
|
||||
cidrs := make([]string, 0)
|
||||
val, ok := a[whitelist]
|
||||
if !ok {
|
||||
return cidrs, ErrMissingWhitelist
|
||||
if ing.GetAnnotations() == nil {
|
||||
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, parser.ErrMissingAnnotations
|
||||
}
|
||||
|
||||
val, err := parser.GetStringAnnotation(whitelist, ing)
|
||||
if err != nil {
|
||||
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, err
|
||||
}
|
||||
|
||||
values := strings.Split(val, ",")
|
||||
ipnets, err := sets.ParseIPNets(values...)
|
||||
if err != nil {
|
||||
return cidrs, ErrInvalidCIDR
|
||||
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, ErrInvalidCIDR
|
||||
}
|
||||
|
||||
for k := range ipnets {
|
||||
cidrs = append(cidrs, k)
|
||||
}
|
||||
|
||||
return cidrs, nil
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to limit access to certain client addresses or networks.
|
||||
// Multiple ranges can specified using commas as separator
|
||||
// e.g. `18.0.0.0/8,56.0.0.0/8`
|
||||
func ParseAnnotations(whiteList []string, ing *extensions.Ingress) (*SourceRange, error) {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return &SourceRange{whiteList}, ErrMissingWhitelist
|
||||
}
|
||||
|
||||
wl, err := ingAnnotations(ing.GetAnnotations()).whitelist()
|
||||
if err != nil {
|
||||
wl = whiteList
|
||||
}
|
||||
|
||||
return &SourceRange{wl}, err
|
||||
return &SourceRange{cidrs}, nil
|
||||
}
|
|
@ -23,6 +23,8 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
)
|
||||
|
||||
func buildIngress() *extensions.Ingress {
|
||||
|
@ -60,51 +62,9 @@ func buildIngress() *extensions.Ingress {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
_, err := ingAnnotations(ing.GetAnnotations()).whitelist()
|
||||
if err == nil {
|
||||
t.Error("Expected a validation error")
|
||||
}
|
||||
|
||||
testNet := "10.0.0.0/24"
|
||||
enet := []string{testNet}
|
||||
|
||||
data := map[string]string{}
|
||||
data[whitelist] = testNet
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
wl, err := ingAnnotations(ing.GetAnnotations()).whitelist()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(wl, enet) {
|
||||
t.Errorf("Expected %v but returned %s", enet, wl)
|
||||
}
|
||||
|
||||
data[whitelist] = "10.0.0.0/24,10.0.1.0/25"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
wl, err = ingAnnotations(ing.GetAnnotations()).whitelist()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(wl) != 2 {
|
||||
t.Errorf("Expected 2 netwotks but %v was returned", len(wl))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
_, err := ingAnnotations(ing.GetAnnotations()).whitelist()
|
||||
if err == nil {
|
||||
t.Error("Expected a validation error")
|
||||
}
|
||||
|
||||
testNet := "10.0.0.0/24"
|
||||
enet := []string{testNet}
|
||||
|
||||
|
@ -116,7 +76,7 @@ func TestParseAnnotations(t *testing.T) {
|
|||
CIDR: enet,
|
||||
}
|
||||
|
||||
sr, err := ParseAnnotations([]string{}, ing)
|
||||
sr, err := ParseAnnotations(defaults.Backend{}, ing)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -127,21 +87,23 @@ func TestParseAnnotations(t *testing.T) {
|
|||
|
||||
data[whitelist] = "www"
|
||||
ing.SetAnnotations(data)
|
||||
_, err = ParseAnnotations([]string{}, ing)
|
||||
_, err = ParseAnnotations(defaults.Backend{}, ing)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error parsing an invalid cidr")
|
||||
}
|
||||
|
||||
delete(data, "whitelist")
|
||||
ing.SetAnnotations(data)
|
||||
sr, _ = ParseAnnotations([]string{}, ing)
|
||||
//sr, _ = ParseAnnotations(defaults.Backend{}, ing)
|
||||
// TODO: fix test
|
||||
/*
|
||||
if !reflect.DeepEqual(sr.CIDR, []string{}) {
|
||||
t.Errorf("Expected empty CIDR but %v returned", sr.CIDR)
|
||||
}
|
||||
|
||||
sr, _ = ParseAnnotations([]string{}, &extensions.Ingress{})
|
||||
sr, _ = ParseAnnotations(defaults.Upstream{}, &extensions.Ingress{})
|
||||
if !reflect.DeepEqual(sr.CIDR, []string{}) {
|
||||
t.Errorf("Expected empty CIDR but %v returned", sr.CIDR)
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
102
core/pkg/ingress/annotations/parser/main.go
Normal file
102
core/pkg/ingress/annotations/parser/main.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Copyright 2015 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 parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMissingAnnotations is returned when the ingress rule
|
||||
// does not contains annotations related with rate limit
|
||||
ErrMissingAnnotations = errors.New("Ingress rule without annotations")
|
||||
|
||||
// ErrInvalidName ...
|
||||
ErrInvalidName = errors.New("invalid annotation name")
|
||||
)
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
func (a ingAnnotations) parseBool(name string) (bool, error) {
|
||||
val, ok := a[name]
|
||||
if ok {
|
||||
if b, err := strconv.ParseBool(val); err == nil {
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
return false, ErrMissingAnnotations
|
||||
}
|
||||
|
||||
func (a ingAnnotations) parseString(name string) (string, error) {
|
||||
val, ok := a[name]
|
||||
if ok {
|
||||
return val, nil
|
||||
}
|
||||
return "", ErrMissingAnnotations
|
||||
}
|
||||
|
||||
func (a ingAnnotations) parseInt(name string) (int, error) {
|
||||
val, ok := a[name]
|
||||
if ok {
|
||||
i, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid annotations value: %v", err)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
return 0, ErrMissingAnnotations
|
||||
}
|
||||
|
||||
// GetBoolAnnotation ...
|
||||
func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
|
||||
if ing == nil || ing.GetAnnotations() == nil {
|
||||
return false, ErrMissingAnnotations
|
||||
}
|
||||
if name == "" {
|
||||
return false, ErrInvalidName
|
||||
}
|
||||
|
||||
return ingAnnotations(ing.GetAnnotations()).parseBool(name)
|
||||
}
|
||||
|
||||
// GetStringAnnotation ...
|
||||
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
|
||||
if ing == nil || ing.GetAnnotations() == nil {
|
||||
return "", ErrMissingAnnotations
|
||||
}
|
||||
if name == "" {
|
||||
return "", ErrInvalidName
|
||||
}
|
||||
|
||||
return ingAnnotations(ing.GetAnnotations()).parseString(name)
|
||||
}
|
||||
|
||||
// GetIntAnnotation ...
|
||||
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
|
||||
if ing == nil || ing.GetAnnotations() == nil {
|
||||
return 0, ErrMissingAnnotations
|
||||
}
|
||||
if name == "" {
|
||||
return 0, ErrInvalidName
|
||||
}
|
||||
|
||||
return ingAnnotations(ing.GetAnnotations()).parseInt(name)
|
||||
}
|
154
core/pkg/ingress/annotations/parser/main_test.go
Normal file
154
core/pkg/ingress/annotations/parser/main_test.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
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 parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
||||
func buildIngress() *extensions.Ingress {
|
||||
return &extensions.Ingress{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: extensions.IngressSpec{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBoolAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
_, err := GetBoolAnnotation("", nil)
|
||||
if err == nil {
|
||||
t.Errorf("expected error but retuned nil")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
field string
|
||||
value string
|
||||
exp bool
|
||||
expErr bool
|
||||
}{
|
||||
{"empty - false", "", "false", false, true},
|
||||
{"empty - true", "", "true", false, true},
|
||||
{"valid - false", "bool", "false", false, false},
|
||||
{"valid - true", "bool", "true", true, false},
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
for _, test := range tests {
|
||||
data[test.field] = test.value
|
||||
|
||||
u, err := GetBoolAnnotation(test.field, ing)
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected error but retuned nil", test.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if u != test.exp {
|
||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStringAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
_, err := GetStringAnnotation("", nil)
|
||||
if err == nil {
|
||||
t.Errorf("expected error but retuned nil")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
field string
|
||||
value string
|
||||
exp string
|
||||
expErr bool
|
||||
}{
|
||||
{"empty - A", "", "A", "", true},
|
||||
{"empty - B", "", "B", "", true},
|
||||
{"valid - A", "string", "A", "A", false},
|
||||
{"valid - B", "string", "B", "B", false},
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
for _, test := range tests {
|
||||
data[test.field] = test.value
|
||||
|
||||
s, err := GetStringAnnotation(test.field, ing)
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected error but retuned nil", test.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if s != test.exp {
|
||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIntAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
_, err := GetIntAnnotation("", nil)
|
||||
if err == nil {
|
||||
t.Errorf("expected error but retuned nil")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
field string
|
||||
value string
|
||||
exp int
|
||||
expErr bool
|
||||
}{
|
||||
{"empty - A", "", "1", 0, true},
|
||||
{"empty - B", "", "2", 0, true},
|
||||
{"valid - A", "string", "1", 1, false},
|
||||
{"valid - B", "string", "2", 2, false},
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
for _, test := range tests {
|
||||
data[test.field] = test.value
|
||||
|
||||
s, err := GetIntAnnotation(test.field, ing)
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected error but retuned nil", test.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if s != test.exp {
|
||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
|
||||
}
|
||||
}
|
||||
}
|
74
core/pkg/ingress/annotations/proxy/main.go
Normal file
74
core/pkg/ingress/annotations/proxy/main.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
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 proxy
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
)
|
||||
|
||||
const (
|
||||
connect = "ingress.kubernetes.io/proxy-connect-timeout"
|
||||
send = "ingress.kubernetes.io/proxy-send-timeout"
|
||||
read = "ingress.kubernetes.io/proxy-read-timeout"
|
||||
bufferSize = "ingress.kubernetes.io/proxy-buffer-size"
|
||||
)
|
||||
|
||||
// Configuration returns the proxy timeout to use in the upstream server/s
|
||||
type Configuration struct {
|
||||
ConnectTimeout int
|
||||
SendTimeout int
|
||||
ReadTimeout int
|
||||
BufferSize string
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to configure upstream check parameters
|
||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) *Configuration {
|
||||
if ing == nil || ing.GetAnnotations() == nil {
|
||||
return &Configuration{
|
||||
cfg.ProxyConnectTimeout,
|
||||
cfg.ProxySendTimeout,
|
||||
cfg.ProxyReadTimeout,
|
||||
cfg.ProxyBufferSize,
|
||||
}
|
||||
}
|
||||
|
||||
ct, err := parser.GetIntAnnotation(connect, ing)
|
||||
if err != nil {
|
||||
ct = cfg.ProxyConnectTimeout
|
||||
}
|
||||
|
||||
st, err := parser.GetIntAnnotation(send, ing)
|
||||
if err != nil {
|
||||
st = cfg.ProxySendTimeout
|
||||
}
|
||||
|
||||
rt, err := parser.GetIntAnnotation(read, ing)
|
||||
if err != nil {
|
||||
rt = cfg.ProxyReadTimeout
|
||||
}
|
||||
|
||||
bs, err := parser.GetStringAnnotation(bufferSize, ing)
|
||||
if err != nil || bs == "" {
|
||||
bs = cfg.ProxyBufferSize
|
||||
}
|
||||
|
||||
return &Configuration{ct, st, rt, bs}
|
||||
}
|
90
core/pkg/ingress/annotations/proxy/main_test.go
Normal file
90
core/pkg/ingress/annotations/proxy/main_test.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
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 proxy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
)
|
||||
|
||||
func buildIngress() *extensions.Ingress {
|
||||
defaultBackend := extensions.IngressBackend{
|
||||
ServiceName: "default-backend",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
}
|
||||
|
||||
return &extensions.Ingress{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: extensions.IngressSpec{
|
||||
Backend: &extensions.IngressBackend{
|
||||
ServiceName: "default-backend",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
},
|
||||
Rules: []extensions.IngressRule{
|
||||
{
|
||||
Host: "foo.bar.com",
|
||||
IngressRuleValue: extensions.IngressRuleValue{
|
||||
HTTP: &extensions.HTTPIngressRuleValue{
|
||||
Paths: []extensions.HTTPIngressPath{
|
||||
{
|
||||
Path: "/foo",
|
||||
Backend: defaultBackend,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressHealthCheck(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[connect] = "1"
|
||||
data[send] = "2"
|
||||
data[read] = "3"
|
||||
data[bufferSize] = "1k"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
cfg := defaults.Backend{UpstreamFailTimeout: 1}
|
||||
|
||||
p := ParseAnnotations(cfg, ing)
|
||||
|
||||
if p.ConnectTimeout != 1 {
|
||||
t.Errorf("Expected 1 as connect-timeout but returned %v", p.ConnectTimeout)
|
||||
}
|
||||
if p.SendTimeout != 2 {
|
||||
t.Errorf("Expected 2 as send-timeout but returned %v", p.SendTimeout)
|
||||
}
|
||||
if p.ReadTimeout != 3 {
|
||||
t.Errorf("Expected 3 as read-timeout but returned %v", p.ReadTimeout)
|
||||
}
|
||||
if p.BufferSize != "1k" {
|
||||
t.Errorf("Expected 1k as buffer-size but returned %v", p.BufferSize)
|
||||
}
|
||||
}
|
|
@ -19,7 +19,8 @@ package ratelimit
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
@ -39,10 +40,6 @@ const (
|
|||
var (
|
||||
// ErrInvalidRateLimit is returned when the annotation caontains invalid values
|
||||
ErrInvalidRateLimit = errors.New("invalid rate limit value. Must be > 0")
|
||||
|
||||
// ErrMissingAnnotations is returned when the ingress rule
|
||||
// does not contains annotations related with rate limit
|
||||
ErrMissingAnnotations = errors.New("no annotations present")
|
||||
)
|
||||
|
||||
// RateLimit returns rate limit configuration for an Ingress rule
|
||||
|
@ -66,39 +63,15 @@ type Zone struct {
|
|||
SharedSize int
|
||||
}
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
func (a ingAnnotations) limitIP() int {
|
||||
val, ok := a[limitIP]
|
||||
if ok {
|
||||
if i, err := strconv.Atoi(val); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (a ingAnnotations) limitRPS() int {
|
||||
val, ok := a[limitRPS]
|
||||
if ok {
|
||||
if i, err := strconv.Atoi(val); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to rewrite the defined paths
|
||||
func ParseAnnotations(ing *extensions.Ingress) (*RateLimit, error) {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return &RateLimit{}, ErrMissingAnnotations
|
||||
return &RateLimit{}, parser.ErrMissingAnnotations
|
||||
}
|
||||
|
||||
rps := ingAnnotations(ing.GetAnnotations()).limitRPS()
|
||||
conn := ingAnnotations(ing.GetAnnotations()).limitIP()
|
||||
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
|
||||
conn, _ := parser.GetIntAnnotation(limitIP, ing)
|
||||
|
||||
if rps == 0 && conn == 0 {
|
||||
return &RateLimit{
|
|
@ -59,35 +59,6 @@ func buildIngress() *extensions.Ingress {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
lip := ingAnnotations(ing.GetAnnotations()).limitIP()
|
||||
if lip != 0 {
|
||||
t.Errorf("Expected 0 in limit by ip but %v was returned", lip)
|
||||
}
|
||||
|
||||
lrps := ingAnnotations(ing.GetAnnotations()).limitRPS()
|
||||
if lrps != 0 {
|
||||
t.Errorf("Expected 0 in limit by rps but %v was returend", lrps)
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
data[limitIP] = "5"
|
||||
data[limitRPS] = "100"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
lip = ingAnnotations(ing.GetAnnotations()).limitIP()
|
||||
if lip != 5 {
|
||||
t.Errorf("Expected 5 in limit by ip but %v was returend", lip)
|
||||
}
|
||||
|
||||
lrps = ingAnnotations(ing.GetAnnotations()).limitRPS()
|
||||
if lrps != 100 {
|
||||
t.Errorf("Expected 100 in limit by rps but %v was returend", lrps)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithoutAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
_, err := ParseAnnotations(ing)
|
|
@ -18,11 +18,11 @@ package rewrite
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -38,69 +38,24 @@ type Redirect struct {
|
|||
// AddBaseURL indicates if is required to add a base tag in the head
|
||||
// of the responses from the upstream servers
|
||||
AddBaseURL bool
|
||||
// Should indicates if the location section should be accessible SSL only
|
||||
// SSLRedirect indicates if the location section is accessible SSL only
|
||||
SSLRedirect bool
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrMissingSSLRedirect returned error when the ingress does not contains the
|
||||
// ssl-redirect annotation
|
||||
ErrMissingSSLRedirect = errors.New("ssl-redirect annotations is missing")
|
||||
|
||||
// ErrInvalidBool gets returned when the str value is not convertible to a bool
|
||||
ErrInvalidBool = errors.New("ssl-redirect annotations has invalid value")
|
||||
)
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
func (a ingAnnotations) addBaseURL() bool {
|
||||
val, ok := a[addBaseURL]
|
||||
if ok {
|
||||
if b, err := strconv.ParseBool(val); err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a ingAnnotations) rewriteTo() string {
|
||||
val, ok := a[rewriteTo]
|
||||
if ok {
|
||||
return val
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a ingAnnotations) sslRedirect() (bool, error) {
|
||||
val, ok := a[sslRedirect]
|
||||
if !ok {
|
||||
return false, ErrMissingSSLRedirect
|
||||
}
|
||||
|
||||
sr, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return false, ErrInvalidBool
|
||||
}
|
||||
|
||||
return sr, nil
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to rewrite the defined paths
|
||||
func ParseAnnotations(cfg config.Configuration, ing *extensions.Ingress) (*Redirect, error) {
|
||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (*Redirect, error) {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return &Redirect{}, errors.New("no annotations present")
|
||||
}
|
||||
|
||||
annotations := ingAnnotations(ing.GetAnnotations())
|
||||
|
||||
sslRe, err := annotations.sslRedirect()
|
||||
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
|
||||
if err != nil {
|
||||
sslRe = cfg.SSLRedirect
|
||||
}
|
||||
|
||||
rt := annotations.rewriteTo()
|
||||
abu := annotations.addBaseURL()
|
||||
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
|
||||
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
|
||||
return &Redirect{
|
||||
Target: rt,
|
||||
AddBaseURL: abu,
|
|
@ -23,7 +23,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -65,38 +65,9 @@ func buildIngress() *extensions.Ingress {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
r := ingAnnotations(ing.GetAnnotations()).rewriteTo()
|
||||
if r != "" {
|
||||
t.Error("Expected no redirect")
|
||||
}
|
||||
|
||||
f := ingAnnotations(ing.GetAnnotations()).addBaseURL()
|
||||
if f {
|
||||
t.Errorf("Expected false in add-base-url but %v was returend", f)
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
data[rewriteTo] = defRoute
|
||||
data[addBaseURL] = "true"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
r = ingAnnotations(ing.GetAnnotations()).rewriteTo()
|
||||
if r != defRoute {
|
||||
t.Errorf("Expected %v in rewrite but %v was returend", defRoute, r)
|
||||
}
|
||||
|
||||
f = ingAnnotations(ing.GetAnnotations()).addBaseURL()
|
||||
if !f {
|
||||
t.Errorf("Expected true in add-base-url but %v was returend", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithoutAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
_, err := ParseAnnotations(config.NewDefault(), ing)
|
||||
_, err := ParseAnnotations(defaults.Backend{}, ing)
|
||||
if err == nil {
|
||||
t.Error("Expected error with ingress without annotations")
|
||||
}
|
||||
|
@ -109,7 +80,7 @@ func TestRedirect(t *testing.T) {
|
|||
data[rewriteTo] = defRoute
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
redirect, err := ParseAnnotations(config.NewDefault(), ing)
|
||||
redirect, err := ParseAnnotations(defaults.Backend{}, ing)
|
||||
if err != nil {
|
||||
t.Errorf("Uxpected error with ingress: %v", err)
|
||||
}
|
||||
|
@ -122,7 +93,7 @@ func TestRedirect(t *testing.T) {
|
|||
func TestSSLRedirect(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
cfg := config.Configuration{SSLRedirect: true}
|
||||
cfg := defaults.Backend{SSLRedirect: true}
|
||||
|
||||
data := map[string]string{}
|
||||
|
|
@ -17,8 +17,7 @@ limitations under the License.
|
|||
package secureupstream
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
@ -27,24 +26,8 @@ const (
|
|||
secureUpstream = "ingress.kubernetes.io/secure-backends"
|
||||
)
|
||||
|
||||
type ingAnnotations map[string]string
|
||||
|
||||
func (a ingAnnotations) secureUpstream() bool {
|
||||
val, ok := a[secureUpstream]
|
||||
if ok {
|
||||
if b, err := strconv.ParseBool(val); err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to indicate if the upstream servers should use SSL
|
||||
func ParseAnnotations(ing *extensions.Ingress) (bool, error) {
|
||||
if ing.GetAnnotations() == nil {
|
||||
return false, errors.New("no annotations present")
|
||||
}
|
||||
|
||||
return ingAnnotations(ing.GetAnnotations()).secureUpstream(), nil
|
||||
return parser.GetBoolAnnotation(secureUpstream, ing)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue