Merge branch 'master' of https://github.com/kubernetes/ingress-nginx into proxyssl
This commit is contained in:
commit
65b9e2c574
391 changed files with 23957 additions and 20447 deletions
26
.gitignore
vendored
26
.gitignore
vendored
|
@ -25,15 +25,6 @@
|
|||
Session.vim
|
||||
.netrwhist
|
||||
|
||||
# coverage artifacts
|
||||
.coverprofile
|
||||
/gover.coverprofile
|
||||
|
||||
e2e-tests
|
||||
|
||||
coverage.txt
|
||||
test/e2e/e2e\.test
|
||||
|
||||
# mkdocs
|
||||
site
|
||||
|
||||
|
@ -41,11 +32,18 @@ site
|
|||
gh-pages
|
||||
|
||||
# Docker-based builds
|
||||
/test/binaries
|
||||
/.env
|
||||
/.gocache/
|
||||
/bin/
|
||||
test/binaries
|
||||
|
||||
test/e2e-image/wait-for-nginx\.sh
|
||||
# coverage artifacts
|
||||
.coverprofile
|
||||
gover.coverprofile
|
||||
|
||||
e2e-tests
|
||||
coverage.txt
|
||||
test/e2e/e2e\.test
|
||||
.env
|
||||
.gocache/
|
||||
bin
|
||||
test/e2e-image/wait-for-nginx.sh
|
||||
.cache
|
||||
cover.out
|
||||
|
|
12
Changelog.md
12
Changelog.md
|
@ -1,5 +1,13 @@
|
|||
# Changelog
|
||||
|
||||
### 0.25.1
|
||||
|
||||
**Image:** `quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.1`
|
||||
|
||||
_Changes:_
|
||||
|
||||
- [X] [#4440](https://github.com/kubernetes/ingress-nginx/pull/4440) Fixes for CVE-2018-16843, CVE-2018-16844, CVE-2019-9511, CVE-2019-9513, and CVE-2019-9516
|
||||
|
||||
### 0.25.0
|
||||
|
||||
**Image:** `quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0`
|
||||
|
@ -25,7 +33,7 @@ _Non-functional improvements:_
|
|||
|
||||
_Changes:_
|
||||
|
||||
- [X] [#3506](https://github.com/kubernetes/ingress-nginx/pull/3506) Improve the external authorization concept from opt-in to secure-by-default
|
||||
- [X] [#3506](https://github.com/kubernetes/ingress-nginx/pull/3506) Improve the external authorization concept from opt-in to secure-by-default
|
||||
- [X] [#3802](https://github.com/kubernetes/ingress-nginx/pull/3802) Add a validating webhook for ingress sanity check
|
||||
- [X] [#3803](https://github.com/kubernetes/ingress-nginx/pull/3803) use nkeys for counting lua table elements
|
||||
- [X] [#3852](https://github.com/kubernetes/ingress-nginx/pull/3852) Enable arm again
|
||||
|
@ -414,7 +422,7 @@ _Changes:_
|
|||
- [X] [#3655](https://github.com/kubernetes/ingress-nginx/pull/3655) Remove flag sort-backends
|
||||
- [X] [#3656](https://github.com/kubernetes/ingress-nginx/pull/3656) Change default value of flag for ssl chain completion
|
||||
- [X] [#3660](https://github.com/kubernetes/ingress-nginx/pull/3660) Revert max-worker-connections default value
|
||||
- [X] [#3664](https://github.com/kubernetes/ingress-nginx/pull/3664) Fix invalid validation creating prometheus valid host values
|
||||
- [X] [#3664](https://github.com/kubernetes/ingress-nginx/pull/3664) Fix invalid validation creating prometheus valid host values
|
||||
|
||||
_Documentation:_
|
||||
|
||||
|
|
4
Makefile
4
Makefile
|
@ -16,7 +16,7 @@
|
|||
all: all-container
|
||||
|
||||
# Use the 0.0 tag for testing, it shouldn't clobber any release builds
|
||||
TAG ?= 0.25.0
|
||||
TAG ?= 0.25.1
|
||||
REGISTRY ?= quay.io/kubernetes-ingress-controller
|
||||
DOCKER ?= docker
|
||||
SED_I ?= sed -i
|
||||
|
@ -73,7 +73,7 @@ export E2E_CHECK_LEAKS
|
|||
export SLOW_E2E_THRESHOLD
|
||||
|
||||
# Set default base image dynamically for each arch
|
||||
BASEIMAGE?=quay.io/kubernetes-ingress-controller/nginx-$(ARCH):0.90
|
||||
BASEIMAGE?=quay.io/kubernetes-ingress-controller/nginx-$(ARCH):0.91
|
||||
|
||||
ifeq ($(ARCH),arm)
|
||||
QEMUARCH=arm
|
||||
|
|
|
@ -27,6 +27,9 @@ if [ -z "${PKG}" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
export CGO_ENABLED=1
|
||||
export GODEBUG=netdns=go+2
|
||||
|
||||
rm -rf coverage.txt
|
||||
for d in $(go list "${PKG}/..." | grep -v vendor | grep -v '/test/e2e' | grep -v images); do
|
||||
t=$(date +%s);
|
||||
|
|
|
@ -22,7 +22,6 @@ set -o errexit
|
|||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
SKIP_MINIKUBE_START=${SKIP_MINIKUBE_START:-}
|
||||
NAMESPACE="${NAMESPACE:-ingress-nginx}"
|
||||
echo "NAMESPACE is set to ${NAMESPACE}"
|
||||
|
||||
|
@ -34,18 +33,15 @@ export REGISTRY=${REGISTRY:-ingress-controller}
|
|||
|
||||
DEV_IMAGE=${REGISTRY}/nginx-ingress-controller:${TAG}
|
||||
|
||||
if [ -z "${SKIP_MINIKUBE_START}" ]; then
|
||||
test "$(minikube status | grep -c Running) -ge 2 && $(minikube status | grep -q 'Correctly Configured')" || minikube start \
|
||||
--extra-config=kubelet.sync-frequency=1s \
|
||||
--extra-config=apiserver.authorization-mode=RBAC
|
||||
test $(minikube status | grep -c Running) -ge 2 && $(minikube status | grep -q 'Correctly Configured') || minikube start \
|
||||
--extra-config=kubelet.sync-frequency=1s \
|
||||
--extra-config=apiserver.authorization-mode=RBAC
|
||||
|
||||
eval $(minikube docker-env --shell bash)
|
||||
fi
|
||||
eval $(minikube docker-env --shell bash)
|
||||
|
||||
echo "[dev-env] building container"
|
||||
make build container
|
||||
|
||||
docker save "${DEV_IMAGE}" | (eval $(minikube docker-env --shell bash) && docker load) || true
|
||||
docker tag "${REGISTRY}/nginx-ingress-controller-${ARCH}:${TAG}" ${DEV_IMAGE}
|
||||
|
||||
# kubectl >= 1.14 includes Kustomize via "apply -k". Makes it easier to use on Linux as well, assuming kubectl installed
|
||||
KUBE_CLIENT_VERSION=$(kubectl version --client --short | awk '{print $3}' | cut -d. -f2) || true
|
||||
|
@ -60,6 +56,8 @@ if ! kubectl get namespace "${NAMESPACE}"; then
|
|||
kubectl create namespace "${NAMESPACE}"
|
||||
fi
|
||||
|
||||
kubectl get deploy nginx-ingress-controller -n ${NAMESPACE} && kubectl delete deploy nginx-ingress-controller -n ${NAMESPACE}
|
||||
|
||||
ROOT=./deploy/minikube
|
||||
|
||||
if [[ ${KUBE_CLIENT_VERSION} -lt 14 ]]; then
|
||||
|
@ -71,8 +69,7 @@ if [[ ${KUBE_CLIENT_VERSION} -lt 14 ]]; then
|
|||
echo "[dev-env] deploying NGINX Ingress controller in namespace $NAMESPACE"
|
||||
kustomize build $ROOT | kubectl apply -f -
|
||||
else
|
||||
sed -i "\\|^namespace:|c \\namespace: ${NAMESPACE}" "${ROOT}/kustomization.yaml"
|
||||
sed -i "\\|^- name: quay.io|c \\- name: quay.io/kubernetes-ingress-controller/nginx-ingress-controller=${DEV_IMAGE}" "${ROOT}/kustomization.yaml"
|
||||
sed -i -e "s|^namespace: .*|namespace: ${NAMESPACE}|g" "${ROOT}/kustomization.yaml"
|
||||
|
||||
echo "[dev-env] deploying NGINX Ingress controller in namespace $NAMESPACE"
|
||||
kubectl apply -k "${ROOT}"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM alpine:3.9
|
||||
FROM alpine:3.10
|
||||
|
||||
RUN apk update && apk add --no-cache \
|
||||
bash \
|
||||
|
|
|
@ -63,7 +63,8 @@ kubectl create clusterrolebinding permissive-binding \
|
|||
--user=kubelet \
|
||||
--serviceaccount=default:ingress-nginx-e2e || true
|
||||
|
||||
until kubectl get secret | grep -q ^ingress-nginx-e2e-token; do \
|
||||
echo -e "${BGREEN}Waiting service account...${NC}"; \
|
||||
until kubectl get secret | grep -q -e ^ingress-nginx-e2e-token; do \
|
||||
echo -e "waiting for api token"; \
|
||||
sleep 3; \
|
||||
done
|
||||
|
|
|
@ -30,6 +30,7 @@ declare -a mandatory
|
|||
mandatory=(
|
||||
IMAGE
|
||||
ARCH
|
||||
TAG
|
||||
)
|
||||
|
||||
missing=false
|
||||
|
@ -56,12 +57,12 @@ trap cleanup EXIT
|
|||
|
||||
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||
|
||||
# the ingress controller needs this two variables. To avoid the
|
||||
# the ingress controller needs this two variables. To avoid the
|
||||
# creation of any object in the cluster we use invalid names.
|
||||
POD_NAMESPACE="invalid-namespace"
|
||||
POD_NAME="invalid-namespace"
|
||||
|
||||
export TAG=local
|
||||
export TAG
|
||||
export IMAGE
|
||||
|
||||
if [[ "${ARCH}" != "amd64" ]]; then
|
||||
|
@ -69,8 +70,14 @@ if [[ "${ARCH}" != "amd64" ]]; then
|
|||
make -C "${KUBE_ROOT}" register-qemu
|
||||
fi
|
||||
|
||||
echo -e "${BGREEN}Building ingress controller image${NC}"
|
||||
make -C "${KUBE_ROOT}" build "sub-container-${ARCH}"
|
||||
USE_EXISTING_IMAGE=${USE_EXISTING_IMAGE:-false}
|
||||
if [[ "${USE_EXISTING_IMAGE}" == "true" ]]; then
|
||||
echo -e "${BGREEN}Downloading ingress controller image${NC}"
|
||||
docker pull "${IMAGE}-${ARCH}:${TAG}"
|
||||
else
|
||||
echo -e "${BGREEN}Building ingress controller image${NC}"
|
||||
make -C "${KUBE_ROOT}" build "sub-container-${ARCH}"
|
||||
fi
|
||||
|
||||
CONTEXT=$(kubectl config current-context)
|
||||
|
||||
|
@ -103,7 +110,7 @@ docker run \
|
|||
-v "${SSL_VOLUME}:/etc/ingress-controller/ssl/" \
|
||||
-v "${HOME}/.kube:${HOME}/.kube:ro" \
|
||||
${MINIKUBE_VOLUME} \
|
||||
"${IMAGE}:${TAG}" /nginx-ingress-controller \
|
||||
"${IMAGE}-${ARCH}:${TAG}" /nginx-ingress-controller \
|
||||
--update-status=false \
|
||||
--v=2 \
|
||||
--apiserver-host=http://0.0.0.0:8001 \
|
||||
|
|
|
@ -29,4 +29,5 @@ resty \
|
|||
--shdict "certificate_data 16M" \
|
||||
--shdict "balancer_ewma 1M" \
|
||||
--shdict "balancer_ewma_last_touched_at 1M" \
|
||||
--shdict "balancer_ewma_locks 512k" \
|
||||
./rootfs/etc/nginx/lua/test/run.lua ${BUSTED_ARGS} ./rootfs/etc/nginx/lua/test/
|
||||
|
|
|
@ -28,5 +28,9 @@ if [ -z "${PKG}" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
go test -v -race -tags "cgo" \
|
||||
# enabled to use host dns resolver
|
||||
export CGO_ENABLED=1
|
||||
export GODEBUG=netdns=go+2
|
||||
|
||||
go test -v -race \
|
||||
$(go list "${PKG}/..." | grep -v vendor | grep -v '/test/e2e' | grep -v images | grep -v "docs/examples")
|
||||
|
|
|
@ -141,7 +141,7 @@ extension for this to succeed.`)
|
|||
`Customized address to set as the load-balancer status of Ingress objects this controller satisfies.
|
||||
Requires the update-status parameter.`)
|
||||
|
||||
enableDynamicCertificates = flags.Bool("enable-dynamic-certificates", true,
|
||||
_ = flags.Bool("enable-dynamic-certificates", true,
|
||||
`Dynamically update SSL certificates instead of reloading NGINX. Feature backed by OpenResty Lua libraries.`)
|
||||
|
||||
enableMetrics = flags.Bool("enable-metrics", true,
|
||||
|
@ -171,6 +171,8 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
|
|||
flags.MarkDeprecated("status-port", `The status port is a unix socket now.`)
|
||||
flags.MarkDeprecated("force-namespace-isolation", `This flag doesn't do anything.`)
|
||||
|
||||
flags.MarkDeprecated("enable-dynamic-certificates", `Only dynamic mode is supported`)
|
||||
|
||||
flag.Set("logtostderr", "true")
|
||||
|
||||
flags.AddGoFlagSet(flag.CommandLine)
|
||||
|
@ -232,7 +234,6 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
|
|||
}
|
||||
|
||||
ngx_config.EnableSSLChainCompletion = *enableSSLChainCompletion
|
||||
ngx_config.EnableDynamicCertificates = *enableDynamicCertificates
|
||||
|
||||
config := &controller.Configuration{
|
||||
APIServerHost: *apiserverHost,
|
||||
|
|
|
@ -39,7 +39,6 @@ import (
|
|||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/ingress-nginx/internal/ingress/controller"
|
||||
"k8s.io/ingress-nginx/internal/ingress/metric"
|
||||
"k8s.io/ingress-nginx/internal/k8s"
|
||||
|
@ -63,13 +62,6 @@ func main() {
|
|||
klog.Fatal(err)
|
||||
}
|
||||
|
||||
nginxVersion()
|
||||
|
||||
fs, err := file.NewLocalFS()
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
|
||||
kubeClient, err := createApiserverClient(conf.APIServerHost, conf.KubeConfigFile)
|
||||
if err != nil {
|
||||
handleFatalInitError(err)
|
||||
|
@ -98,8 +90,8 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
conf.FakeCertificate = ssl.GetFakeSSLCert(fs)
|
||||
klog.Infof("Created fake certificate with PemFileName: %v", conf.FakeCertificate.PemFileName)
|
||||
conf.FakeCertificate = ssl.GetFakeSSLCert()
|
||||
klog.Infof("SSL fake certificate created %v", conf.FakeCertificate.PemFileName)
|
||||
|
||||
k8s.IsNetworkingIngressAvailable = k8s.NetworkingIngressAvailable(kubeClient)
|
||||
if !k8s.IsNetworkingIngressAvailable {
|
||||
|
@ -125,7 +117,7 @@ func main() {
|
|||
}
|
||||
mc.Start()
|
||||
|
||||
ngx := controller.NewNGINXController(conf, mc, fs)
|
||||
ngx := controller.NewNGINXController(conf, mc)
|
||||
go handleSigterm(ngx, func(code int) {
|
||||
os.Exit(code)
|
||||
})
|
||||
|
|
|
@ -19,6 +19,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -28,8 +29,8 @@ import (
|
|||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/ingress-nginx/internal/ingress/controller"
|
||||
"k8s.io/ingress-nginx/internal/nginx"
|
||||
)
|
||||
|
||||
func TestCreateApiserverClient(t *testing.T) {
|
||||
|
@ -39,6 +40,15 @@ func TestCreateApiserverClient(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// the default value of nginx.TemplatePath assumes the template exists in
|
||||
// the root filesystem and not in the rootfs directory
|
||||
path, err := filepath.Abs(filepath.Join("../../rootfs/", nginx.TemplatePath))
|
||||
if err == nil {
|
||||
nginx.TemplatePath = path
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSigterm(t *testing.T) {
|
||||
clientSet := fake.NewSimpleClientset()
|
||||
|
||||
|
@ -77,12 +87,7 @@ func TestHandleSigterm(t *testing.T) {
|
|||
}
|
||||
conf.Client = clientSet
|
||||
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ngx := controller.NewNGINXController(conf, nil, fs)
|
||||
ngx := controller.NewNGINXController(conf, nil)
|
||||
|
||||
go handleSigterm(ngx, func(code int) {
|
||||
if code != 1 {
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 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/exec"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
func nginxVersion() {
|
||||
flag := "-v"
|
||||
|
||||
if klog.V(2) {
|
||||
flag = "-V"
|
||||
}
|
||||
|
||||
cmd := exec.Command("nginx", flag)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Run()
|
||||
}
|
|
@ -95,6 +95,21 @@ $ export REGISTRY=<your-docker-registry>
|
|||
|
||||
To find the registry simply run: `docker system info | grep Registry`
|
||||
|
||||
### Building the e2e test image
|
||||
|
||||
The e2e test image can also be built through the Makefile.
|
||||
|
||||
```console
|
||||
$ make e2e-test-image
|
||||
```
|
||||
|
||||
You can then make this image available on your minikube host by exporting the image and loading it with the minikube docker context:
|
||||
|
||||
```console
|
||||
$ docker save nginx-ingress-controller:e2e | (eval $(minikube docker-env) && docker load)
|
||||
```
|
||||
|
||||
|
||||
### Nginx Controller
|
||||
|
||||
Build a raw server binary
|
||||
|
@ -137,6 +152,8 @@ $ cd $GOPATH/src/k8s.io/ingress-nginx
|
|||
$ make e2e-test
|
||||
```
|
||||
|
||||
NOTE: if your e2e pod keeps hanging in an ImagePullBackoff, make sure you've made your e2e nginx-ingress-controller image available to minikube as explained in [Building the e2e test image](./Building the e2e test image)
|
||||
|
||||
To run unit-tests for lua code locally, run:
|
||||
|
||||
```console
|
||||
|
|
63
docs/enhancements/20190724-only-dynamic-ssl.md
Normal file
63
docs/enhancements/20190724-only-dynamic-ssl.md
Normal file
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: Remove static SSL configuration mode
|
||||
authors:
|
||||
- "@aledbf"
|
||||
reviewers:
|
||||
- "@ElvinEfendi"
|
||||
approvers:
|
||||
- "@ElvinEfendi"
|
||||
editor: TBD
|
||||
creation-date: 2019-07-24
|
||||
last-updated: 2019-07-24
|
||||
status: implementable
|
||||
see-also:
|
||||
replaces:
|
||||
superseded-by:
|
||||
---
|
||||
|
||||
# Remove static SSL configuration mode
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- toc -->
|
||||
- [Summary](#summary)
|
||||
- [Motivation](#motivation)
|
||||
- [Goals](#goals)
|
||||
- [Non-Goals](#non-goals)
|
||||
- [Proposal](#proposal)
|
||||
- [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)
|
||||
- [Drawbacks](#drawbacks)
|
||||
- [Alternatives](#alternatives)
|
||||
<!-- /toc -->
|
||||
|
||||
## Summary
|
||||
|
||||
Since release [0.19.0](https://github.com/kubernetes/ingress-nginx/releases/tag/nginx-0.19.0) is possible to configure SSL certificates without the need of NGINX reloads (thanks to lua) and after release [0.24.0](https://github.com/kubernetes/ingress-nginx/releases/tag/nginx-0.19.0) the default enabled mode is dynamic.
|
||||
|
||||
## Motivation
|
||||
|
||||
The static configuration implies reloads, something that affects the majority of the users.
|
||||
|
||||
### Goals
|
||||
|
||||
- Deprecation of the flag `--enable-dynamic-certificates`.
|
||||
- Cleanup of the codebase.
|
||||
|
||||
### Non-Goals
|
||||
|
||||
- Features related to certificate authentication are not changed in any way.
|
||||
|
||||
## Proposal
|
||||
|
||||
- Remove static SSL configuration
|
||||
|
||||
### Implementation Details/Notes/Constraints
|
||||
|
||||
- Deprecate the flag Move the directives `ssl_certificate` and `ssl_certificate_key` from each server block to the `http` section. These settings are required to avoid NGINX errors in the logs.
|
||||
- Remove any action of the flag `--enable-dynamic-certificates`
|
||||
|
||||
## Drawbacks
|
||||
|
||||
## Alternatives
|
||||
|
||||
Keep both implementations
|
51
docs/enhancements/20190815-zone-aware-routing.md
Normal file
51
docs/enhancements/20190815-zone-aware-routing.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
title: Availability zone aware routing
|
||||
authors:
|
||||
- "@ElvinEfendi"
|
||||
reviewers:
|
||||
- "@aledbf"
|
||||
approvers:
|
||||
- "@aledbf"
|
||||
editor: TBD
|
||||
creation-date: 2019-08-15
|
||||
last-updated: 2019-08-15
|
||||
status: provisional
|
||||
---
|
||||
|
||||
# Availability zone aware routing
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- toc -->
|
||||
- [Summary](#summary)
|
||||
- [Motivation](#motivation)
|
||||
- [Goals](#goals)
|
||||
- [Non-Goals](#non-goals)
|
||||
<!-- /toc -->
|
||||
|
||||
## Summary
|
||||
|
||||
Teach ingress-nginx about availability zones where endpoints are running in. This way ingress-nginx pod will do its best to proxy to zone-local endpoint.
|
||||
|
||||
## Motivation
|
||||
|
||||
When users run their services across multiple availability zones they usually pay for egress traffic between zones. Providers such as GCP, AWS charges money for that.
|
||||
ingress-nginx when picking an endpoint to route request to does not consider whether the endpoint is in different zone or the same one. That means it's at least equally likely
|
||||
that it will pick an endpoint from another zone and proxy the request to it. In this situation response from the endpoint to ingress-nginx pod is considered as
|
||||
inter zone traffic and costs money.
|
||||
|
||||
|
||||
At the time of this writing GCP charges $0.01 per GB of inter zone egress traffic according to https://cloud.google.com/compute/network-pricing.
|
||||
According to https://datapath.io/resources/blog/what-are-aws-data-transfer-costs-and-how-to-minimize-them/ AWS also charges the same amount of money sa GCP for cross zone, egress traffic.
|
||||
|
||||
This can be a lot of money depending on once's traffic. By teaching ingress-nginx about zones we can eliminate or at least decrease this cost.
|
||||
|
||||
Arguably inter-zone network latency should also be better than cross zone.
|
||||
|
||||
### Goals
|
||||
|
||||
* Given a regional cluster running ingress-nginx, ingress-nginx should do best effort to pick zone-local endpoint when proxying
|
||||
|
||||
### Non-Goals
|
||||
|
||||
* -
|
28
docs/enhancements/README.md
Normal file
28
docs/enhancements/README.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Kubernetes Enhancement Proposals (KEPs)
|
||||
|
||||
A Kubernetes Enhancement Proposal (KEP) is a way to propose, communicate and coordinate on new efforts for the Kubernetes project. For this reason, the `ingress-nginx` project is adopting it.
|
||||
|
||||
## Quick start for the KEP process
|
||||
|
||||
Follow the process outlined in the [KEP template](YYYYMMDD-kep-template.md)
|
||||
|
||||
### Do I have to use the KEP process?
|
||||
|
||||
No... but we hope that you will.
|
||||
Over time having a rich set of KEPs in one place will make it easier for people to track what is going on in the community and find a structured historic record.
|
||||
|
||||
KEPs are only required when the changes are wide ranging and impact most of the project.
|
||||
|
||||
### Why would I want to use the KEP process?
|
||||
|
||||
Our aim with KEPs is to clearly communicate new efforts to the Kubernetes contributor community.
|
||||
As such, we want to build a well curated set of clear proposals in a common format with useful metadata.
|
||||
|
||||
Benefits to KEP users (in the limit):
|
||||
|
||||
* Exposure on a kubernetes blessed web site that is findable via web search engines.
|
||||
* Cross indexing of KEPs so that users can find connections and the current status of any KEP.
|
||||
* A clear process with approvers and reviewers for making decisions.
|
||||
This will lead to more structured decisions that stick as there is a discoverable record around the decisions.
|
||||
|
||||
We are inspired by IETF RFCs, Python PEPs, and Rust RFCs.
|
182
docs/enhancements/YYYYMMDD-kep-template.md
Normal file
182
docs/enhancements/YYYYMMDD-kep-template.md
Normal file
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
title: KEP Template
|
||||
authors:
|
||||
- "@janedoe"
|
||||
reviewers:
|
||||
- TBD
|
||||
- "@alicedoe"
|
||||
approvers:
|
||||
- TBD
|
||||
- "@oscardoe"
|
||||
editor: TBD
|
||||
creation-date: yyyy-mm-dd
|
||||
last-updated: yyyy-mm-dd
|
||||
status: provisional|implementable|implemented|deferred|rejected|withdrawn|replaced
|
||||
see-also:
|
||||
- "/docs/enhancements/20190101-we-heard-you-like-keps.md"
|
||||
- "/docs/enhancements/20190102-everyone-gets-a-kep.md"
|
||||
replaces:
|
||||
- "/docs/enhancements/20181231-replaced-kep.md"
|
||||
superseded-by:
|
||||
- "/docs/enhancements/20190104-superceding-kep.md"
|
||||
---
|
||||
|
||||
# Title
|
||||
|
||||
This is the title of the KEP.
|
||||
Keep it simple and descriptive.
|
||||
A good title can help communicate what the KEP is and should be considered as part of any review.
|
||||
|
||||
The title should be lowercased and spaces/punctuation should be replaced with `-`.
|
||||
|
||||
To get started with this template:
|
||||
|
||||
1. **Make a copy of this template.**
|
||||
Create a copy of this template and name it `YYYYMMDD-my-title.md`, where `YYYYMMDD` is the date the KEP was first drafted.
|
||||
1. **Fill out the "overview" sections.**
|
||||
This includes the Summary and Motivation sections.
|
||||
These should be easy if you've preflighted the idea of the KEP in an issue.
|
||||
1. **Create a PR.**
|
||||
Assign it to folks that are sponsoring this process.
|
||||
1. **Create an issue**
|
||||
When filing an enhancement tracking issue, please ensure to complete all fields in the template.
|
||||
1. **Merge early.**
|
||||
Avoid getting hung up on specific details and instead aim to get the goal of the KEP merged quickly.
|
||||
The best way to do this is to just start with the "Overview" sections and fill out details incrementally in follow on PRs.
|
||||
View anything marked as a `provisional` as a working document and subject to change.
|
||||
Aim for single topic PRs to keep discussions focused.
|
||||
If you disagree with what is already in a document, open a new PR with suggested changes.
|
||||
|
||||
The canonical place for the latest set of instructions (and the likely source of this file) is [here](/keps/YYYYMMDD-kep-template.md).
|
||||
|
||||
The `Metadata` section above is intended to support the creation of tooling around the KEP process.
|
||||
This will be a YAML section that is fenced as a code block.
|
||||
See the KEP process for details on each of these items.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
A table of contents is helpful for quickly jumping to sections of a KEP and for highlighting any additional information provided beyond the standard KEP template.
|
||||
|
||||
Ensure the TOC is wrapped with <code><!-- toc --&rt;<!-- /toc --&rt;</code> tags, and then generate with `hack/update-toc.sh`.
|
||||
|
||||
<!-- toc -->
|
||||
- [Summary](#summary)
|
||||
- [Motivation](#motivation)
|
||||
- [Goals](#goals)
|
||||
- [Non-Goals](#non-goals)
|
||||
- [Proposal](#proposal)
|
||||
- [User Stories [optional]](#user-stories-optional)
|
||||
- [Story 1](#story-1)
|
||||
- [Story 2](#story-2)
|
||||
- [Implementation Details/Notes/Constraints [optional]](#implementation-detailsnotesconstraints-optional)
|
||||
- [Risks and Mitigations](#risks-and-mitigations)
|
||||
- [Design Details](#design-details)
|
||||
- [Test Plan](#test-plan)
|
||||
- [Removing a deprecated flag](#removing-a-deprecated-flag)
|
||||
- [Implementation History](#implementation-history)
|
||||
- [Drawbacks [optional]](#drawbacks-optional)
|
||||
- [Alternatives [optional]](#alternatives-optional)
|
||||
<!-- /toc -->
|
||||
|
||||
## Summary
|
||||
|
||||
The `Summary` section is incredibly important for producing high quality user-focused documentation such as release notes or a development roadmap.
|
||||
It should be possible to collect this information before implementation begins in order to avoid requiring implementors to split their attention between writing release notes and implementing the feature itself.
|
||||
|
||||
A good summary is probably at least a paragraph in length.
|
||||
|
||||
## Motivation
|
||||
|
||||
This section is for explicitly listing the motivation, goals and non-goals of this KEP.
|
||||
Describe why the change is important and the benefits to users.
|
||||
The motivation section can optionally provide links to [experience reports][] to demonstrate the interest in a KEP within the wider Kubernetes community.
|
||||
|
||||
[experience reports]: https://github.com/golang/go/wiki/ExperienceReports
|
||||
|
||||
### Goals
|
||||
|
||||
List the specific goals of the KEP.
|
||||
How will we know that this has succeeded?
|
||||
|
||||
### Non-Goals
|
||||
|
||||
What is out of scope for this KEP?
|
||||
Listing non-goals helps to focus discussion and make progress.
|
||||
|
||||
## Proposal
|
||||
|
||||
This is where we get down to the nitty gritty of what the proposal actually is.
|
||||
|
||||
### User Stories [optional]
|
||||
|
||||
Detail the things that people will be able to do if this KEP is implemented.
|
||||
Include as much detail as possible so that people can understand the "how" of the system.
|
||||
The goal here is to make this feel real for users without getting bogged down.
|
||||
|
||||
#### Story 1
|
||||
|
||||
#### Story 2
|
||||
|
||||
### Implementation Details/Notes/Constraints [optional]
|
||||
|
||||
What are the caveats to the implementation?
|
||||
What are some important details that didn't come across above.
|
||||
Go in to as much detail as necessary here.
|
||||
This might be a good place to talk about core concepts and how they releate.
|
||||
|
||||
### Risks and Mitigations
|
||||
|
||||
What are the risks of this proposal and how do we mitigate.
|
||||
Think broadly.
|
||||
For example, consider both security and how this will impact the larger kubernetes ecosystem.
|
||||
|
||||
How will security be reviewed and by whom?
|
||||
How will UX be reviewed and by whom?
|
||||
|
||||
Consider including folks that also work outside project.
|
||||
|
||||
## Design Details
|
||||
|
||||
### Test Plan
|
||||
|
||||
**Note:** *Section not required until targeted at a release.*
|
||||
|
||||
Consider the following in developing a test plan for this enhancement:
|
||||
|
||||
- Will there be e2e and integration tests, in addition to unit tests?
|
||||
- How will it be tested in isolation vs with other components?
|
||||
|
||||
No need to outline all of the test cases, just the general strategy.
|
||||
Anything that would count as tricky in the implementation and anything particularly challenging to test should be called out.
|
||||
|
||||
All code is expected to have adequate tests (eventually with coverage expectations).
|
||||
Please adhere to the [Kubernetes testing guidelines][testing-guidelines] when drafting this test plan.
|
||||
|
||||
[testing-guidelines]: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
|
||||
|
||||
#### Removing a deprecated flag
|
||||
|
||||
- Announce deprecation and support policy of the existing flag
|
||||
- Two versions passed since introducing the functionality which deprecates the flag (to address version skew)
|
||||
- Address feedback on usage/changed behavior, provided on GitHub issues
|
||||
- Deprecate the flag
|
||||
|
||||
## Implementation History
|
||||
|
||||
Major milestones in the life cycle of a KEP should be tracked in `Implementation History`.
|
||||
Major milestones might include
|
||||
|
||||
- the `Summary` and `Motivation` sections being merged signaling acceptance
|
||||
- the `Proposal` section being merged signaling agreement on a proposed design
|
||||
- the date implementation started
|
||||
- the first Kubernetes release where an initial version of the KEP was available
|
||||
- the version of Kubernetes where the KEP graduated to general availability
|
||||
- when the KEP was retired or superseded
|
||||
|
||||
## Drawbacks [optional]
|
||||
|
||||
Why should this KEP _not_ be implemented.
|
||||
|
||||
## Alternatives [optional]
|
||||
|
||||
Similar to the `Drawbacks` section the `Alternatives` section is used to highlight and record other possible approaches to delivering the value proposed by a KEP.
|
|
@ -31,7 +31,7 @@ metadata:
|
|||
|
||||
### Example: OAuth2 Proxy + Kubernetes-Dashboard
|
||||
|
||||
This example will show you how to deploy [`oauth2_proxy`](https://github.com/bitly/oauth2_proxy)
|
||||
This example will show you how to deploy [`oauth2_proxy`](https://github.com/pusher/oauth2_proxy)
|
||||
into a Kubernetes cluster and use it to protect the Kubernetes Dashboard using github as oAuth2 provider
|
||||
|
||||
#### Prepare
|
||||
|
@ -55,7 +55,7 @@ kubectl create -f https://raw.githubusercontent.com/kubernetes/kops/master/addon
|
|||
|
||||
- OAUTH2_PROXY_CLIENT_ID with the github `<Client ID>`
|
||||
- OAUTH2_PROXY_CLIENT_SECRET with the github `<Client Secret>`
|
||||
- OAUTH2_PROXY_COOKIE_SECRET with value of `python -c 'import os,base64; print base64.b64encode(os.urandom(16))'`
|
||||
- OAUTH2_PROXY_COOKIE_SECRET with value of `python -c 'import os,base64; print base64.b64encode(os.urandom(16))'`
|
||||
|
||||
4. Customize the contents of the file dashboard-ingress.yaml:
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ spec:
|
|||
# docker run -ti --rm python:3-alpine python -c 'import secrets,base64; print(base64.b64encode(base64.b64encode(secrets.token_bytes(16))));'
|
||||
- name: OAUTH2_PROXY_COOKIE_SECRET
|
||||
value: SECRET
|
||||
image: docker.io/colemickens/oauth2_proxy:latest
|
||||
image: quay.io/pusher/oauth2_proxy:latest
|
||||
imagePullPolicy: Always
|
||||
name: oauth2-proxy
|
||||
ports:
|
||||
|
|
23
docs/examples/psp/README.md
Normal file
23
docs/examples/psp/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Pod Security Policy (PSP)
|
||||
|
||||
In most clusters today, by default, all resources (e.g. Deployments and ReplicatSets)
|
||||
have permissions to create pods.
|
||||
Kubernetes however provides a more fine-grained authorization policy called
|
||||
[Pod Security Policy (PSP)](https://kubernetes.io/docs/concepts/policy/pod-security-policy/).
|
||||
|
||||
PSP allows the cluster owner to define the permission of each object, for example creating a pod.
|
||||
If you have PSP enabled on the cluster, and you deploy ingress-nginx,
|
||||
you will need to provide the Deployment with the permissions to create pods.
|
||||
|
||||
Before applying any objects, first apply the PSP permissions by running:
|
||||
```console
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/docs/examples/psp/psp.yaml
|
||||
```
|
||||
|
||||
Now that the pod security policy is applied, we can continue as usual by applying the
|
||||
[mandatory.yaml](https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml)
|
||||
according to the [Installation Guide](../../deploy/index.md).
|
||||
|
||||
Note: PSP permissions must be granted before to the creation of the Deployment and the ReplicaSet.
|
||||
If the Deployment or ReplicaSet already exist, they will receive the PSP permissions
|
||||
only after deleting them and reapplying mandatory.yaml.
|
87
docs/examples/psp/psp.yaml
Normal file
87
docs/examples/psp/psp.yaml
Normal file
|
@ -0,0 +1,87 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ingress-nginx
|
||||
|
||||
---
|
||||
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodSecurityPolicy
|
||||
metadata:
|
||||
annotations:
|
||||
# Assumes apparmor available
|
||||
apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
|
||||
apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default'
|
||||
seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default'
|
||||
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'docker/default'
|
||||
name: ingress-nginx
|
||||
spec:
|
||||
allowedCapabilities:
|
||||
- NET_BIND_SERVICE
|
||||
allowPrivilegeEscalation: true
|
||||
fsGroup:
|
||||
rule: 'MustRunAs'
|
||||
ranges:
|
||||
- min: 1
|
||||
max: 65535
|
||||
hostIPC: false
|
||||
hostNetwork: false
|
||||
hostPID: false
|
||||
hostPorts:
|
||||
- min: 80
|
||||
max: 65535
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsUser:
|
||||
rule: 'MustRunAsNonRoot'
|
||||
ranges:
|
||||
- min: 33
|
||||
max: 65535
|
||||
seLinux:
|
||||
rule: 'RunAsAny'
|
||||
supplementalGroups:
|
||||
rule: 'MustRunAs'
|
||||
ranges:
|
||||
# Forbid adding the root group.
|
||||
- min: 1
|
||||
max: 65535
|
||||
volumes:
|
||||
- 'configMap'
|
||||
- 'downwardAPI'
|
||||
- 'emptyDir'
|
||||
- 'projected'
|
||||
- 'secret'
|
||||
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: ingress-nginx-psp
|
||||
namespace: ingress-nginx
|
||||
rules:
|
||||
- apiGroups:
|
||||
- policy
|
||||
resourceNames:
|
||||
- ingress-nginx
|
||||
resources:
|
||||
- podsecuritypolicies
|
||||
verbs:
|
||||
- use
|
||||
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: ingress-nginx-psp
|
||||
namespace: ingress-nginx
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: ingress-nginx-psp
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: default
|
||||
- kind: ServiceAccount
|
||||
name: nginx-ingress-serviceaccount
|
|
@ -120,7 +120,7 @@ $ kubectl ingress-nginx backends -n ingress-nginx
|
|||
"secureCACert": {
|
||||
"secret": "",
|
||||
"caFilename": "",
|
||||
"pemSha": ""
|
||||
"caSha": ""
|
||||
},
|
||||
"sslPassthrough": false,
|
||||
"endpoints": [
|
||||
|
|
|
@ -44,7 +44,6 @@ They are set in the container spec of the `nginx-ingress-controller` Deployment
|
|||
| `--version` | Show release information about the NGINX Ingress controller and exit. |
|
||||
| `--vmodule moduleSpec` | comma-separated list of pattern=N settings for file-filtered logging |
|
||||
| `--watch-namespace string` | Namespace the controller watches for updates to Kubernetes objects. This includes Ingresses, Services and all configuration resources. All namespaces are watched if this parameter is left empty. |
|
||||
| `--disable-catch-all` | Disable support for catch-all Ingresses. |
|
||||
|`--validating-webhook`|The address to start an admission controller on|
|
||||
|`--validating-webhook-certificate`|The certificate the webhook is using for its TLS handling|
|
||||
|`--validating-webhook-key`|The key the webhook is using for its TLS handling|
|
||||
|
|
117
docs/user-guide/fcgi-services.md
Normal file
117
docs/user-guide/fcgi-services.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
|
||||
|
||||
# Exposing FastCGI Servers
|
||||
|
||||
> **FastCGI** is a [binary protocol](https://en.wikipedia.org/wiki/Binary_protocol "Binary protocol") for interfacing interactive programs with a [web server](https://en.wikipedia.org/wiki/Web_server "Web server"). [...] (It's) aim is to reduce the overhead related to interfacing between web server and CGI programs, allowing a server to handle more web page requests per unit of time.
|
||||
>
|
||||
> — Wikipedia
|
||||
|
||||
The _ingress-nginx_ ingress controller can be used to directly expose [FastCGI](https://en.wikipedia.org/wiki/FastCGI) servers. Enabling FastCGI in your Ingress only requires setting the _backend-protocol_ annotation to `FCGI`, and with a couple more annotations you can customize the way _ingress-nginx_ handles the communication with your FastCGI _server_.
|
||||
|
||||
|
||||
## Example Objects to Expose a FastCGI Pod
|
||||
|
||||
The _Pod_ example object below exposes port `9000`, which is the conventional FastCGI port.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: example-app
|
||||
labels:
|
||||
app: example-app
|
||||
spec:
|
||||
containers:
|
||||
- name: example-app
|
||||
image: example-app:1.0
|
||||
ports:
|
||||
- containerPort: 9000
|
||||
name: fastcgi
|
||||
```
|
||||
|
||||
The _Service_ object example below matches port `9000` from the _Pod_ object above.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: example-service
|
||||
spec:
|
||||
selector:
|
||||
app: example-app
|
||||
ports:
|
||||
- port: 9000
|
||||
targetPort: 9000
|
||||
name: fastcgi
|
||||
```
|
||||
|
||||
And the _Ingress_ and _ConfigMap_ objects below demonstrates the supported _FastCGI_ specific annotations (NGINX actually has 50 FastCGI directives, all of which have not been exposed in the ingress yet), and matches the service `example-service`, and the port named `fastcgi` from above. The _ConfigMap_ **must** be created first for the _Ingress Controller_ to be able to find it when the _Ingress_ object is created, otherwise you will need to restart the _Ingress Controller_ pods.
|
||||
|
||||
```yaml
|
||||
# The ConfigMap MUST be created first for the ingress controller to be able to
|
||||
# find it when the Ingress object is created.
|
||||
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: example-cm
|
||||
data:
|
||||
SCRIPT_FILENAME: "/example/index.php"
|
||||
|
||||
---
|
||||
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "nginx"
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "FCGI"
|
||||
nginx.ingress.kubernetes.io/fastcgi-index: "index.php"
|
||||
nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-cm"
|
||||
name: example-app
|
||||
spec:
|
||||
rules:
|
||||
- host: app.example.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: example-service
|
||||
servicePort: fastcgi
|
||||
```
|
||||
|
||||
## The FastCGI Ingress Annotations
|
||||
|
||||
### The `nginx.ingress.kubernetes.io/backend-protocol` Annotation
|
||||
|
||||
To enable FastCGI, the `backend-protocol` annotation needs to be set to `FCGI`, which overrides the default `HTTP` value.
|
||||
|
||||
> `nginx.ingress.kubernetes.io/backend-protocol: "FCGI"`
|
||||
|
||||
This enables the _FastCGI_ mode for the whole _Ingress_ object.
|
||||
|
||||
### The `nginx.ingress.kubernetes.io/fastcgi-index` Annotation
|
||||
|
||||
To specify an index file, the `fastcgi-index` annotation value can optionally be set. In the example below, the value is set to `index.php`. This annotation corresponds to [the _NGINX_ `fastcgi_index` directive](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_index).
|
||||
|
||||
> `nginx.ingress.kubernetes.io/fastcgi-index: "index.php"`
|
||||
|
||||
### The `nginx.ingress.kubernetes.io/fastcgi-params-configmap` Annotation
|
||||
|
||||
To specify [_NGINX_ `fastcgi_param` directives](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_param), the `fastcgi-params-configmap` annotation is used, which in turn must lead to a _ConfigMap_ object containing the _NGINX_ `fastcgi_param` directives as key/values.
|
||||
|
||||
> `nginx.ingress.kubernetes.io/fastcgi-params: "example-configmap"`
|
||||
|
||||
And the _ConfigMap_ object to specify the `SCRIPT_FILENAME` and `HTTP_PROXY` _NGINX's_ `fastcgi_param` directives will look like the following:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: example-configmap
|
||||
data:
|
||||
SCRIPT_FILENAME: "/example/index.php"
|
||||
HTTP_PROXY: ""
|
||||
```
|
||||
Using the _namespace/_ prefix is also supported, for example:
|
||||
|
||||
> `nginx.ingress.kubernetes.io/fastcgi-params: "example-namespace/example-configmap"`
|
|
@ -112,6 +112,8 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|
|||
|[nginx.ingress.kubernetes.io/enable-owasp-core-rules](#modsecurity)|bool|
|
||||
|[nginx.ingress.kubernetes.io/modsecurity-transaction-id](#modsecurity)|string|
|
||||
|[nginx.ingress.kubernetes.io/modsecurity-snippet](#modsecurity)|string|
|
||||
|[nginx.ingress.kubernetes.io/mirror-uri](#mirror)|string|
|
||||
|[nginx.ingress.kubernetes.io/mirror-request-body](#mirror)|string|
|
||||
|
||||
### Canary
|
||||
|
||||
|
@ -817,3 +819,34 @@ By default, a request would need to satisfy all authentication requirements in o
|
|||
```yaml
|
||||
nginx.ingress.kubernetes.io/satisfy: "any"
|
||||
```
|
||||
|
||||
### Mirror
|
||||
|
||||
Enables a request to be mirrored to a mirror backend. Responses by mirror backends are ignored. This feature is useful, to see how requests will react in "test" backends.
|
||||
|
||||
You can mirror a request to the `/mirror` path on your ingress, by applying the below:
|
||||
|
||||
```yaml
|
||||
nginx.ingress.kubernetes.io/mirror-uri: "/mirror"
|
||||
```
|
||||
|
||||
The mirror path can be defined as a separate ingress resource:
|
||||
|
||||
```
|
||||
location = /mirror {
|
||||
internal;
|
||||
proxy_pass http://test_backend;
|
||||
}
|
||||
```
|
||||
|
||||
By default the request-body is sent to the mirror backend, but can be turned off by applying:
|
||||
|
||||
```yaml
|
||||
nginx.ingress.kubernetes.io/mirror-request-body: "off"
|
||||
```
|
||||
|
||||
**Note:** The mirror directive will be applied to all paths within the ingress resource.
|
||||
|
||||
The request sent to the mirror is linked to the orignial request. If you have a slow mirror backend, then the orignial request will throttle.
|
||||
|
||||
For more information on the mirror module see https://nginx.org/en/docs/http/ngx_http_mirror_module.html
|
||||
|
|
|
@ -34,7 +34,6 @@ The following table shows a configuration option's name, type, and the default v
|
|||
|[access-log-path](#access-log-path)|string|"/var/log/nginx/access.log"|
|
||||
|[enable-access-log-for-default-backend](#enable-access-log-for-default-backend)|bool|"false"|
|
||||
|[error-log-path](#error-log-path)|string|"/var/log/nginx/error.log"|
|
||||
|[enable-dynamic-tls-records](#enable-dynamic-tls-records)|bool|"true"|
|
||||
|[enable-modsecurity](#enable-modsecurity)|bool|"false"|
|
||||
|[enable-owasp-modsecurity-crs](#enable-owasp-modsecurity-crs)|bool|"false"|
|
||||
|[client-header-buffer-size](#client-header-buffer-size)|string|"1k"|
|
||||
|
@ -149,6 +148,7 @@ The following table shows a configuration option's name, type, and the default v
|
|||
|[skip-access-log-urls](#skip-access-log-urls)|[]string|[]string{}|
|
||||
|[limit-rate](#limit-rate)|int|0|
|
||||
|[limit-rate-after](#limit-rate-after)|int|0|
|
||||
|[lua-shared-dicts](#lua-shared-dicts)|string|""|
|
||||
|[http-redirect-code](#http-redirect-code)|int|308|
|
||||
|[proxy-buffering](#proxy-buffering)|string|"off"|
|
||||
|[limit-req-status-code](#limit-req-status-code)|int|503|
|
||||
|
@ -209,13 +209,6 @@ __Note:__ the file `/var/log/nginx/error.log` is a symlink to `/dev/stderr`
|
|||
_References:_
|
||||
[http://nginx.org/en/docs/ngx_core_module.html#error_log](http://nginx.org/en/docs/ngx_core_module.html#error_log)
|
||||
|
||||
## enable-dynamic-tls-records
|
||||
|
||||
Enables dynamically sized TLS records to improve time-to-first-byte. _**default:**_ is enabled
|
||||
|
||||
_References:_
|
||||
[https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency](https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency)
|
||||
|
||||
## enable-modsecurity
|
||||
|
||||
Enables the modsecurity module for NGINX. _**default:**_ is disabled
|
||||
|
@ -488,6 +481,14 @@ Sets the [SSL protocols](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#
|
|||
|
||||
Please check the result of the configuration using `https://ssllabs.com/ssltest/analyze.html` or `https://testssl.sh`.
|
||||
|
||||
## ssl-early-data
|
||||
|
||||
Enables or disables TLS 1.3 [early data](https://tools.ietf.org/html/rfc8446#section-2.3)
|
||||
|
||||
This requires `ssl-protocols` to have `TLSv1.3` enabled.
|
||||
|
||||
[ssl_early_data](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data). The default is: `false`.
|
||||
|
||||
## ssl-session-cache
|
||||
|
||||
Enables or disables the use of shared [SSL cache](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache) among worker processes.
|
||||
|
@ -855,6 +856,21 @@ _References:_
|
|||
|
||||
Sets the initial amount after which the further transmission of a response to a client will be rate limited.
|
||||
|
||||
## lua-shared-dicts
|
||||
|
||||
Customize default Lua shared dictionaries or define more. You can use the following syntax to do so:
|
||||
|
||||
```
|
||||
lua-shared-dicts: "<my dict name>: <my dict size>, [<my dict name>: <my dict size>], ..."
|
||||
```
|
||||
|
||||
For example following will set default `certificate_data` dictionary to `100M` and will introduce a new dictionary called
|
||||
`my_custom_plugin`:
|
||||
|
||||
```
|
||||
lua-shared-dicts: "certificate_data: 100, my_custom_plugin: 5"
|
||||
```
|
||||
|
||||
_References:_
|
||||
[http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after](http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after)
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ In addition to the built-in functions provided by the Go package the following f
|
|||
- hasSuffix: [strings.HasSuffix](https://golang.org/pkg/strings/#HasSuffix)
|
||||
- toUpper: [strings.ToUpper](https://golang.org/pkg/strings/#ToUpper)
|
||||
- toLower: [strings.ToLower](https://golang.org/pkg/strings/#ToLower)
|
||||
- quote: wraps a string in double quotes
|
||||
- buildLocation: helps to build the NGINX Location section in each server
|
||||
- buildProxyPass: builds the reverse proxy configuration
|
||||
- buildRateLimit: helps to build a limit zone inside a location if contains a rate limit annotation
|
||||
|
|
|
@ -7,7 +7,7 @@ log_format upstreaminfo
|
|||
'{{ if $cfg.useProxyProtocol }}$proxy_protocol_addr{{ else }}$remote_addr{{ end }} - '
|
||||
'[$the_real_ip] - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
|
||||
'$request_length $request_time [$proxy_upstream_name] $upstream_addr '
|
||||
'$request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr '
|
||||
'$upstream_response_length $upstream_response_time $upstream_status $req_id';
|
||||
```
|
||||
|
||||
|
@ -26,6 +26,7 @@ log_format upstreaminfo
|
|||
| `$request_length` | request length (including request line, header, and request body) |
|
||||
| `$request_time` | time elapsed since the first bytes were read from the client |
|
||||
| `$proxy_upstream_name` | name of the upstream. The format is `upstream-<namespace>-<service name>-<service port>` |
|
||||
| `$proxy_alternative_upstream_name` | name of the alternative upstream. The format is `upstream-<namespace>-<service name>-<service port>` |
|
||||
| `$upstream_addr` | the IP address and port (or the path to the domain socket) of the upstream server. If several servers were contacted during request processing, their addresses are separated by commas. |
|
||||
| `$upstream_response_length` | the length of the response obtained from the upstream server |
|
||||
| `$upstream_response_time` | time spent on receiving the response from the upstream server as seconds with millisecond resolution |
|
||||
|
@ -45,4 +46,4 @@ Additional available variables:
|
|||
Sources:
|
||||
|
||||
- [Upstream variables](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#variables)
|
||||
- [Embedded variables](http://nginx.org/en/docs/http/ngx_http_core_module.html#variables)
|
||||
- [Embedded variables](http://nginx.org/en/docs/http/ngx_http_core_module.html#variables)
|
||||
|
|
51
go.mod
51
go.mod
|
@ -3,14 +3,16 @@ module k8s.io/ingress-nginx
|
|||
go 1.12
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.38.0 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/Sirupsen/logrus v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/eapache/channels v1.1.0
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect
|
||||
github.com/go-logr/logr v0.1.0 // indirect
|
||||
github.com/go-logr/zapr v0.1.1 // indirect
|
||||
github.com/go-openapi/swag v0.19.0 // indirect
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/imdario/mergo v0.3.7
|
||||
github.com/json-iterator/go v1.1.6
|
||||
|
@ -25,7 +27,6 @@ require (
|
|||
github.com/ncabatoff/procfs v0.0.0-20180903163354-e1a38cb53622 // indirect
|
||||
github.com/onsi/ginkgo v1.8.0
|
||||
github.com/onsi/gomega v1.5.0
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/opencontainers/runc v0.1.1
|
||||
github.com/parnurzeal/gorequest v0.2.15
|
||||
github.com/paultag/sniff v0.0.0-20170624152000-87325c3dddf4
|
||||
|
@ -34,9 +35,9 @@ require (
|
|||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f
|
||||
github.com/prometheus/common v0.2.0
|
||||
github.com/prometheus/procfs v0.0.0-20190328153300-af7bedc223fb // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect
|
||||
github.com/spf13/cobra v0.0.4
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/tallclair/mdtoc v0.0.0-20190627191617-4dc3d6f90813
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926
|
||||
github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
|
||||
|
@ -46,24 +47,42 @@ require (
|
|||
gopkg.in/go-playground/pool.v3 v3.1.1
|
||||
|
||||
k8s.io/api v0.0.0
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190626210203-fdc73e13f9a6
|
||||
k8s.io/apiextensions-apiserver v0.0.0
|
||||
k8s.io/apimachinery v0.0.0
|
||||
k8s.io/apiserver v0.0.0-20190625052034-8c075cba2f8c
|
||||
k8s.io/cli-runtime v0.0.0-20190711111425-61e036b70227
|
||||
k8s.io/apiserver v0.0.0
|
||||
k8s.io/cli-runtime v0.0.0
|
||||
k8s.io/client-go v12.0.0+incompatible
|
||||
k8s.io/cloud-provider v0.0.0-20190711113108-0d51509e0ef5 // indirect
|
||||
k8s.io/code-generator v0.0.0-20190620073620-d55040311883
|
||||
k8s.io/component-base v0.0.0-20190626045757-ca439aa083f5
|
||||
k8s.io/klog v0.3.3
|
||||
k8s.io/kubernetes v0.0.0
|
||||
k8s.io/code-generator v0.0.0
|
||||
k8s.io/component-base v0.0.0
|
||||
k8s.io/klog v0.4.0
|
||||
k8s.io/kubernetes v1.15.1
|
||||
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a // indirect
|
||||
sigs.k8s.io/controller-runtime v0.1.10
|
||||
sigs.k8s.io/testing_frameworks v0.1.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/Sirupsen/logrus => github.com/sirupsen/logrus v1.4.1
|
||||
|
||||
k8s.io/api => k8s.io/api v0.0.0-20190703205437-39734b2a72fe
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76
|
||||
k8s.io/kubernetes => k8s.io/kubernetes v1.14.3
|
||||
k8s.io/api => k8s.io/api v0.0.0-20190718183219-b59d8169aab5
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190620085550-3a2f62f126c9
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190711103026-7bf792636534
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190718184206-a1aa83af71a7
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20190620085659-429467d76d0e
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20190718183610-8e956561bbf5
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20190620090041-1a7e1f6630cd
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.0.0-20190620090010-a60497bb9ffa
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20190620085131-4cd66be69262
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.0.0-20190531030430-6117653b35f1
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.0.0-20190620090114-816aa063c73d
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20190620085316-c835efc41000
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.0.0-20190620085943-52018c8ce3c1
|
||||
k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.0.0-20190620085811-cc0b23ba60a9
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.0.0-20190620085909-5dfb14b3a101
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.0.0-20190620085837-98477dc0c87c
|
||||
k8s.io/kubernetes => k8s.io/kubernetes v1.15.1
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.0.0-20190620090159-a9e4f3cb5bf3
|
||||
k8s.io/metrics => k8s.io/metrics v0.0.0-20190620085627-5b02f62e9559
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.0.0-20190620085357-8191e314a1f7
|
||||
)
|
||||
|
|
284
go.sum
284
go.sum
|
@ -1,11 +1,22 @@
|
|||
bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
github.com/Azure/azure-sdk-for-go v21.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA=
|
||||
github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20181220005116-f8e995905100/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
|
||||
github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
|
@ -13,49 +24,83 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
|
|||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e h1:h0gP0hBU6DsA5IQduhLWGOEfIUKzJS5hhXQBSgHuF/g=
|
||||
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM=
|
||||
github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e/go.mod h1:uHBSeeATKpVazAACZBDPL/Nk/UhQDDsJWDlqYJo8/Us=
|
||||
github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY=
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
|
||||
github.com/client9/misspell v0.0.0-20170928000206-9ce5d979ffda/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cfssl v0.0.0-20180726162950-56268a613adf/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0=
|
||||
github.com/codedellemc/goscaleio v0.0.0-20170830184815-20e2ce2cf885/go.mod h1:JIHmDHNZO4tmA3y3RHp6+Gap6kFsNf55W9Pn/3YS9IY=
|
||||
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
|
||||
github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
|
||||
github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||
github.com/containernetworking/cni v0.6.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U=
|
||||
github.com/cpuguy83/go-md2man v1.0.4/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cyphar/filepath-securejoin v0.0.0-20170720062807-ae69057f2299/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
github.com/d2g/dhcp4client v0.0.0-20170829104524-6e570ed0a266/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda h1:NyywMz59neOoVRFDz+ccfKWxn784fiHMDnZSy6T+JXY=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff h1:FKH02LHYqSmeWd3GBh0KIkM8JBpw3RrShgtcWShdWJg=
|
||||
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k=
|
||||
github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/ekalinin/github-markdown-toc v0.0.0-20190514155158-83fadb60a7f1/go.mod h1:XfZS1iyC28CnllR54Ou2Ero6qs4Rmn7GpVumNSj1DZo=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw=
|
||||
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/fatih/camelcase v0.0.0-20160318181535-f6a740d52f96/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU=
|
||||
|
@ -75,36 +120,30 @@ github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpR
|
|||
github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8=
|
||||
github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
|
||||
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.17.2 h1:eb2NbuCnoe8cWAxhtK6CfMWUYmiFEZJ9Hx3Z2WRwJ5M=
|
||||
github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.17.2 h1:K/ycE/XTUDFltNHSO32cGRUhrVGJD64o8WgAIZNyc3k=
|
||||
github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8=
|
||||
github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
|
@ -113,29 +152,45 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
|
|||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v0.0.0-20160127222235-bd3c8e81be01/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
|
||||
github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8=
|
||||
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
|
||||
github.com/gomarkdown/markdown v0.0.0-20190222000725-ee6a7931a1e4 h1:vELsocEzlhM4lk2nhxolEaQrMp25u7/i9IX8s9uLads=
|
||||
github.com/gomarkdown/markdown v0.0.0-20190222000725-ee6a7931a1e4/go.mod h1:gmFANS06wAVmF0B9yi65QKsRmPQ97tze7FRLswua+OY=
|
||||
github.com/google/btree v0.0.0-20160524151835-7d79101e329e h1:JHB7F/4TJCrYBW8+GZO8VkWDj1jxcWuCl6uxKODiyi4=
|
||||
github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/cadvisor v0.33.2-0.20190411163913-9db8c7dee20a/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8 h1:L9JPKrtsHMQ4VCRQfHvbbHBfB2Urn8xf6QZeXZ+OrN4=
|
||||
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
|
||||
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w=
|
||||
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
|
@ -143,7 +198,12 @@ github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v0.0.0-20160711231752-d8c773c4cba1/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/heketi/heketi v0.0.0-20181109135656-558b29266ce0/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o=
|
||||
github.com/heketi/rest v0.0.0-20180404230133-aa6a65207413/go.mod h1:BeS3M108VzVlmAue3lv2WcGuPAX94/KN63MUURzbYSI=
|
||||
github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4=
|
||||
github.com/heketi/utils v0.0.0-20170317161834-435bc5bdfa64/go.mod h1:RYlF4ghFZPPmk2TC5REt5OFwvfb6lzxFWrTWB+qs28s=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
|
@ -151,48 +211,74 @@ github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
|||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.0.0-20141017032234-72f9bd7c4e0c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jteeuwen/go-bindata v0.0.0-20151023091102-a0ff2567cfb7/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kardianos/osext v0.0.0-20150410034420-8fef92e41e22/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.0.0-20140812000539-f31442d60e51/go.mod h1:Bvhd+E3laJ0AVkG0c9rmtZcnhV0HQ3+c3YxxqTvc/gA=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.0.0-20130911015532-6807e777504f/go.mod h1:sjUstKUATFIcff4qlB53Kml0wQPtJVc/3fWrmuUmcfA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/libopenstorage/openstorage v0.0.0-20170906232338-093a0c388875/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA=
|
||||
github.com/magiconair/properties v0.0.0-20160816085511-61b492c03cf4/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/mattn/go-shellwords v0.0.0-20180605041737-f8471b0a71de/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4=
|
||||
github.com/mholt/caddy v0.0.0-20180213163048-2de495001514/go.mod h1:Wb1PlT4DAYSqOEd03MsqkdkXnTxA8v9pKjdpxbqM1kY=
|
||||
github.com/miekg/dns v0.0.0-20160614162101-5d001d020961/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY=
|
||||
github.com/mistifyio/go-zfs v0.0.0-20151009155749-1b4ae6fb4e77/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 h1:kw1v0NlnN+GZcU8Ma8CLF2Zzgjfx95gs3/GN3vYAPpo=
|
||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
|
||||
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mmarkdown/mmark v2.0.40+incompatible h1:vMeUeDzBK3H+/mU0oMVfMuhSXJlIA+DE/DMPQNAj5C4=
|
||||
github.com/mmarkdown/mmark v2.0.40+incompatible/go.mod h1:Uvmoz7tvsWpr7bMVxIpqZPyN3FbOtzDmnsJDFp7ltJs=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52 h1:8zDEa5yAIWYBHSDpPbSgGIBL/SvPSE9/FlB3aQ54d/A=
|
||||
github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52/go.mod h1:jE2HT8eoucYyUPBFJMreiVlC3KPHkDMtN8wn+ef7Y64=
|
||||
github.com/mrunalp/fileutils v0.0.0-20160930181131-4ee1cc9a8058/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mvdan/xurls v0.0.0-20160110113200-1b768d7c393a/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/ncabatoff/go-seq v0.0.0-20180805175032-b08ef85ed833 h1:t4WWQ9I797y7QUgeEjeXnVb+oYuEDQc6gLvrZJTYo94=
|
||||
github.com/ncabatoff/go-seq v0.0.0-20180805175032-b08ef85ed833/go.mod h1:0CznHmXSjMEqs5Tezj/w2emQoM41wzYM9KpDKUHPYag=
|
||||
github.com/ncabatoff/process-exporter v0.0.0-20180915144445-bdf24ef23850 h1:+N9D9mM5Nr4iDYOrZBVDT7w7wGhFNTvEXWX80LNXJMo=
|
||||
|
@ -202,26 +288,34 @@ github.com/ncabatoff/procfs v0.0.0-20180903163354-e1a38cb53622/go.mod h1:1Sa4zSa
|
|||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420 h1:Yu3681ykYHDfLoI6XVjL4JWmkE+3TX9yfIWwRCh1kFM=
|
||||
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v0.0.0-20170604055404-372ad780f634/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.0.0-20181113202123-f000fe11ece1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v1.0.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v0.0.0-20170621221121-4a2974bf1ee9/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
|
||||
github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU=
|
||||
github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
||||
github.com/paultag/sniff v0.0.0-20170624152000-87325c3dddf4 h1:bOK8V55gl1AEWE9KiXSZ0fzARvVBehdmYZOTFcQ5kUo=
|
||||
github.com/paultag/sniff v0.0.0-20170624152000-87325c3dddf4/go.mod h1:J3XXNGJINXLa4yIivdUT0Ad/srv2q0pSOWbbm6El2EY=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v0.0.0-20160930220758-4d0e916071f6/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=
|
||||
|
@ -237,46 +331,68 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R
|
|||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190328153300-af7bedc223fb h1:LvNCMEj0FFZQYsxZb7o3xQPrtqOOB6lrTUOWshC+ZTs=
|
||||
github.com/prometheus/procfs v0.0.0-20190328153300-af7bedc223fb/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/robfig/cron v0.0.0-20170309132418-df38d32658d8/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
|
||||
github.com/russross/blackfriday v0.0.0-20151117072312-300106c228d5/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/seccomp/libseccomp-golang v0.0.0-20150813023252-1b506fc7c24e/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20151028001915-10ef21a441db/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sigma/go-inotify v0.0.0-20181102212354-c87b6cf5033d/go.mod h1:stlh9OsqBQSdwxTxX73mu41BBtRbIpZLQ7flcAoxAfo=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spf13/afero v0.0.0-20160816080757-b28a7effac97/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v0.0.0-20160730092037-e31f36ffc91a/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE=
|
||||
github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20160311093646-33c24e77fb80/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v0.0.0-20160820190039-7fb2782df3d8/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tallclair/mdtoc v0.0.0-20190627191617-4dc3d6f90813 h1:1Q3NGZNH8/oSGhSe0J8WjkFCeIOb0JSy7M4RpqY64XI=
|
||||
github.com/tallclair/mdtoc v0.0.0-20190627191617-4dc3d6f90813/go.mod h1:BjDk9nfX4091pXLHhvf6Ejr4/r//9NslWmweWb2Hkbs=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
|
||||
github.com/vmware/photon-controller-go-sdk v0.0.0-20170310013346-4a435daef6cc/go.mod h1:e6humHha1ekIwTCm+A5Qed5mG8V4JL+ChHcUOJ+L/8U=
|
||||
github.com/xanzy/go-cloudstack v0.0.0-20160728180336-1e2cbf647e57/go.mod h1:s3eL3z5pNXF5FVybcT+LIVdId8pYn709yv6v5mrkrQE=
|
||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272 h1:scDk3LAM8x+NPuywVGC0q0/G+5Ed8M0+YXecz4XnWRM=
|
||||
github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272/go.mod h1:KNkcm66cr4ilOiEcjydK+tc2ShPUhqmuoXCljXUBPu8=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg=
|
||||
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg=
|
||||
|
@ -286,17 +402,17 @@ go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslx
|
|||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495 h1:I6A9Ag9FpEKOjcKrRNjQkPHawoXIhKyTGfvvjFAiiAk=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -307,51 +423,55 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r
|
|||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI=
|
||||
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181004145325-8469e314837c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g=
|
||||
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20170824195420-5d2fd3ccab98/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485 h1:OB/uP/Puiu5vS5QMRPrXCDWUPb+kt8f1KW8oQzFejQw=
|
||||
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e h1:jRyg0XfpwWlhEV8mDfdNGBeSJM2fuyh9Yjrnd8kF2Ts=
|
||||
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
|
||||
google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
|
@ -359,7 +479,11 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
|||
google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7 h1:ZUjXAXmrAyrmmCPHgCA/vChHcpsX27MZ3yBonD/z1KE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
|
@ -370,54 +494,67 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
|||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo=
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
|
||||
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/pool.v3 v3.1.1 h1:4Qcj91IsYTpIeRhe/eo6Fz+w6uKWPEghx8vHFTYMfhw=
|
||||
gopkg.in/go-playground/pool.v3 v3.1.1/go.mod h1:pUAGBximS/hccTTSzEop6wvvQhVa3QPDFFW+8REdutg=
|
||||
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20190703205437-39734b2a72fe h1:MFaHtAyhZcfBZocN91muHSqnwiF5yfXx7yGoehneNYg=
|
||||
k8s.io/api v0.0.0-20190703205437-39734b2a72fe/go.mod h1:J5EZ0KSEjvyKOBy5BDHSF3zn82madLLWg7nUKaOHZKU=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190626210203-fdc73e13f9a6 h1:qmQstpcwzWz3sF4IdIdHNd+QqEW27NtIl3g1Hc64pKo=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190626210203-fdc73e13f9a6/go.mod h1:BtPMQLDJSVfn7fHhoZrhbSHNrEFnXjKNVefNBc7M4/c=
|
||||
k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76 h1:vxMYBaJgczGAIpJAOBco2eHuFYIyDdNIebt60jxLauA=
|
||||
k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA=
|
||||
k8s.io/apiserver v0.0.0-20190625052034-8c075cba2f8c h1:hQTBSsQW7vz11pD5H+u8CJDPH5tPMZU7fZNrngblrkI=
|
||||
k8s.io/apiserver v0.0.0-20190625052034-8c075cba2f8c/go.mod h1:iv88XxGh9TIPUnSeQVGhypLx6/n0xn1/8OxMRrkKUP4=
|
||||
k8s.io/cli-runtime v0.0.0-20190711111425-61e036b70227 h1:q8pjgm8GrV8Zsnb2bdy4SHM41rASdMH59D8LHQDoKQM=
|
||||
k8s.io/cli-runtime v0.0.0-20190711111425-61e036b70227/go.mod h1:ZJEA9p+Wpusb5qdOk+B2TSXNY6xFrcduZrWaVaZI0V4=
|
||||
k8s.io/client-go v0.0.0-20190624085356-2c6e35a5b9cf/go.mod h1:rQMvHbaXi4pHSLf91Z1YI3h2Av+T3jsFKEIAWucw7hc=
|
||||
k8s.io/client-go v0.0.0-20190626045420-1ec4b74c7bda/go.mod h1:+MutroXOEjjw9n8kfVq8ZJEE/Fk3YVww/+4uIg+m/S0=
|
||||
k8s.io/client-go v0.0.0-20190711103903-4a0861cac5e0/go.mod h1:MdhWxiGHTqqubuL4pqFLTAhEjVr0Oa6nj8mAy3kFIEI=
|
||||
k8s.io/client-go v12.0.0+incompatible h1:YlJxncpeVUC98/WMZKC3JZGk/OXQWCZjAB4Xr3B17RY=
|
||||
k8s.io/client-go v12.0.0+incompatible/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k=
|
||||
k8s.io/cloud-provider v0.0.0-20190711113108-0d51509e0ef5 h1:sRUsF1Fop2aeMJOfCT4xlQJUvMhk+EznyzY7RMW6ZzQ=
|
||||
k8s.io/cloud-provider v0.0.0-20190711113108-0d51509e0ef5/go.mod h1:5LDt9VVqCTfW59z7dQ8QXZ3L+CwHP/6P1rRSBCgdb3o=
|
||||
k8s.io/code-generator v0.0.0-20190620073620-d55040311883 h1:NWWNvN6IdpmQvZ43rVccCI8GPUrheK8XNdqeKycw0DI=
|
||||
k8s.io/code-generator v0.0.0-20190620073620-d55040311883/go.mod h1:+a+9g9W0llgbgvx6qOb+VbeZPH5km1FrVyMQe9/jkQY=
|
||||
k8s.io/component-base v0.0.0-20190624085813-dd74dcc4bb91/go.mod h1:rzptoTTLijQSUO6SiDWhyXGRbVquvfM7QY0fvdPyXSU=
|
||||
k8s.io/component-base v0.0.0-20190626045757-ca439aa083f5 h1:q/LCzptcxSw9EyZFILhgkvgSh3TBPM8UhLWCq19sV7I=
|
||||
k8s.io/component-base v0.0.0-20190626045757-ca439aa083f5/go.mod h1:rzptoTTLijQSUO6SiDWhyXGRbVquvfM7QY0fvdPyXSU=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20190718183219-b59d8169aab5 h1:X3LHYU4fwu75lvvWypbppCKuhqg1KrvcZ1lLaAgmE/g=
|
||||
k8s.io/api v0.0.0-20190718183219-b59d8169aab5/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190620085550-3a2f62f126c9 h1:k9vwVsnIfkX2eTHTTfJoYo6pGwwVBUAr7VZzinSrjLM=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190620085550-3a2f62f126c9/go.mod h1:di7nkarEFC6V6WvJIq4rLpWymXYPM3wwqZFtYIA0Xg0=
|
||||
k8s.io/apimachinery v0.0.0-20190711103026-7bf792636534 h1:Iz41otzY4YmgjBECtotKCfHHKm9FSZ5x/1ykagnI30s=
|
||||
k8s.io/apimachinery v0.0.0-20190711103026-7bf792636534/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA=
|
||||
k8s.io/apiserver v0.0.0-20190718184206-a1aa83af71a7 h1:8mEhvrlegNEsADNHBah9RiCpEGaG1WVLall4pitumBs=
|
||||
k8s.io/apiserver v0.0.0-20190718184206-a1aa83af71a7/go.mod h1:U+p3oErZ9CNJpjYE9Kx0IZ0VHFiDC92/xM4znOfwqaU=
|
||||
k8s.io/cli-runtime v0.0.0-20190620085659-429467d76d0e h1:54KOxJkSycxbYrueshmFp1cUAfVbEP9sjYKVbIXYw9M=
|
||||
k8s.io/cli-runtime v0.0.0-20190620085659-429467d76d0e/go.mod h1:nxflCnMaMcLCeQNRLyGeoY2crlsPMVmCPc0iGTUvajE=
|
||||
k8s.io/client-go v0.0.0-20190718183610-8e956561bbf5 h1:ZIHnBytv9H1jiI7K/Szva829lP6zKsluhjVRdxf5kHA=
|
||||
k8s.io/client-go v0.0.0-20190718183610-8e956561bbf5/go.mod h1:ozblAqkW495yoAX60QZyxQBq5W0YixE9Ffn4F91RO0g=
|
||||
k8s.io/cloud-provider v0.0.0-20190620090041-1a7e1f6630cd h1:3rNeJmlNQjzcuUi6h1loRsqKjMQSxWDAWYDMittuTGE=
|
||||
k8s.io/cloud-provider v0.0.0-20190620090041-1a7e1f6630cd/go.mod h1:Un5YLpjmC+XZ9zO19edNEnyy9bQ9gt2H210665c4FWc=
|
||||
k8s.io/cluster-bootstrap v0.0.0-20190620090010-a60497bb9ffa/go.mod h1:nKdJhweDROdvToPucD2iin6nuJ0zg8qRxBu2i/LqxL0=
|
||||
k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b h1:p+PRuwXWwk5e+UYvicGiavEupapqM5NOxUl3y1GkD6c=
|
||||
k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b/go.mod h1:G8bQwmHm2eafm5bgtX67XDZQ8CWKSGu9DekI+yN4Y5I=
|
||||
k8s.io/component-base v0.0.0-20190620085131-4cd66be69262 h1:2j4mE+jKntnIcQjOOWyzb/2GsJIuPqjORLMDeHnagpM=
|
||||
k8s.io/component-base v0.0.0-20190620085131-4cd66be69262/go.mod h1:VLedAFwENz2swOjm0zmUXpAP2mV55c49xgaOzPBI/QQ=
|
||||
k8s.io/cri-api v0.0.0-20190531030430-6117653b35f1 h1:NVZIgq492plCUB4GlZUTdgGQRPOKmBWpw2HlOJDm9OQ=
|
||||
k8s.io/cri-api v0.0.0-20190531030430-6117653b35f1/go.mod h1:K6Ux7uDbzKhacgqW0OJg3rjXk/SR9kprCPfSUDXGB5A=
|
||||
k8s.io/csi-translation-lib v0.0.0-20190620090114-816aa063c73d/go.mod h1:q5k0Vv3qsOTu2PUrTh+4mRAj+Pt+1BRyWiiwr5LBFOM=
|
||||
k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af h1:SwjZbO0u5ZuaV6TRMWOGB40iaycX8sbdMQHtjNZ19dk=
|
||||
k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/heapster v1.2.0-beta.1/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
|
||||
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c=
|
||||
k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=
|
||||
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-aggregator v0.0.0-20190620085316-c835efc41000/go.mod h1:VQ+wumV2o4KXe3arqjS/+4l3Hd+3cZOOCf1wcGPzPJg=
|
||||
k8s.io/kube-controller-manager v0.0.0-20190620085943-52018c8ce3c1/go.mod h1:E2bR8dj02P7Ydp/oaGjos0ydxIps1dcqvA21BsAGr9I=
|
||||
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI=
|
||||
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
||||
k8s.io/kubernetes v1.14.3 h1:/FQkOJpjc1jGA37s7Rt3U10VwIKW685ejrgOp4UDRFE=
|
||||
k8s.io/kubernetes v1.14.3/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/kube-proxy v0.0.0-20190620085811-cc0b23ba60a9/go.mod h1:s7TcaJtcwsGGyi9q1gmmDuTBwJcJn/rfyAOIUUh3JjI=
|
||||
k8s.io/kube-scheduler v0.0.0-20190620085909-5dfb14b3a101/go.mod h1:aiWD0dWmuCnURY1+o3E0WgKQOlWFFqHESjz4nhzXXh8=
|
||||
k8s.io/kubelet v0.0.0-20190620085837-98477dc0c87c/go.mod h1:L9fEppwvpkTrPZju8XolATHatuUt4Vscd/SVHZZGHKc=
|
||||
k8s.io/kubernetes v1.15.1 h1:bCoCfn9sRFf47U5wn/y6I397hduMEpJ2gh4uN8BUYGI=
|
||||
k8s.io/kubernetes v1.15.1/go.mod h1:3RE5ikMc73WK+dSxk4pQuQ6ZaJcPXiZX2dj98RcdCuM=
|
||||
k8s.io/legacy-cloud-providers v0.0.0-20190620090159-a9e4f3cb5bf3/go.mod h1:vN4qQ5sBl+8105txtOVlFFNAXTc8lYvM41krV0K7xXc=
|
||||
k8s.io/metrics v0.0.0-20190620085627-5b02f62e9559/go.mod h1:f5M7Wu7aoJPaYxys9OpQdzhfOVWhuS/WE20voRy8KEY=
|
||||
k8s.io/repo-infra v0.0.0-20181204233714-00fe14e3d1a3/go.mod h1:+G1xBfZDfVFsm1Tj/HNCvg4QqWx8rJ2Fxpqr1rqp/gQ=
|
||||
k8s.io/sample-apiserver v0.0.0-20190620085357-8191e314a1f7/go.mod h1:RVMdQ03C5ZqPz82uV82L72wju27+zFFnyzKje9DVBZI=
|
||||
k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4=
|
||||
k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
|
||||
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw=
|
||||
|
@ -436,3 +573,4 @@ sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkW
|
|||
sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
|
||||
|
|
|
@ -21,5 +21,6 @@ limitations under the License.
|
|||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/tallclair/mdtoc"
|
||||
_ "k8s.io/code-generator"
|
||||
)
|
||||
|
|
34
hack/update-toc.sh
Executable file
34
hack/update-toc.sh
Executable file
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2019 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
export KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||
|
||||
# Install tools we need, but only from vendor/...
|
||||
cd ${KUBE_ROOT}
|
||||
go install ./vendor/github.com/tallclair/mdtoc
|
||||
if ! which mdtoc >/dev/null 2>&1; then
|
||||
echo "Can't find mdtoc - is your GOPATH 'bin' in your PATH?" >&2
|
||||
echo " GOPATH: ${GOPATH}" >&2
|
||||
echo " PATH: ${PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update tables of contents if necessary.
|
||||
grep --include='*.md' -rl docs/enhancements/* -e '<!-- toc -->' | xargs mdtoc --inplace
|
105
images/fastcgi-helloserver/Makefile
Normal file
105
images/fastcgi-helloserver/Makefile
Normal file
|
@ -0,0 +1,105 @@
|
|||
all: all-container
|
||||
|
||||
BUILDTAGS=
|
||||
|
||||
# Use the 0.0 tag for testing, it shouldn't clobber any release builds
|
||||
TAG?=0.1
|
||||
REGISTRY?=kubernetes-ingress-controller
|
||||
GOOS?=linux
|
||||
DOCKER?=docker
|
||||
SED_I?=sed -i
|
||||
GOHOSTOS ?= $(shell go env GOHOSTOS)
|
||||
|
||||
PKG=k8s.io/ingress-nginx/images/fastcgi-helloserver
|
||||
|
||||
ifeq ($(GOHOSTOS),darwin)
|
||||
SED_I=sed -i ''
|
||||
endif
|
||||
|
||||
REPO_INFO=$(shell git config --get remote.origin.url)
|
||||
|
||||
ARCH ?= $(shell go env GOARCH)
|
||||
GOARCH = ${ARCH}
|
||||
|
||||
BASEIMAGE?=alpine:3.9
|
||||
|
||||
ALL_ARCH = amd64 arm arm64 ppc64le
|
||||
|
||||
QEMUVERSION=v3.0.0
|
||||
|
||||
IMGNAME = fastcgi-helloserver
|
||||
IMAGE = $(REGISTRY)/$(IMGNAME)
|
||||
MULTI_ARCH_IMG = $(IMAGE)-$(ARCH)
|
||||
|
||||
ifeq ($(ARCH),arm)
|
||||
QEMUARCH=arm
|
||||
GOARCH=arm
|
||||
endif
|
||||
ifeq ($(ARCH),arm64)
|
||||
QEMUARCH=aarch64
|
||||
endif
|
||||
ifeq ($(ARCH),ppc64le)
|
||||
QEMUARCH=ppc64le
|
||||
GOARCH=ppc64le
|
||||
endif
|
||||
|
||||
TEMP_DIR := $(shell mktemp -d)
|
||||
|
||||
DOCKERFILE := $(TEMP_DIR)/rootfs/Dockerfile
|
||||
|
||||
sub-container-%:
|
||||
$(MAKE) ARCH=$* build container
|
||||
|
||||
sub-push-%:
|
||||
$(MAKE) ARCH=$* push
|
||||
|
||||
all-container: $(addprefix sub-container-,$(ALL_ARCH))
|
||||
|
||||
all-push: $(addprefix sub-push-,$(ALL_ARCH))
|
||||
|
||||
container: .container-$(ARCH)
|
||||
.container-$(ARCH):
|
||||
cp -r ./* $(TEMP_DIR)
|
||||
$(SED_I) 's|BASEIMAGE|$(BASEIMAGE)|g' $(DOCKERFILE)
|
||||
$(SED_I) "s|QEMUARCH|$(QEMUARCH)|g" $(DOCKERFILE)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
|
||||
$(SED_I) "/CROSS_BUILD_/d" $(DOCKERFILE)
|
||||
else
|
||||
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
|
||||
# $(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMUVERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(TEMP_DIR)/rootfs
|
||||
$(SED_I) "s/CROSS_BUILD_//g" $(DOCKERFILE)
|
||||
endif
|
||||
|
||||
$(DOCKER) build -t $(MULTI_ARCH_IMG):$(TAG) $(TEMP_DIR)/rootfs
|
||||
|
||||
ifeq ($(ARCH), amd64)
|
||||
# This is for to maintain the backward compatibility
|
||||
$(DOCKER) tag $(MULTI_ARCH_IMG):$(TAG) $(IMAGE):$(TAG)
|
||||
endif
|
||||
|
||||
push: .push-$(ARCH)
|
||||
.push-$(ARCH):
|
||||
$(DOCKER) push $(MULTI_ARCH_IMG):$(TAG)
|
||||
ifeq ($(ARCH), amd64)
|
||||
$(DOCKER) push $(IMAGE):$(TAG)
|
||||
endif
|
||||
|
||||
clean:
|
||||
$(DOCKER) rmi -f $(MULTI_ARCH_IMG):$(TAG) || true
|
||||
|
||||
build: clean
|
||||
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -a -installsuffix cgo \
|
||||
-ldflags "-s -w" \
|
||||
-o ${TEMP_DIR}/rootfs/fastcgi-helloserver ${PKG}/...
|
||||
|
||||
release: all-container all-push
|
||||
echo "done"
|
||||
|
||||
.PHONY: register-qemu
|
||||
register-qemu:
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for binaries in multiple platforms
|
||||
$(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
|
30
images/fastcgi-helloserver/main.go
Normal file
30
images/fastcgi-helloserver/main.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
)
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
keys, ok := r.URL.Query()["name"]
|
||||
|
||||
if !ok || len(keys[0]) < 1 {
|
||||
fmt.Fprintf(w, "Hello world!")
|
||||
return
|
||||
}
|
||||
|
||||
key := keys[0]
|
||||
fmt.Fprintf(w, "Hello "+string(key)+"!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/hello", hello)
|
||||
|
||||
l, err := net.Listen("tcp", "0.0.0.0:9000")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fcgi.Serve(l, nil)
|
||||
}
|
21
images/fastcgi-helloserver/rootfs/Dockerfile
Executable file
21
images/fastcgi-helloserver/rootfs/Dockerfile
Executable file
|
@ -0,0 +1,21 @@
|
|||
# Copyright 2017 The Kubernetes Authors. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
FROM BASEIMAGE
|
||||
|
||||
CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/
|
||||
|
||||
COPY . /
|
||||
|
||||
CMD ["/fastcgi-helloserver"]
|
|
@ -13,7 +13,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
# 0.0.0 shouldn't clobber any released builds
|
||||
TAG ?= 0.90
|
||||
TAG ?= 0.92
|
||||
REGISTRY ?= quay.io/kubernetes-ingress-controller
|
||||
ARCH ?= $(shell go env GOARCH)
|
||||
DOCKER ?= docker
|
||||
|
|
|
@ -21,7 +21,7 @@ set -o pipefail
|
|||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
export OPENRESTY_VERSION=1.15.8.1
|
||||
export OPENRESTY_VERSION=1.15.8.2
|
||||
export NGINX_DIGEST_AUTH=cd8641886c873cf543255aeda20d23e4cd603d05
|
||||
export NGINX_SUBSTITUTIONS=bc58cb11844bc42735bbaef7085ea86ace46d05b
|
||||
export NGINX_OPENTRACING_VERSION=0.8.0
|
||||
|
@ -29,7 +29,7 @@ export OPENTRACING_CPP_VERSION=1.5.1
|
|||
export ZIPKIN_CPP_VERSION=0.5.2
|
||||
export JAEGER_VERSION=cdfaf5bb25ff5f8ec179fd548e6c7c2ade9a6a09
|
||||
export MSGPACK_VERSION=3.1.1
|
||||
export DATADOG_CPP_VERSION=0.4.3
|
||||
export DATADOG_CPP_VERSION=1.0.1
|
||||
export MODSECURITY_VERSION=d7101e13685efd7e7c9f808871b202656a969f4b
|
||||
export MODSECURITY_LIB_VERSION=3.0.3
|
||||
export OWASP_MODSECURITY_CRS_VERSION=3.1.0
|
||||
|
@ -152,7 +152,7 @@ get_src 3183450d897baa9309347c8617edc0c97c5b29ffc32bd2d12f498edf2dcbeffa \
|
|||
get_src bda49f996a73d2c6080ff0523e7b535917cd28c8a79c3a5da54fc29332d61d1e \
|
||||
"https://github.com/msgpack/msgpack-c/archive/cpp-$MSGPACK_VERSION.tar.gz"
|
||||
|
||||
get_src 7ef075c5936cfcca37d32c3b83b3b05d86f8a919d61fc94634f97a5c6542cff4 \
|
||||
get_src f7fb2ad541f812c36fd78f9a38e4582d87dadb563ab80bee3f7c3a2132a425c5 \
|
||||
"https://github.com/DataDog/dd-opentracing-cpp/archive/v$DATADOG_CPP_VERSION.tar.gz"
|
||||
|
||||
get_src f5470132d8756eef293833e30508926894883924a445e3b9a07c869d26d4706d \
|
||||
|
|
|
@ -16,92 +16,5 @@ limitations under the License.
|
|||
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
// ReadWriteByUser defines linux permission to read and write files for the owner user
|
||||
const ReadWriteByUser = 0660
|
||||
|
||||
// ReadByUserGroup defines linux permission to read files by the user and group owner/s
|
||||
const ReadByUserGroup = 0640
|
||||
|
||||
// Filesystem is an interface that we can use to mock various filesystem operations
|
||||
type Filesystem interface {
|
||||
filesystem.Filesystem
|
||||
}
|
||||
|
||||
// NewLocalFS implements Filesystem using same-named functions from "os" and "io/ioutil".
|
||||
func NewLocalFS() (Filesystem, error) {
|
||||
fs := filesystem.DefaultFs{}
|
||||
|
||||
for _, directory := range directories {
|
||||
err := fs.MkdirAll(directory, ReadWriteByUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
// NewFakeFS creates an in-memory filesystem with all the required
|
||||
// paths used by the ingress controller.
|
||||
// This allows running test without polluting the local machine.
|
||||
func NewFakeFS() (Filesystem, error) {
|
||||
osFs := filesystem.DefaultFs{}
|
||||
fakeFs := filesystem.NewFakeFs()
|
||||
|
||||
//TODO: find another way to do this
|
||||
rootFS := filepath.Clean(fmt.Sprintf("%v/%v", os.Getenv("GOPATH"), "src/k8s.io/ingress-nginx/rootfs"))
|
||||
|
||||
var fileList []string
|
||||
err := filepath.Walk(rootFS, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file := strings.TrimPrefix(path, rootFS)
|
||||
if file == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
fileList = append(fileList, file)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range fileList {
|
||||
realPath := fmt.Sprintf("%v%v", rootFS, file)
|
||||
|
||||
data, err := osFs.ReadFile(realPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fakeFile, err := fakeFs.Create(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = fakeFile.Write(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return fakeFs, nil
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 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 file
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewFakeFS(t *testing.T) {
|
||||
fs, err := NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating filesystem abstraction: %v", err)
|
||||
}
|
||||
|
||||
if fs == nil {
|
||||
t.Fatal("expected a filesystem but none returned")
|
||||
}
|
||||
|
||||
_, err = fs.Stat("/etc/nginx/nginx.conf")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error reading default nginx.conf file: %v", err)
|
||||
}
|
||||
}
|
|
@ -39,12 +39,14 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/customhttperrors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/portinredirect"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
|
||||
|
@ -83,6 +85,7 @@ type Ingress struct {
|
|||
CustomHTTPErrors []int
|
||||
DefaultBackend *apiv1.Service
|
||||
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
|
||||
FastCGI fastcgi.Config
|
||||
Denied *string
|
||||
ExternalAuth authreq.Config
|
||||
EnableGlobalAuth bool
|
||||
|
@ -109,6 +112,7 @@ type Ingress struct {
|
|||
LuaRestyWAF luarestywaf.Config
|
||||
InfluxDB influxdb.Config
|
||||
ModSecurity modsecurity.Config
|
||||
Mirror mirror.Config
|
||||
}
|
||||
|
||||
// Extractor defines the annotation parsers to be used in the extraction of annotations
|
||||
|
@ -130,6 +134,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
|||
"CorsConfig": cors.NewParser(cfg),
|
||||
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
|
||||
"DefaultBackend": defaultbackend.NewParser(cfg),
|
||||
"FastCGI": fastcgi.NewParser(cfg),
|
||||
"ExternalAuth": authreq.NewParser(cfg),
|
||||
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
|
||||
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
|
||||
|
@ -156,6 +161,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
|||
"InfluxDB": influxdb.NewParser(cfg),
|
||||
"BackendProtocol": backendprotocol.NewParser(cfg),
|
||||
"ModSecurity": modsecurity.NewParser(cfg),
|
||||
"Mirror": mirror.NewParser(cfg),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error)
|
|||
return &resolver.AuthSSLCert{
|
||||
Secret: name,
|
||||
CAFileName: "/opt/ca.pem",
|
||||
PemSHA: "123",
|
||||
CASHA: "123",
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
|
|
@ -86,12 +86,7 @@ func (e1 *Config) Equal(e2 *Config) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
match = sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -168,7 +168,7 @@ func TestHeaderAnnotations(t *testing.T) {
|
|||
i, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected error but retuned nil", err.Error())
|
||||
t.Error("expected error but retuned nil")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ func TestCacheDurationAnnotations(t *testing.T) {
|
|||
i, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected error but retuned nil", err.Error())
|
||||
t.Errorf("expected error but retuned nil")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, erro
|
|||
return &resolver.AuthSSLCert{
|
||||
Secret: "default/demo-secret",
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
PemSHA: "abc",
|
||||
CASHA: "abc",
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
@ -202,12 +202,12 @@ func TestEquals(t *testing.T) {
|
|||
sslCert1 := resolver.AuthSSLCert{
|
||||
Secret: "default/demo-secret",
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
PemSHA: "abc",
|
||||
CASHA: "abc",
|
||||
}
|
||||
sslCert2 := resolver.AuthSSLCert{
|
||||
Secret: "default/other-demo-secret",
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
PemSHA: "abc",
|
||||
CASHA: "abc",
|
||||
}
|
||||
cfg1.AuthSSLCert = sslCert1
|
||||
cfg2.AuthSSLCert = sslCert2
|
||||
|
|
|
@ -31,7 +31,7 @@ import (
|
|||
const HTTP = "HTTP"
|
||||
|
||||
var (
|
||||
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS)$`)
|
||||
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`)
|
||||
)
|
||||
|
||||
type backendProtocol struct {
|
||||
|
|
107
internal/ingress/annotations/fastcgi/main.go
Normal file
107
internal/ingress/annotations/fastcgi/main.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
Copyright 2018 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 fastcgi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
type fastcgi struct {
|
||||
r resolver.Resolver
|
||||
}
|
||||
|
||||
// Config describes the per location fastcgi config
|
||||
type Config struct {
|
||||
Index string `json:"index"`
|
||||
Params map[string]string `json:"params"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two Configuration types
|
||||
func (l1 *Config) Equal(l2 *Config) bool {
|
||||
if l1 == l2 {
|
||||
return true
|
||||
}
|
||||
|
||||
if l1 == nil || l2 == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if l1.Index != l2.Index {
|
||||
return false
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(l1.Params, l2.Params)
|
||||
}
|
||||
|
||||
// NewParser creates a new fastcgiConfig protocol annotation parser
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return fastcgi{r}
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to indicate the fastcgiConfig.
|
||||
func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
|
||||
fcgiConfig := Config{}
|
||||
|
||||
if ing.GetAnnotations() == nil {
|
||||
return fcgiConfig, nil
|
||||
}
|
||||
|
||||
index, err := parser.GetStringAnnotation("fastcgi-index", ing)
|
||||
if err != nil {
|
||||
index = ""
|
||||
}
|
||||
fcgiConfig.Index = index
|
||||
|
||||
cm, err := parser.GetStringAnnotation("fastcgi-params-configmap", ing)
|
||||
if err != nil {
|
||||
return fcgiConfig, nil
|
||||
}
|
||||
|
||||
cmns, cmn, err := cache.SplitMetaNamespaceKey(cm)
|
||||
if err != nil {
|
||||
return fcgiConfig, ing_errors.LocationDenied{
|
||||
Reason: errors.Wrap(err, "error reading configmap name from annotation"),
|
||||
}
|
||||
}
|
||||
|
||||
if cmns == "" {
|
||||
cmns = ing.Namespace
|
||||
}
|
||||
|
||||
cm = fmt.Sprintf("%v/%v", cmns, cmn)
|
||||
cmap, err := a.r.GetConfigMap(cm)
|
||||
if err != nil {
|
||||
return fcgiConfig, ing_errors.LocationDenied{
|
||||
Reason: errors.Wrapf(err, "unexpected error reading configmap %v", cm),
|
||||
}
|
||||
}
|
||||
|
||||
fcgiConfig.Params = cmap.Data
|
||||
|
||||
return fcgiConfig, nil
|
||||
}
|
263
internal/ingress/annotations/fastcgi/main_test.go
Normal file
263
internal/ingress/annotations/fastcgi/main_test.go
Normal file
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
Copyright 2018 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 fastcgi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
return &networking.Ingress{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: networking.IngressSpec{
|
||||
Backend: &networking.IngressBackend{
|
||||
ServiceName: "fastcgi",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type mockConfigMap struct {
|
||||
resolver.Mock
|
||||
}
|
||||
|
||||
func (m mockConfigMap) GetConfigMap(name string) (*api.ConfigMap, error) {
|
||||
if name != "default/demo-configmap" {
|
||||
return nil, errors.Errorf("there is no configmap with name %v", name)
|
||||
}
|
||||
|
||||
return &api.ConfigMap{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Namespace: api.NamespaceDefault,
|
||||
Name: "demo-secret",
|
||||
},
|
||||
Data: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestParseEmptyFastCGIAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if config.Index != "" {
|
||||
t.Errorf("Index should be an empty string")
|
||||
}
|
||||
|
||||
if 0 != len(config.Params) {
|
||||
t.Errorf("Params should be an empty slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFastCGIIndexAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
const expectedAnnotation = "index.php"
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-index")] = expectedAnnotation
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if config.Index != "index.php" {
|
||||
t.Errorf("expected %s but %v returned", expectedAnnotation, config.Index)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEmptyFastCGIParamsConfigMapAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = ""
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if 0 != len(config.Params) {
|
||||
t.Errorf("Params should be an empty slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFastCGIInvalidParamsConfigMapAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"}
|
||||
for _, configmap := range invalidConfigMapList {
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err == nil {
|
||||
t.Errorf("Reading an unexisting configmap should return an error")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if 0 != len(config.Params) {
|
||||
t.Errorf("Params should be an empty slice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFastCGIParamsConfigMapAnnotationWithoutNS(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "demo-configmap"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if 2 != len(config.Params) {
|
||||
t.Errorf("Params should have a length of 2")
|
||||
}
|
||||
|
||||
if "200" != config.Params["REDIRECT_STATUS"] || "$server_name" != config.Params["SERVER_NAME"] {
|
||||
t.Errorf("Params value is not the one expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFastCGIParamsConfigMapAnnotationWithNS(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "default/demo-configmap"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if 2 != len(config.Params) {
|
||||
t.Errorf("Params should have a length of 2")
|
||||
}
|
||||
|
||||
if "200" != config.Params["REDIRECT_STATUS"] || "$server_name" != config.Params["SERVER_NAME"] {
|
||||
t.Errorf("Params value is not the one expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigEquality(t *testing.T) {
|
||||
|
||||
var nilConfig *Config
|
||||
|
||||
config := Config{
|
||||
Index: "index.php",
|
||||
Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
|
||||
}
|
||||
|
||||
configCopy := Config{
|
||||
Index: "index.php",
|
||||
Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
|
||||
}
|
||||
|
||||
config2 := Config{
|
||||
Index: "index.php",
|
||||
Params: map[string]string{"REDIRECT_STATUS": "200"},
|
||||
}
|
||||
|
||||
config3 := Config{
|
||||
Index: "index.py",
|
||||
Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
|
||||
}
|
||||
|
||||
config4 := Config{
|
||||
Index: "index.php",
|
||||
Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
|
||||
}
|
||||
|
||||
if !config.Equal(&config) {
|
||||
t.Errorf("config should be equal to itself")
|
||||
}
|
||||
|
||||
if nilConfig.Equal(&config) {
|
||||
t.Errorf("Foo")
|
||||
}
|
||||
|
||||
if !config.Equal(&configCopy) {
|
||||
t.Errorf("config should be equal to configCopy")
|
||||
}
|
||||
|
||||
if config.Equal(&config2) {
|
||||
t.Errorf("config2 should not be equal to config")
|
||||
}
|
||||
|
||||
if config.Equal(&config3) {
|
||||
t.Errorf("config3 should not be equal to config")
|
||||
}
|
||||
|
||||
if !config.Equal(&config4) {
|
||||
t.Errorf("config4 should be equal to config")
|
||||
}
|
||||
}
|
58
internal/ingress/annotations/mirror/main.go
Normal file
58
internal/ingress/annotations/mirror/main.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Copyright 2019 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 mirror
|
||||
|
||||
import (
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
// Config returns the mirror to use in a given location
|
||||
type Config struct {
|
||||
URI string `json:"uri"`
|
||||
RequestBody string `json:"requestBody"`
|
||||
}
|
||||
|
||||
type mirror struct {
|
||||
r resolver.Resolver
|
||||
}
|
||||
|
||||
// NewParser creates a new mirror configuration annotation parser
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return mirror{r}
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to configure mirror
|
||||
func (a mirror) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
config := &Config{}
|
||||
var err error
|
||||
|
||||
config.URI, err = parser.GetStringAnnotation("mirror-uri", ing)
|
||||
if err != nil {
|
||||
config.URI = ""
|
||||
}
|
||||
|
||||
config.RequestBody, err = parser.GetStringAnnotation("mirror-request-body", ing)
|
||||
if err != nil || config.RequestBody != "off" {
|
||||
config.RequestBody = "on"
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
86
internal/ingress/annotations/mirror/main_test.go
Normal file
86
internal/ingress/annotations/mirror/main_test.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright 2019 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 mirror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
uri := parser.GetAnnotationWithPrefix("mirror-uri")
|
||||
requestBody := parser.GetAnnotationWithPrefix("mirror-request-body")
|
||||
|
||||
ap := NewParser(&resolver.Mock{})
|
||||
if ap == nil {
|
||||
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
annotations map[string]string
|
||||
expected *Config
|
||||
}{
|
||||
{map[string]string{uri: "/mirror", requestBody: ""}, &Config{
|
||||
URI: "/mirror",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
{map[string]string{uri: "/mirror", requestBody: "off"}, &Config{
|
||||
URI: "/mirror",
|
||||
RequestBody: "off",
|
||||
}},
|
||||
{map[string]string{uri: "", requestBody: "ahh"}, &Config{
|
||||
URI: "",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
{map[string]string{uri: "", requestBody: ""}, &Config{
|
||||
URI: "",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
{map[string]string{}, &Config{
|
||||
URI: "",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
{nil, &Config{
|
||||
URI: "",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
}
|
||||
|
||||
ing := &networking.Ingress{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: networking.IngressSpec{},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
result, _ := ap.Parse(ing)
|
||||
fmt.Printf("%t", result)
|
||||
if !reflect.DeepEqual(result, testCase.expected) {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,22 +25,23 @@ import (
|
|||
|
||||
// Config returns the proxy timeout to use in the upstream server/s
|
||||
type Config struct {
|
||||
BodySize string `json:"bodySize"`
|
||||
ConnectTimeout int `json:"connectTimeout"`
|
||||
SendTimeout int `json:"sendTimeout"`
|
||||
ReadTimeout int `json:"readTimeout"`
|
||||
BuffersNumber int `json:"buffersNumber"`
|
||||
BufferSize string `json:"bufferSize"`
|
||||
CookieDomain string `json:"cookieDomain"`
|
||||
CookiePath string `json:"cookiePath"`
|
||||
NextUpstream string `json:"nextUpstream"`
|
||||
NextUpstreamTimeout int `json:"nextUpstreamTimeout"`
|
||||
NextUpstreamTries int `json:"nextUpstreamTries"`
|
||||
ProxyRedirectFrom string `json:"proxyRedirectFrom"`
|
||||
ProxyRedirectTo string `json:"proxyRedirectTo"`
|
||||
RequestBuffering string `json:"requestBuffering"`
|
||||
ProxyBuffering string `json:"proxyBuffering"`
|
||||
ProxyHTTPVersion string `json:"proxyHTTPVersion"`
|
||||
BodySize string `json:"bodySize"`
|
||||
ConnectTimeout int `json:"connectTimeout"`
|
||||
SendTimeout int `json:"sendTimeout"`
|
||||
ReadTimeout int `json:"readTimeout"`
|
||||
BuffersNumber int `json:"buffersNumber"`
|
||||
BufferSize string `json:"bufferSize"`
|
||||
CookieDomain string `json:"cookieDomain"`
|
||||
CookiePath string `json:"cookiePath"`
|
||||
NextUpstream string `json:"nextUpstream"`
|
||||
NextUpstreamTimeout int `json:"nextUpstreamTimeout"`
|
||||
NextUpstreamTries int `json:"nextUpstreamTries"`
|
||||
ProxyRedirectFrom string `json:"proxyRedirectFrom"`
|
||||
ProxyRedirectTo string `json:"proxyRedirectTo"`
|
||||
RequestBuffering string `json:"requestBuffering"`
|
||||
ProxyBuffering string `json:"proxyBuffering"`
|
||||
ProxyHTTPVersion string `json:"proxyHTTPVersion"`
|
||||
ProxyMaxTempFileSize string `json:"proxyMaxTempFileSize"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two Configuration types
|
||||
|
@ -100,6 +101,10 @@ func (l1 *Config) Equal(l2 *Config) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if l1.ProxyMaxTempFileSize != l2.ProxyMaxTempFileSize {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -200,5 +205,10 @@ func (a proxy) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
config.ProxyHTTPVersion = defBackend.ProxyHTTPVersion
|
||||
}
|
||||
|
||||
config.ProxyMaxTempFileSize, err = parser.GetStringAnnotation("proxy-max-temp-file-size", ing)
|
||||
if err != nil {
|
||||
config.ProxyMaxTempFileSize = defBackend.ProxyMaxTempFileSize
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|||
ProxyRequestBuffering: "on",
|
||||
ProxyBuffering: "off",
|
||||
ProxyHTTPVersion: "1.1",
|
||||
ProxyMaxTempFileSize: "1024m",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,6 +102,7 @@ func TestProxy(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = "1.0"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = "128k"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||
|
@ -147,6 +149,9 @@ func TestProxy(t *testing.T) {
|
|||
if p.ProxyHTTPVersion != "1.0" {
|
||||
t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
|
||||
}
|
||||
if p.ProxyMaxTempFileSize != "128k" {
|
||||
t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyWithNoAnnotation(t *testing.T) {
|
||||
|
@ -196,4 +201,7 @@ func TestProxyWithNoAnnotation(t *testing.T) {
|
|||
if p.ProxyHTTPVersion != "1.1" {
|
||||
t.Errorf("expected 1.1 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
|
||||
}
|
||||
if p.ProxyMaxTempFileSize != "1024m" {
|
||||
t.Errorf("expected 1024m as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
if nginxAffinity.Cookie.Name != "INGRESSCOOKIE" {
|
||||
t.Errorf("expected route as session-cookie-name but returned %v", nginxAffinity.Cookie.Name)
|
||||
t.Errorf("expected INGRESSCOOKIE as session-cookie-name but returned %v", nginxAffinity.Cookie.Name)
|
||||
}
|
||||
|
||||
if nginxAffinity.Cookie.Expires != "4500" {
|
||||
|
|
|
@ -18,6 +18,7 @@ package controller
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -63,7 +64,7 @@ func (n *NGINXController) Check(_ *http.Request) error {
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "unexpected error reading /proc directory")
|
||||
}
|
||||
f, err := n.fileSystem.ReadFile(nginx.PID)
|
||||
f, err := ioutil.ReadFile(nginx.PID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unexpected error reading %v", nginx.PID)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/server/healthz"
|
||||
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
|
@ -55,14 +54,10 @@ func TestNginxCheck(t *testing.T) {
|
|||
defer server.Close()
|
||||
server.Start()
|
||||
|
||||
// mock filesystem
|
||||
fs := filesystem.DefaultFs{}
|
||||
|
||||
n := &NGINXController{
|
||||
cfg: &Configuration{
|
||||
ListenPorts: &ngx_config.ListenPorts{},
|
||||
},
|
||||
fileSystem: fs,
|
||||
}
|
||||
|
||||
t.Run("no pid or process", func(t *testing.T) {
|
||||
|
@ -72,8 +67,8 @@ func TestNginxCheck(t *testing.T) {
|
|||
})
|
||||
|
||||
// create pid file
|
||||
fs.MkdirAll("/tmp", file.ReadWriteByUser)
|
||||
pidFile, err := fs.Create(nginx.PID)
|
||||
os.MkdirAll("/tmp", file.ReadWriteByUser)
|
||||
pidFile, err := os.Create(nginx.PID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
@ -33,8 +33,6 @@ import (
|
|||
var (
|
||||
// EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates.
|
||||
EnableSSLChainCompletion = false
|
||||
// EnableDynamicCertificates Dynamically update SSL certificates instead of reloading NGINX
|
||||
EnableDynamicCertificates = true
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -75,6 +73,10 @@ const (
|
|||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
sslProtocols = "TLSv1.2"
|
||||
|
||||
// Disable TLS 1.3 early data
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
|
||||
sslEarlyData = false
|
||||
|
||||
// Time during which a client may reuse the session parameters stored in a cache.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
|
||||
sslSessionTimeout = "10m"
|
||||
|
@ -124,11 +126,6 @@ type Configuration struct {
|
|||
// By default error logs go to /var/log/nginx/error.log
|
||||
ErrorLogPath string `json:"error-log-path,omitempty"`
|
||||
|
||||
// EnableDynamicTLSRecords enables dynamic TLS record sizes
|
||||
// https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency
|
||||
// By default this is enabled
|
||||
EnableDynamicTLSRecords bool `json:"enable-dynamic-tls-records"`
|
||||
|
||||
// EnableModsecurity enables the modsecurity module for NGINX
|
||||
// By default this is disabled
|
||||
EnableModsecurity bool `json:"enable-modsecurity"`
|
||||
|
@ -317,6 +314,10 @@ type Configuration struct {
|
|||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
SSLProtocols string `json:"ssl-protocols,omitempty"`
|
||||
|
||||
// Enables or disable TLS 1.3 early data.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
|
||||
SSLEarlyData bool `json:"ssl-early-data,omitempty"`
|
||||
|
||||
// Enables or disables the use of shared SSL cache among worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
SSLSessionCache bool `json:"ssl-session-cache,omitempty"`
|
||||
|
@ -607,6 +608,13 @@ type Configuration struct {
|
|||
|
||||
// Block all requests with given Referer headers
|
||||
BlockReferers []string `json:"block-referers"`
|
||||
|
||||
// Lua shared dict configuration data / certificate data
|
||||
LuaSharedDicts map[string]int `json:"lua-shared-dicts"`
|
||||
|
||||
// DefaultSSLCertificate holds the default SSL certificate to use in the configuration
|
||||
// It can be the fake certificate or the one behind the flag --default-ssl-certificate
|
||||
DefaultSSLCertificate *ingress.SSLCert `json:"-"`
|
||||
}
|
||||
|
||||
// NewDefault returns the default nginx configuration
|
||||
|
@ -640,7 +648,6 @@ func NewDefault() Configuration {
|
|||
ClientHeaderTimeout: 60,
|
||||
ClientBodyBufferSize: "8k",
|
||||
ClientBodyTimeout: 60,
|
||||
EnableDynamicTLSRecords: true,
|
||||
EnableUnderscoresInHeaders: false,
|
||||
ErrorLogLevel: errorLevel,
|
||||
UseForwardedHeaders: false,
|
||||
|
@ -683,6 +690,7 @@ func NewDefault() Configuration {
|
|||
SSLCiphers: sslCiphers,
|
||||
SSLECDHCurve: "auto",
|
||||
SSLProtocols: sslProtocols,
|
||||
SSLEarlyData: sslEarlyData,
|
||||
SSLSessionCache: true,
|
||||
SSLSessionCacheSize: sslSessionCacheSize,
|
||||
SSLSessionTickets: true,
|
||||
|
@ -720,6 +728,7 @@ func NewDefault() Configuration {
|
|||
LimitRateAfter: 0,
|
||||
ProxyBuffering: "off",
|
||||
ProxyHTTPVersion: "1.1",
|
||||
ProxyMaxTempFileSize: "1024m",
|
||||
},
|
||||
UpstreamKeepaliveConnections: 32,
|
||||
UpstreamKeepaliveTimeout: 60,
|
||||
|
@ -767,25 +776,24 @@ func (cfg Configuration) BuildLogFormatUpstream() string {
|
|||
|
||||
// TemplateConfig contains the nginx configuration to render the file nginx.conf
|
||||
type TemplateConfig struct {
|
||||
ProxySetHeaders map[string]string
|
||||
AddHeaders map[string]string
|
||||
BacklogSize int
|
||||
Backends []*ingress.Backend
|
||||
PassthroughBackends []*ingress.SSLPassthroughBackend
|
||||
Servers []*ingress.Server
|
||||
TCPBackends []ingress.L4Service
|
||||
UDPBackends []ingress.L4Service
|
||||
HealthzURI string
|
||||
Cfg Configuration
|
||||
IsIPV6Enabled bool
|
||||
IsSSLPassthroughEnabled bool
|
||||
NginxStatusIpv4Whitelist []string
|
||||
NginxStatusIpv6Whitelist []string
|
||||
RedirectServers interface{}
|
||||
ListenPorts *ListenPorts
|
||||
PublishService *apiv1.Service
|
||||
EnableDynamicCertificates bool
|
||||
EnableMetrics bool
|
||||
ProxySetHeaders map[string]string
|
||||
AddHeaders map[string]string
|
||||
BacklogSize int
|
||||
Backends []*ingress.Backend
|
||||
PassthroughBackends []*ingress.SSLPassthroughBackend
|
||||
Servers []*ingress.Server
|
||||
TCPBackends []ingress.L4Service
|
||||
UDPBackends []ingress.L4Service
|
||||
HealthzURI string
|
||||
Cfg Configuration
|
||||
IsIPV6Enabled bool
|
||||
IsSSLPassthroughEnabled bool
|
||||
NginxStatusIpv4Whitelist []string
|
||||
NginxStatusIpv6Whitelist []string
|
||||
RedirectServers interface{}
|
||||
ListenPorts *ListenPorts
|
||||
PublishService *apiv1.Service
|
||||
EnableMetrics bool
|
||||
|
||||
PID string
|
||||
StatusSocket string
|
||||
|
|
|
@ -167,7 +167,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
|
|||
}
|
||||
|
||||
err := wait.ExponentialBackoff(retry, func() (bool, error) {
|
||||
err := configureDynamically(pcfg)
|
||||
err := n.configureDynamically(pcfg)
|
||||
if err == nil {
|
||||
klog.V(2).Infof("Dynamic reconfiguration succeeded.")
|
||||
return true, nil
|
||||
|
@ -846,9 +846,9 @@ func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *netw
|
|||
|
||||
// serviceEndpoints returns the upstream servers (Endpoints) associated with a Service.
|
||||
func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingress.Endpoint, error) {
|
||||
svc, err := n.store.GetService(svcKey)
|
||||
|
||||
var upstreams []ingress.Endpoint
|
||||
|
||||
svc, err := n.store.GetService(svcKey)
|
||||
if err != nil {
|
||||
return upstreams, err
|
||||
}
|
||||
|
@ -859,14 +859,26 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
|
|||
if svc.Spec.Type == apiv1.ServiceTypeExternalName {
|
||||
externalPort, err := strconv.Atoi(backendPort)
|
||||
if err != nil {
|
||||
klog.Warningf("Only numeric ports are allowed in ExternalName Services: %q is not a valid port number.", backendPort)
|
||||
return upstreams, nil
|
||||
// check if the service ports have one with backendPort as name
|
||||
found := false
|
||||
for _, port := range svc.Spec.Ports {
|
||||
if port.Name == backendPort {
|
||||
externalPort = int(port.Port)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
klog.Errorf("service %v/%v does not have a port with name %v", svc.Namespace, svc.Namespace, backendPort)
|
||||
return upstreams, nil
|
||||
}
|
||||
}
|
||||
|
||||
servicePort := apiv1.ServicePort{
|
||||
Protocol: "TCP",
|
||||
Port: int32(externalPort),
|
||||
TargetPort: intstr.FromString(backendPort),
|
||||
TargetPort: intstr.FromInt(externalPort),
|
||||
}
|
||||
endps := getEndpoints(svc, &servicePort, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
|
||||
if len(endps) == 0 {
|
||||
|
@ -897,19 +909,18 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
|
|||
return upstreams, nil
|
||||
}
|
||||
|
||||
// overridePemFileNameAndPemSHA should only be called when EnableDynamicCertificates
|
||||
// ideally this function should not exist, the only reason why we use it is that
|
||||
// we rely on PemFileName in nginx.tmpl to configure SSL directives
|
||||
// and PemSHA to force reload
|
||||
func (n *NGINXController) overridePemFileNameAndPemSHA(cert *ingress.SSLCert) {
|
||||
// TODO(elvinefendi): It is not great but we currently use PemFileName to decide whether SSL needs to be configured
|
||||
// in nginx configuration or not. The whole thing needs to be refactored, we should rely on a proper
|
||||
// signal to configure SSL, not PemFileName.
|
||||
cert.PemFileName = n.cfg.FakeCertificate.PemFileName
|
||||
func (n *NGINXController) getDefaultSSLCertificate() *ingress.SSLCert {
|
||||
// read custom default SSL certificate, fall back to generated default certificate
|
||||
if n.cfg.DefaultSSLCertificate != "" {
|
||||
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
|
||||
if err == nil {
|
||||
return certificate
|
||||
}
|
||||
|
||||
// TODO(elvinefendi): This is again another hacky way of avoiding Nginx reload when certificate
|
||||
// changes in dynamic SSL mode since FakeCertificate never changes.
|
||||
cert.PemSHA = n.cfg.FakeCertificate.PemSHA
|
||||
klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err)
|
||||
}
|
||||
|
||||
return n.cfg.FakeCertificate
|
||||
}
|
||||
|
||||
// createServers builds a map of host name to Server structs from a map of
|
||||
|
@ -924,42 +935,28 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
|
||||
bdef := n.store.GetDefaultBackend()
|
||||
ngxProxy := proxy.Config{
|
||||
BodySize: bdef.ProxyBodySize,
|
||||
ConnectTimeout: bdef.ProxyConnectTimeout,
|
||||
SendTimeout: bdef.ProxySendTimeout,
|
||||
ReadTimeout: bdef.ProxyReadTimeout,
|
||||
BuffersNumber: bdef.ProxyBuffersNumber,
|
||||
BufferSize: bdef.ProxyBufferSize,
|
||||
CookieDomain: bdef.ProxyCookieDomain,
|
||||
CookiePath: bdef.ProxyCookiePath,
|
||||
NextUpstream: bdef.ProxyNextUpstream,
|
||||
NextUpstreamTimeout: bdef.ProxyNextUpstreamTimeout,
|
||||
NextUpstreamTries: bdef.ProxyNextUpstreamTries,
|
||||
RequestBuffering: bdef.ProxyRequestBuffering,
|
||||
ProxyRedirectFrom: bdef.ProxyRedirectFrom,
|
||||
ProxyBuffering: bdef.ProxyBuffering,
|
||||
ProxyHTTPVersion: bdef.ProxyHTTPVersion,
|
||||
}
|
||||
|
||||
defaultCertificate := n.cfg.FakeCertificate
|
||||
|
||||
// read custom default SSL certificate, fall back to generated default certificate
|
||||
if n.cfg.DefaultSSLCertificate != "" {
|
||||
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
|
||||
if err == nil {
|
||||
defaultCertificate = certificate
|
||||
if ngx_config.EnableDynamicCertificates {
|
||||
n.overridePemFileNameAndPemSHA(defaultCertificate)
|
||||
}
|
||||
} else {
|
||||
klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err)
|
||||
}
|
||||
BodySize: bdef.ProxyBodySize,
|
||||
ConnectTimeout: bdef.ProxyConnectTimeout,
|
||||
SendTimeout: bdef.ProxySendTimeout,
|
||||
ReadTimeout: bdef.ProxyReadTimeout,
|
||||
BuffersNumber: bdef.ProxyBuffersNumber,
|
||||
BufferSize: bdef.ProxyBufferSize,
|
||||
CookieDomain: bdef.ProxyCookieDomain,
|
||||
CookiePath: bdef.ProxyCookiePath,
|
||||
NextUpstream: bdef.ProxyNextUpstream,
|
||||
NextUpstreamTimeout: bdef.ProxyNextUpstreamTimeout,
|
||||
NextUpstreamTries: bdef.ProxyNextUpstreamTries,
|
||||
RequestBuffering: bdef.ProxyRequestBuffering,
|
||||
ProxyRedirectFrom: bdef.ProxyRedirectFrom,
|
||||
ProxyBuffering: bdef.ProxyBuffering,
|
||||
ProxyHTTPVersion: bdef.ProxyHTTPVersion,
|
||||
ProxyMaxTempFileSize: bdef.ProxyMaxTempFileSize,
|
||||
}
|
||||
|
||||
// initialize default server and root location
|
||||
servers[defServerName] = &ingress.Server{
|
||||
Hostname: defServerName,
|
||||
SSLCert: *defaultCertificate,
|
||||
SSLCert: n.getDefaultSSLCertificate(),
|
||||
Locations: []*ingress.Location{
|
||||
{
|
||||
Path: rootLocation,
|
||||
|
@ -1023,6 +1020,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
if host == "" {
|
||||
host = defServerName
|
||||
}
|
||||
|
||||
if _, ok := servers[host]; ok {
|
||||
// server already configured
|
||||
continue
|
||||
|
@ -1090,7 +1088,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
}
|
||||
|
||||
// only add a certificate if the server does not have one previously configured
|
||||
if servers[host].SSLCert.PemFileName != "" {
|
||||
if servers[host].SSLCert != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1100,10 +1098,8 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
}
|
||||
|
||||
tlsSecretName := extractTLSSecretName(host, ing, n.store.GetLocalSSLCert)
|
||||
|
||||
if tlsSecretName == "" {
|
||||
klog.V(3).Infof("Host %q is listed in the TLS section but secretName is empty. Using default certificate.", host)
|
||||
servers[host].SSLCert = *defaultCertificate
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1111,7 +1107,6 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
cert, err := n.store.GetLocalSSLCert(secrKey)
|
||||
if err != nil {
|
||||
klog.Warningf("Error getting SSL certificate %q: %v. Using default certificate", secrKey, err)
|
||||
servers[host].SSLCert = *defaultCertificate
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1126,16 +1121,11 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
klog.Warningf("SSL certificate %q does not contain a Common Name or Subject Alternative Name for server %q: %v",
|
||||
secrKey, host, err)
|
||||
klog.Warningf("Using default certificate")
|
||||
servers[host].SSLCert = *defaultCertificate
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ngx_config.EnableDynamicCertificates {
|
||||
n.overridePemFileNameAndPemSHA(cert)
|
||||
}
|
||||
|
||||
servers[host].SSLCert = *cert
|
||||
servers[host].SSLCert = cert
|
||||
|
||||
if cert.ExpireTime.Before(time.Now().Add(240 * time.Hour)) {
|
||||
klog.Warningf("SSL certificate for server %q is about to expire (%v)", host, cert.ExpireTime)
|
||||
|
@ -1176,9 +1166,11 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
|
|||
loc.InfluxDB = anns.InfluxDB
|
||||
loc.DefaultBackend = anns.DefaultBackend
|
||||
loc.BackendProtocol = anns.BackendProtocol
|
||||
loc.FastCGI = anns.FastCGI
|
||||
loc.CustomHTTPErrors = anns.CustomHTTPErrors
|
||||
loc.ModSecurity = anns.ModSecurity
|
||||
loc.Satisfy = anns.Satisfy
|
||||
loc.Mirror = anns.Mirror
|
||||
}
|
||||
|
||||
// OK to merge canary ingresses iff there exists one or more ingresses to potentially merge into
|
||||
|
|
|
@ -37,7 +37,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
|
||||
|
@ -1109,11 +1108,6 @@ func newNGINXController(t *testing.T) *NGINXController {
|
|||
t.Fatalf("error creating the configuration map: %v", err)
|
||||
}
|
||||
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
storer := store.New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
@ -1122,12 +1116,11 @@ func newNGINXController(t *testing.T) *NGINXController {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
channels.NewRingChannel(10),
|
||||
pod,
|
||||
false)
|
||||
|
||||
sslCert := ssl.GetFakeSSLCert(fs)
|
||||
sslCert := ssl.GetFakeSSLCert()
|
||||
config := &Configuration{
|
||||
FakeCertificate: sslCert,
|
||||
ListenPorts: &ngx_config.ListenPorts{
|
||||
|
@ -1136,10 +1129,9 @@ func newNGINXController(t *testing.T) *NGINXController {
|
|||
}
|
||||
|
||||
return &NGINXController{
|
||||
store: storer,
|
||||
cfg: config,
|
||||
command: NewNginxCommand(),
|
||||
fileSystem: fs,
|
||||
store: storer,
|
||||
cfg: config,
|
||||
command: NewNginxCommand(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -44,7 +45,6 @@ import (
|
|||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||
|
||||
adm_controler "k8s.io/ingress-nginx/internal/admission/controller"
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
|
@ -69,12 +69,8 @@ const (
|
|||
tempNginxPattern = "nginx-cfg"
|
||||
)
|
||||
|
||||
var (
|
||||
tmplPath = "/etc/nginx/template/nginx.tmpl"
|
||||
)
|
||||
|
||||
// NewNGINXController creates a new NGINX Ingress controller.
|
||||
func NewNGINXController(config *Configuration, mc metric.Collector, fs file.Filesystem) *NGINXController {
|
||||
func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXController {
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(klog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{
|
||||
|
@ -102,8 +98,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
|
|||
|
||||
stopLock: &sync.Mutex{},
|
||||
|
||||
fileSystem: fs,
|
||||
|
||||
runningConfig: new(ingress.Configuration),
|
||||
|
||||
Proxy: &TCPProxy{},
|
||||
|
@ -135,7 +129,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
|
|||
config.DefaultSSLCertificate,
|
||||
config.ResyncPeriod,
|
||||
config.Client,
|
||||
fs,
|
||||
n.updateCh,
|
||||
pod,
|
||||
config.DisableCatchAll)
|
||||
|
@ -156,7 +149,7 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
|
|||
}
|
||||
|
||||
onTemplateChange := func() {
|
||||
template, err := ngx_template.NewTemplate(tmplPath, fs)
|
||||
template, err := ngx_template.NewTemplate(nginx.TemplatePath)
|
||||
if err != nil {
|
||||
// this error is different from the rest because it must be clear why nginx is not working
|
||||
klog.Errorf(`
|
||||
|
@ -172,21 +165,16 @@ Error loading new template: %v
|
|||
n.syncQueue.EnqueueTask(task.GetDummyObject("template-change"))
|
||||
}
|
||||
|
||||
ngxTpl, err := ngx_template.NewTemplate(tmplPath, fs)
|
||||
ngxTpl, err := ngx_template.NewTemplate(nginx.TemplatePath)
|
||||
if err != nil {
|
||||
klog.Fatalf("Invalid NGINX configuration template: %v", err)
|
||||
}
|
||||
|
||||
n.t = ngxTpl
|
||||
|
||||
if _, ok := fs.(filesystem.DefaultFs); !ok {
|
||||
// do not setup watchers on tests
|
||||
return n
|
||||
}
|
||||
|
||||
_, err = watch.NewFileWatcher(tmplPath, onTemplateChange)
|
||||
_, err = watch.NewFileWatcher(nginx.TemplatePath, onTemplateChange)
|
||||
if err != nil {
|
||||
klog.Fatalf("Error creating file watcher for %v: %v", tmplPath, err)
|
||||
klog.Fatalf("Error creating file watcher for %v: %v", nginx.TemplatePath, err)
|
||||
}
|
||||
|
||||
filesToWatch := []string{}
|
||||
|
@ -260,8 +248,6 @@ type NGINXController struct {
|
|||
|
||||
store store.Storer
|
||||
|
||||
fileSystem filesystem.Filesystem
|
||||
|
||||
metricCollector metric.Collector
|
||||
|
||||
validationWebhookServer *http.Server
|
||||
|
@ -582,7 +568,7 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
|
|||
nsSecName := strings.Replace(secretName, "/", "-", -1)
|
||||
dh, ok := secret.Data["dhparam.pem"]
|
||||
if ok {
|
||||
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh, n.fileSystem)
|
||||
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh)
|
||||
if err != nil {
|
||||
klog.Warningf("Error adding or updating dhparam file %v: %v", nsSecName, err)
|
||||
} else {
|
||||
|
@ -594,25 +580,26 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
|
|||
|
||||
cfg.SSLDHParam = sslDHParam
|
||||
|
||||
cfg.DefaultSSLCertificate = n.getDefaultSSLCertificate()
|
||||
|
||||
tc := ngx_config.TemplateConfig{
|
||||
ProxySetHeaders: setHeaders,
|
||||
AddHeaders: addHeaders,
|
||||
BacklogSize: sysctlSomaxconn(),
|
||||
Backends: ingressCfg.Backends,
|
||||
PassthroughBackends: ingressCfg.PassthroughBackends,
|
||||
Servers: ingressCfg.Servers,
|
||||
TCPBackends: ingressCfg.TCPEndpoints,
|
||||
UDPBackends: ingressCfg.UDPEndpoints,
|
||||
Cfg: cfg,
|
||||
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
||||
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
|
||||
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
|
||||
RedirectServers: buildRedirects(ingressCfg.Servers),
|
||||
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
|
||||
ListenPorts: n.cfg.ListenPorts,
|
||||
PublishService: n.GetPublishService(),
|
||||
EnableDynamicCertificates: ngx_config.EnableDynamicCertificates,
|
||||
EnableMetrics: n.cfg.EnableMetrics,
|
||||
ProxySetHeaders: setHeaders,
|
||||
AddHeaders: addHeaders,
|
||||
BacklogSize: sysctlSomaxconn(),
|
||||
Backends: ingressCfg.Backends,
|
||||
PassthroughBackends: ingressCfg.PassthroughBackends,
|
||||
Servers: ingressCfg.Servers,
|
||||
TCPBackends: ingressCfg.TCPEndpoints,
|
||||
UDPBackends: ingressCfg.UDPEndpoints,
|
||||
Cfg: cfg,
|
||||
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
||||
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
|
||||
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
|
||||
RedirectServers: buildRedirects(ingressCfg.Servers),
|
||||
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
|
||||
ListenPorts: n.cfg.ListenPorts,
|
||||
PublishService: n.GetPublishService(),
|
||||
EnableMetrics: n.cfg.EnableMetrics,
|
||||
|
||||
HealthzURI: nginx.HealthPath,
|
||||
PID: nginx.PID,
|
||||
|
@ -698,7 +685,12 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
|||
|
||||
diffOutput, err := exec.Command("diff", "-u", cfgPath, tmpfile.Name()).CombinedOutput()
|
||||
if err != nil {
|
||||
klog.Warningf("Failed to executing diff command: %v", err)
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
ws := exitError.Sys().(syscall.WaitStatus)
|
||||
if ws.ExitStatus() == 2 {
|
||||
klog.Warningf("Failed to executing diff command: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
klog.Infof("NGINX configuration diff:\n%v", string(diffOutput))
|
||||
|
@ -800,7 +792,9 @@ func clearCertificates(config *ingress.Configuration) {
|
|||
var clearedServers []*ingress.Server
|
||||
for _, server := range config.Servers {
|
||||
copyOfServer := *server
|
||||
copyOfServer.SSLCert = ingress.SSLCert{PemFileName: copyOfServer.SSLCert.PemFileName}
|
||||
if copyOfServer.SSLCert != nil {
|
||||
copyOfServer.SSLCert = &ingress.SSLCert{PemFileName: copyOfServer.SSLCert.PemFileName}
|
||||
}
|
||||
clearedServers = append(clearedServers, ©OfServer)
|
||||
}
|
||||
config.Servers = clearedServers
|
||||
|
@ -848,20 +842,112 @@ func (n *NGINXController) IsDynamicConfigurationEnough(pcfg *ingress.Configurati
|
|||
copyOfRunningConfig.ControllerPodsCount = 0
|
||||
copyOfPcfg.ControllerPodsCount = 0
|
||||
|
||||
if ngx_config.EnableDynamicCertificates {
|
||||
clearCertificates(©OfRunningConfig)
|
||||
clearCertificates(©OfPcfg)
|
||||
}
|
||||
clearCertificates(©OfRunningConfig)
|
||||
clearCertificates(©OfPcfg)
|
||||
|
||||
return copyOfRunningConfig.Equal(©OfPcfg)
|
||||
}
|
||||
|
||||
// configureDynamically encodes new Backends in JSON format and POSTs the
|
||||
// payload to an internal HTTP endpoint handled by Lua.
|
||||
func configureDynamically(pcfg *ingress.Configuration) error {
|
||||
backends := make([]*ingress.Backend, len(pcfg.Backends))
|
||||
func (n *NGINXController) configureDynamically(pcfg *ingress.Configuration) error {
|
||||
backendsChanged := !reflect.DeepEqual(n.runningConfig.Backends, pcfg.Backends)
|
||||
if backendsChanged {
|
||||
err := configureBackends(pcfg.Backends)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, backend := range pcfg.Backends {
|
||||
streamConfigurationChanged := !reflect.DeepEqual(n.runningConfig.TCPEndpoints, pcfg.TCPEndpoints) || !reflect.DeepEqual(n.runningConfig.UDPEndpoints, pcfg.UDPEndpoints)
|
||||
if streamConfigurationChanged {
|
||||
err := updateStreamConfiguration(pcfg.TCPEndpoints, pcfg.UDPEndpoints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if n.runningConfig.ControllerPodsCount != pcfg.ControllerPodsCount {
|
||||
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
|
||||
ControllerPodsCount: pcfg.ControllerPodsCount,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if statusCode != http.StatusCreated {
|
||||
return fmt.Errorf("unexpected error code: %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
serversChanged := !reflect.DeepEqual(n.runningConfig.Servers, pcfg.Servers)
|
||||
if serversChanged {
|
||||
err := configureCertificates(pcfg.Servers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateStreamConfiguration(TCPEndpoints []ingress.L4Service, UDPEndpoints []ingress.L4Service) error {
|
||||
streams := make([]ingress.Backend, 0)
|
||||
for _, ep := range TCPEndpoints {
|
||||
var service *apiv1.Service
|
||||
if ep.Service != nil {
|
||||
service = &apiv1.Service{Spec: ep.Service.Spec}
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("tcp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
|
||||
streams = append(streams, ingress.Backend{
|
||||
Name: key,
|
||||
Endpoints: ep.Endpoints,
|
||||
Port: intstr.FromInt(ep.Port),
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
for _, ep := range UDPEndpoints {
|
||||
var service *apiv1.Service
|
||||
if ep.Service != nil {
|
||||
service = &apiv1.Service{Spec: ep.Service.Spec}
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("udp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
|
||||
streams = append(streams, ingress.Backend{
|
||||
Name: key,
|
||||
Endpoints: ep.Endpoints,
|
||||
Port: intstr.FromInt(ep.Port),
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", nginx.StreamSocket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
buf, err := json.Marshal(streams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(conn, "\r\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureBackends(rawBackends []*ingress.Backend) error {
|
||||
backends := make([]*ingress.Backend, len(rawBackends))
|
||||
|
||||
for i, backend := range rawBackends {
|
||||
var service *apiv1.Service
|
||||
if backend.Service != nil {
|
||||
service = &apiv1.Service{Spec: backend.Service.Spec}
|
||||
|
@ -900,99 +986,22 @@ func configureDynamically(pcfg *ingress.Configuration) error {
|
|||
return fmt.Errorf("unexpected error code: %d", statusCode)
|
||||
}
|
||||
|
||||
streams := make([]ingress.Backend, 0)
|
||||
for _, ep := range pcfg.TCPEndpoints {
|
||||
var service *apiv1.Service
|
||||
if ep.Service != nil {
|
||||
service = &apiv1.Service{Spec: ep.Service.Spec}
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("tcp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
|
||||
streams = append(streams, ingress.Backend{
|
||||
Name: key,
|
||||
Endpoints: ep.Endpoints,
|
||||
Port: intstr.FromInt(ep.Port),
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
for _, ep := range pcfg.UDPEndpoints {
|
||||
var service *apiv1.Service
|
||||
if ep.Service != nil {
|
||||
service = &apiv1.Service{Spec: ep.Service.Spec}
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("udp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
|
||||
streams = append(streams, ingress.Backend{
|
||||
Name: key,
|
||||
Endpoints: ep.Endpoints,
|
||||
Port: intstr.FromInt(ep.Port),
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
|
||||
err = updateStreamConfiguration(streams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statusCode, _, err = nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
|
||||
ControllerPodsCount: pcfg.ControllerPodsCount,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if statusCode != http.StatusCreated {
|
||||
return fmt.Errorf("unexpected error code: %d", statusCode)
|
||||
}
|
||||
|
||||
if ngx_config.EnableDynamicCertificates {
|
||||
err = configureCertificates(pcfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateStreamConfiguration(streams []ingress.Backend) error {
|
||||
conn, err := net.Dial("unix", nginx.StreamSocket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
buf, err := json.Marshal(streams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(conn, "\r\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
|
||||
// that is handled by Lua
|
||||
func configureCertificates(pcfg *ingress.Configuration) error {
|
||||
var servers []*ingress.Server
|
||||
func configureCertificates(rawServers []*ingress.Server) error {
|
||||
servers := make([]*ingress.Server, 0)
|
||||
|
||||
for _, server := range pcfg.Servers {
|
||||
if server.SSLCert.PemCertKey == "" {
|
||||
for _, server := range rawServers {
|
||||
if server.SSLCert == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
servers = append(servers, &ingress.Server{
|
||||
Hostname: server.Hostname,
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: server.SSLCert.PemCertKey,
|
||||
},
|
||||
})
|
||||
|
@ -1000,22 +1009,22 @@ func configureCertificates(pcfg *ingress.Configuration) error {
|
|||
if server.Alias != "" && ssl.IsValidHostname(server.Alias, server.SSLCert.CN) {
|
||||
servers = append(servers, &ingress.Server{
|
||||
Hostname: server.Alias,
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: server.SSLCert.PemCertKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
redirects := buildRedirects(pcfg.Servers)
|
||||
redirects := buildRedirects(rawServers)
|
||||
for _, redirect := range redirects {
|
||||
if redirect.SSLCert.PemCertKey == "" {
|
||||
if redirect.SSLCert == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
servers = append(servers, &ingress.Server{
|
||||
Hostname: redirect.From,
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: redirect.SSLCert.PemCertKey,
|
||||
},
|
||||
})
|
||||
|
@ -1126,7 +1135,7 @@ func cleanTempNginxCfg() error {
|
|||
type redirect struct {
|
||||
From string
|
||||
To string
|
||||
SSLCert ingress.SSLCert
|
||||
SSLCert *ingress.SSLCert
|
||||
}
|
||||
|
||||
func buildRedirects(servers []*ingress.Server) []*redirect {
|
||||
|
@ -1170,7 +1179,7 @@ func buildRedirects(servers []*ingress.Server) []*redirect {
|
|||
To: to,
|
||||
}
|
||||
|
||||
if srv.SSLCert.PemSHA != "" {
|
||||
if srv.SSLCert != nil {
|
||||
if ssl.IsValidHostname(from, srv.SSLCert.CN) {
|
||||
r.SSLCert = srv.SSLCert
|
||||
} else {
|
||||
|
|
|
@ -32,14 +32,10 @@ import (
|
|||
apiv1 "k8s.io/api/core/v1"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
"k8s.io/ingress-nginx/internal/nginx"
|
||||
)
|
||||
|
||||
func TestIsDynamicConfigurationEnough(t *testing.T) {
|
||||
ngx_config.EnableDynamicCertificates = false
|
||||
defer func() { ngx_config.EnableDynamicCertificates = true }()
|
||||
|
||||
backends := []*ingress.Backend{{
|
||||
Name: "fakenamespace-myapp-80",
|
||||
Endpoints: []ingress.Endpoint{
|
||||
|
@ -62,7 +58,7 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
|
|||
Backend: "fakenamespace-myapp-80",
|
||||
},
|
||||
},
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: "fake-certificate",
|
||||
},
|
||||
}}
|
||||
|
@ -98,8 +94,6 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
|
|||
Servers: servers,
|
||||
}
|
||||
|
||||
ngx_config.EnableDynamicCertificates = true
|
||||
|
||||
if !n.IsDynamicConfigurationEnough(newConfig) {
|
||||
t.Errorf("Expected to be dynamically configurable when only backends change")
|
||||
}
|
||||
|
@ -112,7 +106,7 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
|
|||
Backend: "fakenamespace-myapp-80",
|
||||
},
|
||||
},
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: "fake-certificate",
|
||||
},
|
||||
}}
|
||||
|
@ -168,6 +162,13 @@ func TestConfigureDynamically(t *testing.T) {
|
|||
defer streamListener.Close()
|
||||
defer os.Remove(nginx.StreamSocket)
|
||||
|
||||
endpointStats := map[string]int{"/configuration/backends": 0, "/configuration/general": 0, "/configuration/servers": 0}
|
||||
resetEndpointStats := func() {
|
||||
for k := range endpointStats {
|
||||
endpointStats[k] = 0
|
||||
}
|
||||
}
|
||||
|
||||
server := &httptest.Server{
|
||||
Listener: listener,
|
||||
Config: &http.Server{
|
||||
|
@ -184,6 +185,8 @@ func TestConfigureDynamically(t *testing.T) {
|
|||
}
|
||||
body := string(b)
|
||||
|
||||
endpointStats[r.URL.Path] += 1
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/configuration/backends":
|
||||
{
|
||||
|
@ -201,6 +204,12 @@ func TestConfigureDynamically(t *testing.T) {
|
|||
t.Errorf("controllerPodsCount should be present in JSON content: %v", body)
|
||||
}
|
||||
}
|
||||
case "/configuration/servers":
|
||||
{
|
||||
if !strings.Contains(body, "[]") {
|
||||
t.Errorf("controllerPodsCount should be present in JSON content: %v", body)
|
||||
}
|
||||
}
|
||||
default:
|
||||
t.Errorf("unknown request to %s", r.URL.Path)
|
||||
}
|
||||
|
@ -246,17 +255,67 @@ func TestConfigureDynamically(t *testing.T) {
|
|||
ControllerPodsCount: 2,
|
||||
}
|
||||
|
||||
ngx_config.EnableDynamicCertificates = false
|
||||
defer func() { ngx_config.EnableDynamicCertificates = true }()
|
||||
n := &NGINXController{
|
||||
runningConfig: &ingress.Configuration{},
|
||||
cfg: &Configuration{},
|
||||
}
|
||||
|
||||
err = configureDynamically(commonConfig)
|
||||
err = n.configureDynamically(commonConfig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic configuration: %v", err)
|
||||
}
|
||||
|
||||
if commonConfig.Backends[0].Endpoints[0].Target != target {
|
||||
t.Errorf("unexpected change in the configuration object after configureDynamically invocation")
|
||||
}
|
||||
for endpoint, count := range endpointStats {
|
||||
if count != 1 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 1, count)
|
||||
}
|
||||
}
|
||||
|
||||
resetEndpointStats()
|
||||
n.runningConfig.Backends = backends
|
||||
err = n.configureDynamically(commonConfig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic configuration: %v", err)
|
||||
}
|
||||
for endpoint, count := range endpointStats {
|
||||
if endpoint == "/configuration/backends" {
|
||||
if count != 0 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 0, count)
|
||||
}
|
||||
} else if count != 1 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 1, count)
|
||||
}
|
||||
}
|
||||
|
||||
resetEndpointStats()
|
||||
n.runningConfig.Servers = servers
|
||||
err = n.configureDynamically(commonConfig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic configuration: %v", err)
|
||||
}
|
||||
if count, _ := endpointStats["/configuration/backends"]; count != 0 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/backends", 0, count)
|
||||
}
|
||||
if count, _ := endpointStats["/configuration/servers"]; count != 0 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/servers", 0, count)
|
||||
}
|
||||
if count, _ := endpointStats["/configuration/general"]; count != 1 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/general", 0, count)
|
||||
}
|
||||
|
||||
resetEndpointStats()
|
||||
n.runningConfig.ControllerPodsCount = commonConfig.ControllerPodsCount
|
||||
err = n.configureDynamically(commonConfig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic configuration: %v", err)
|
||||
}
|
||||
for endpoint, count := range endpointStats {
|
||||
if count != 0 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 0, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureCertificates(t *testing.T) {
|
||||
|
@ -276,7 +335,7 @@ func TestConfigureCertificates(t *testing.T) {
|
|||
|
||||
servers := []*ingress.Server{{
|
||||
Hostname: "myapp.fake",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: "fake-cert",
|
||||
},
|
||||
}}
|
||||
|
@ -316,11 +375,7 @@ func TestConfigureCertificates(t *testing.T) {
|
|||
defer server.Close()
|
||||
server.Start()
|
||||
|
||||
commonConfig := &ingress.Configuration{
|
||||
Servers: servers,
|
||||
}
|
||||
|
||||
err = configureCertificates(commonConfig)
|
||||
err = configureCertificates(servers)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic certificate configuration: %v", err)
|
||||
}
|
||||
|
|
|
@ -17,17 +17,20 @@ limitations under the License.
|
|||
package store
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
"k8s.io/ingress-nginx/internal/net/ssl"
|
||||
)
|
||||
|
||||
|
@ -39,7 +42,6 @@ func (s *k8sStore) syncSecret(key string) {
|
|||
|
||||
klog.V(3).Infof("Syncing Secret %q", key)
|
||||
|
||||
// TODO: getPemCertificate should not write to disk to avoid unnecessary overhead
|
||||
cert, err := s.getPemCertificate(key)
|
||||
if err != nil {
|
||||
if !isErrSecretForAuth(err) {
|
||||
|
@ -92,6 +94,7 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
if cert == nil {
|
||||
return nil, fmt.Errorf("key 'tls.crt' missing from Secret %q", secretName)
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
return nil, fmt.Errorf("key 'tls.key' missing from Secret %q", secretName)
|
||||
}
|
||||
|
@ -101,15 +104,16 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
|
||||
}
|
||||
|
||||
if !ngx_config.EnableDynamicCertificates || len(ca) > 0 {
|
||||
err = ssl.StoreSSLCertOnDisk(s.filesystem, nsSecName, sslCert)
|
||||
if len(ca) > 0 {
|
||||
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while storing certificate and key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ca) > 0 {
|
||||
err = ssl.ConfigureCACertWithCertAndKey(s.filesystem, nsSecName, ca, sslCert)
|
||||
sslCert.CAFileName = path
|
||||
sslCert.CASHA = file.SHA1(path)
|
||||
|
||||
err = ssl.ConfigureCACertWithCertAndKey(nsSecName, ca, sslCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
|
||||
}
|
||||
|
@ -120,14 +124,13 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
msg += " and authentication"
|
||||
}
|
||||
klog.V(3).Info(msg)
|
||||
|
||||
} else if len(ca) > 0 {
|
||||
sslCert, err = ssl.CreateCACert(ca)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
|
||||
}
|
||||
|
||||
err = ssl.ConfigureCACert(s.filesystem, nsSecName, ca, sslCert)
|
||||
err = ssl.ConfigureCACert(nsSecName, ca, sslCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
|
||||
}
|
||||
|
@ -135,7 +138,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
// makes this secret in 'syncSecret' to be used for Certificate Authentication
|
||||
// this does not enable Certificate Authentication
|
||||
klog.V(3).Infof("Configuring Secret %q for TLS authentication", secretName)
|
||||
|
||||
} else {
|
||||
if auth != nil {
|
||||
return nil, ErrSecretForAuth
|
||||
|
@ -147,6 +149,21 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
sslCert.Name = secret.Name
|
||||
sslCert.Namespace = secret.Namespace
|
||||
|
||||
hasher := sha1.New()
|
||||
hasher.Write(sslCert.Certificate.Raw)
|
||||
|
||||
sslCert.PemSHA = hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
// the default SSL certificate needs to be present on disk
|
||||
if secretName == s.defaultSSLCertificate {
|
||||
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "storing default SSL Certificate")
|
||||
}
|
||||
|
||||
sslCert.PemFileName = path
|
||||
}
|
||||
|
||||
return sslCert, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -152,9 +152,9 @@ func (e NotExistsError) Error() string {
|
|||
|
||||
// Run initiates the synchronization of the informers against the API server.
|
||||
func (i *Informer) Run(stopCh chan struct{}) {
|
||||
go i.Secret.Run(stopCh)
|
||||
go i.Endpoint.Run(stopCh)
|
||||
go i.Service.Run(stopCh)
|
||||
go i.Secret.Run(stopCh)
|
||||
go i.ConfigMap.Run(stopCh)
|
||||
go i.Pod.Run(stopCh)
|
||||
|
||||
|
@ -165,6 +165,7 @@ func (i *Informer) Run(stopCh chan struct{}) {
|
|||
i.Service.HasSynced,
|
||||
i.Secret.HasSynced,
|
||||
i.ConfigMap.HasSynced,
|
||||
i.Pod.HasSynced,
|
||||
) {
|
||||
runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
|
||||
}
|
||||
|
@ -208,8 +209,6 @@ type k8sStore struct {
|
|||
// secret in the annotations.
|
||||
secretIngressMap ObjectRefMap
|
||||
|
||||
filesystem file.Filesystem
|
||||
|
||||
// updateCh
|
||||
updateCh *channels.RingChannel
|
||||
|
||||
|
@ -229,7 +228,6 @@ func New(
|
|||
namespace, configmap, tcp, udp, defaultSSLCertificate string,
|
||||
resyncPeriod time.Duration,
|
||||
client clientset.Interface,
|
||||
fs file.Filesystem,
|
||||
updateCh *channels.RingChannel,
|
||||
pod *k8s.PodInfo,
|
||||
disableCatchAll bool) Storer {
|
||||
|
@ -238,7 +236,6 @@ func New(
|
|||
informers: &Informer{},
|
||||
listers: &Lister{},
|
||||
sslStore: NewSSLCertTracker(),
|
||||
filesystem: fs,
|
||||
updateCh: updateCh,
|
||||
backendConfig: ngx_config.NewDefault(),
|
||||
syncSecretMu: &sync.Mutex{},
|
||||
|
@ -494,6 +491,7 @@ func New(
|
|||
}
|
||||
store.syncIngress(ing)
|
||||
}
|
||||
|
||||
updateCh.In() <- Event{
|
||||
Type: DeleteEvent,
|
||||
Obj: obj,
|
||||
|
@ -814,7 +812,7 @@ func (s *k8sStore) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error
|
|||
return &resolver.AuthSSLCert{
|
||||
Secret: name,
|
||||
CAFileName: cert.CAFileName,
|
||||
PemSHA: cert.PemSHA,
|
||||
CASHA: cert.CASHA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -38,10 +38,8 @@ import (
|
|||
"k8s.io/client-go/tools/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
"k8s.io/ingress-nginx/internal/k8s"
|
||||
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||
)
|
||||
|
@ -87,7 +85,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
@ -96,7 +93,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
@ -167,7 +163,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
@ -176,7 +171,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
@ -317,7 +311,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
@ -326,7 +319,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
@ -423,7 +415,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
@ -432,7 +423,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
@ -512,7 +502,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
@ -521,7 +510,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
@ -623,7 +611,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
@ -632,7 +619,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
@ -701,39 +687,6 @@ func TestStore(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("error creating secret: %v", err)
|
||||
}
|
||||
|
||||
t.Run("should exists a secret in the local store and filesystem", func(t *testing.T) {
|
||||
ngx_config.EnableDynamicCertificates = false
|
||||
defer func() { ngx_config.EnableDynamicCertificates = true }()
|
||||
|
||||
err := framework.WaitForSecretInNamespace(clientSet, ns, name)
|
||||
if err != nil {
|
||||
t.Errorf("error waiting for secret: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
pemFile := fmt.Sprintf("%v/%v-%v.pem", file.DefaultSSLDirectory, ns, name)
|
||||
err = framework.WaitForFileInFS(pemFile, fs)
|
||||
if err != nil {
|
||||
t.Errorf("error waiting for file to exist on the file system: %v", err)
|
||||
}
|
||||
|
||||
secretName := fmt.Sprintf("%v/%v", ns, name)
|
||||
sslCert, err := storer.GetLocalSSLCert(secretName)
|
||||
if err != nil {
|
||||
t.Errorf("error reading local secret %v: %v", secretName, err)
|
||||
}
|
||||
|
||||
if sslCert == nil {
|
||||
t.Errorf("expected a secret but none returned")
|
||||
}
|
||||
|
||||
pemSHA := file.SHA1(pemFile)
|
||||
if sslCert.PemSHA != pemSHA {
|
||||
t.Errorf("SHA of secret on disk differs from local secret store (%v != %v)", pemSHA, sslCert.PemSHA)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// test add ingress with secret it doesn't exists and then add secret
|
||||
|
@ -821,22 +774,9 @@ func deleteIngress(ingress *networking.Ingress, clientSet kubernetes.Interface,
|
|||
t.Logf("Ingress %+v deleted", ingress)
|
||||
}
|
||||
|
||||
func newFS(t *testing.T) file.Filesystem {
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating filesystem: %v", err)
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// newStore creates a new mock object store for tests which do not require the
|
||||
// use of Informers.
|
||||
func newStore(t *testing.T) *k8sStore {
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
pod := &k8s.PodInfo{
|
||||
Name: "ingress-1",
|
||||
Namespace: v1.NamespaceDefault,
|
||||
|
@ -853,7 +793,6 @@ func newStore(t *testing.T) *k8sStore {
|
|||
Pod: PodLister{cache.NewStore(cache.MetaNamespaceKeyFunc)},
|
||||
},
|
||||
sslStore: NewSSLCertTracker(),
|
||||
filesystem: fs,
|
||||
updateCh: channels.NewRingChannel(10),
|
||||
syncSecretMu: new(sync.Mutex),
|
||||
backendConfigMu: new(sync.RWMutex),
|
||||
|
|
|
@ -59,10 +59,23 @@ const (
|
|||
globalAuthSnippet = "global-auth-snippet"
|
||||
globalAuthCacheKey = "global-auth-cache-key"
|
||||
globalAuthCacheDuration = "global-auth-cache-duration"
|
||||
luaSharedDictsKey = "lua-shared-dicts"
|
||||
)
|
||||
|
||||
var (
|
||||
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
|
||||
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
|
||||
defaultLuaSharedDicts = map[string]int{
|
||||
"configuration_data": 20,
|
||||
"certificate_data": 20,
|
||||
"balancer_ewma": 10,
|
||||
"balancer_ewma_last_touched_at": 10,
|
||||
"balancer_ewma_locks": 1,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
maxAllowedLuaDictSize = 200
|
||||
maxNumberOfLuaDicts = 100
|
||||
)
|
||||
|
||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||
|
@ -87,7 +100,40 @@ func ReadConfig(src map[string]string) config.Configuration {
|
|||
blockUserAgentList := make([]string, 0)
|
||||
blockRefererList := make([]string, 0)
|
||||
responseHeaders := make([]string, 0)
|
||||
luaSharedDicts := make(map[string]int)
|
||||
|
||||
//parse lua shared dict values
|
||||
if val, ok := conf[luaSharedDictsKey]; ok {
|
||||
delete(conf, luaSharedDictsKey)
|
||||
lsd := strings.Split(val, ",")
|
||||
for _, v := range lsd {
|
||||
v = strings.Replace(v, " ", "", -1)
|
||||
results := strings.SplitN(v, ":", 2)
|
||||
dictName := results[0]
|
||||
size, err := strconv.Atoi(results[1])
|
||||
if err != nil {
|
||||
klog.Errorf("Ignoring non integer value %v for Lua dictionary %v: %v.", results[1], dictName, err)
|
||||
continue
|
||||
}
|
||||
if size > maxAllowedLuaDictSize {
|
||||
klog.Errorf("Ignoring %v for Lua dictionary %v: maximum size is %v.", size, dictName, maxAllowedLuaDictSize)
|
||||
continue
|
||||
}
|
||||
if len(luaSharedDicts)+1 > maxNumberOfLuaDicts {
|
||||
klog.Errorf("Ignoring %v for Lua dictionary %v: can not configure more than %v dictionaries.",
|
||||
size, dictName, maxNumberOfLuaDicts)
|
||||
continue
|
||||
}
|
||||
|
||||
luaSharedDicts[dictName] = size
|
||||
}
|
||||
}
|
||||
// set default Lua shared dicts
|
||||
for k, v := range defaultLuaSharedDicts {
|
||||
if _, ok := luaSharedDicts[k]; !ok {
|
||||
luaSharedDicts[k] = v
|
||||
}
|
||||
}
|
||||
if val, ok := conf[customHTTPErrors]; ok {
|
||||
delete(conf, customHTTPErrors)
|
||||
for _, i := range strings.Split(val, ",") {
|
||||
|
@ -305,6 +351,7 @@ func ReadConfig(src map[string]string) config.Configuration {
|
|||
to.HideHeaders = hideHeadersList
|
||||
to.ProxyStreamResponses = streamResponses
|
||||
to.DisableIpv6DNS = !ing_net.IsIPv6Enabled()
|
||||
to.LuaSharedDicts = luaSharedDicts
|
||||
|
||||
config := &mapstructure.DecoderConfig{
|
||||
Metadata: nil,
|
||||
|
|
|
@ -64,7 +64,6 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
"access-log-path": "/var/log/test/access.log",
|
||||
"error-log-path": "/var/log/test/error.log",
|
||||
"use-gzip": "true",
|
||||
"enable-dynamic-tls-records": "false",
|
||||
"gzip-level": "9",
|
||||
"gzip-types": "text/html",
|
||||
"proxy-real-ip-cidr": "1.1.1.1/8,2.2.2.2/24",
|
||||
|
@ -84,7 +83,6 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
def.SkipAccessLogURLs = []string{"/log", "/demo", "/test"}
|
||||
def.ProxyReadTimeout = 1
|
||||
def.ProxySendTimeout = 2
|
||||
def.EnableDynamicTLSRecords = false
|
||||
def.UseProxyProtocol = true
|
||||
def.GzipLevel = 9
|
||||
def.GzipTypes = "text/html"
|
||||
|
@ -95,7 +93,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
def.NginxStatusIpv4Whitelist = []string{"127.0.0.1", "10.0.0.0/24"}
|
||||
def.NginxStatusIpv6Whitelist = []string{"::1", "2001::/16"}
|
||||
def.ProxyAddOriginalURIHeader = false
|
||||
|
||||
def.LuaSharedDicts = defaultLuaSharedDicts
|
||||
def.DisableIpv6DNS = true
|
||||
|
||||
hash, err := hashstructure.Hash(def, &hashstructure.HashOptions{
|
||||
|
@ -125,6 +123,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
}
|
||||
|
||||
def = config.NewDefault()
|
||||
def.LuaSharedDicts = defaultLuaSharedDicts
|
||||
def.DisableIpv6DNS = true
|
||||
|
||||
hash, err = hashstructure.Hash(def, &hashstructure.HashOptions{
|
||||
|
@ -143,6 +142,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
}
|
||||
|
||||
def = config.NewDefault()
|
||||
def.LuaSharedDicts = defaultLuaSharedDicts
|
||||
def.WhitelistSourceRange = []string{"1.1.1.1/32"}
|
||||
def.DisableIpv6DNS = true
|
||||
|
||||
|
@ -303,3 +303,56 @@ func TestGlobalExternalAuthCacheDurationParsing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaSharedDictsParsing(t *testing.T) {
|
||||
testsCases := []struct {
|
||||
name string
|
||||
entry map[string]string
|
||||
expect map[string]int
|
||||
}{
|
||||
{
|
||||
name: "default dicts configured when lua-shared-dicts is not set",
|
||||
entry: make(map[string]string),
|
||||
expect: defaultLuaSharedDicts,
|
||||
},
|
||||
{
|
||||
name: "configuration_data only",
|
||||
entry: map[string]string{"lua-shared-dicts": "configuration_data:5"},
|
||||
expect: map[string]int{"configuration_data": 5},
|
||||
},
|
||||
{
|
||||
name: "certificate_data only",
|
||||
entry: map[string]string{"lua-shared-dicts": "certificate_data: 4"},
|
||||
expect: map[string]int{"certificate_data": 4},
|
||||
},
|
||||
{
|
||||
name: "custom dicts",
|
||||
entry: map[string]string{"lua-shared-dicts": "configuration_data: 10, my_random_dict:15 , another_example:2"},
|
||||
expect: map[string]int{"configuration_data": 10, "my_random_dict": 15, "another_example": 2},
|
||||
},
|
||||
{
|
||||
name: "invalid size value should be ignored",
|
||||
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 1a"},
|
||||
expect: map[string]int{"mydict": 10},
|
||||
},
|
||||
{
|
||||
name: "dictionary size can not be larger than 200",
|
||||
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 201"},
|
||||
expect: map[string]int{"mydict": 10},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testsCases {
|
||||
// dynamically insert default dicts in the expected output
|
||||
for dictName, dictSize := range defaultLuaSharedDicts {
|
||||
if _, ok := tc.expect[dictName]; !ok {
|
||||
tc.expect[dictName] = dictSize
|
||||
}
|
||||
}
|
||||
|
||||
cfg := ReadConfig(tc.entry)
|
||||
if !reflect.DeepEqual(cfg.LuaSharedDicts, tc.expect) {
|
||||
t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", tc.name, tc.expect, cfg.LuaSharedDicts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
|
@ -37,13 +38,13 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
ing_net "k8s.io/ingress-nginx/internal/net"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -66,8 +67,8 @@ type Template struct {
|
|||
|
||||
//NewTemplate returns a new Template instance or an
|
||||
//error if the specified template file contains errors
|
||||
func NewTemplate(file string, fs file.Filesystem) (*Template, error) {
|
||||
data, err := fs.ReadFile(file)
|
||||
func NewTemplate(file string) (*Template, error) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unexpected error reading template %v", file)
|
||||
}
|
||||
|
@ -132,36 +133,37 @@ var (
|
|||
}
|
||||
return true
|
||||
},
|
||||
"escapeLiteralDollar": escapeLiteralDollar,
|
||||
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
|
||||
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
|
||||
"buildLocation": buildLocation,
|
||||
"buildAuthLocation": buildAuthLocation,
|
||||
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
|
||||
"buildAuthResponseHeaders": buildAuthResponseHeaders,
|
||||
"buildProxyPass": buildProxyPass,
|
||||
"filterRateLimits": filterRateLimits,
|
||||
"buildRateLimitZones": buildRateLimitZones,
|
||||
"buildRateLimit": buildRateLimit,
|
||||
"buildResolversForLua": buildResolversForLua,
|
||||
"configForLua": configForLua,
|
||||
"locationConfigForLua": locationConfigForLua,
|
||||
"buildResolvers": buildResolvers,
|
||||
"buildUpstreamName": buildUpstreamName,
|
||||
"isLocationInLocationList": isLocationInLocationList,
|
||||
"isLocationAllowed": isLocationAllowed,
|
||||
"buildLogFormatUpstream": buildLogFormatUpstream,
|
||||
"buildDenyVariable": buildDenyVariable,
|
||||
"getenv": os.Getenv,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"trimSpace": strings.TrimSpace,
|
||||
"toUpper": strings.ToUpper,
|
||||
"toLower": strings.ToLower,
|
||||
"formatIP": formatIP,
|
||||
"buildNextUpstream": buildNextUpstream,
|
||||
"getIngressInformation": getIngressInformation,
|
||||
"escapeLiteralDollar": escapeLiteralDollar,
|
||||
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
|
||||
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
|
||||
"luaConfigurationRequestBodySize": luaConfigurationRequestBodySize,
|
||||
"buildLocation": buildLocation,
|
||||
"buildAuthLocation": buildAuthLocation,
|
||||
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
|
||||
"buildAuthResponseHeaders": buildAuthResponseHeaders,
|
||||
"buildProxyPass": buildProxyPass,
|
||||
"filterRateLimits": filterRateLimits,
|
||||
"buildRateLimitZones": buildRateLimitZones,
|
||||
"buildRateLimit": buildRateLimit,
|
||||
"configForLua": configForLua,
|
||||
"locationConfigForLua": locationConfigForLua,
|
||||
"buildResolvers": buildResolvers,
|
||||
"buildUpstreamName": buildUpstreamName,
|
||||
"isLocationInLocationList": isLocationInLocationList,
|
||||
"isLocationAllowed": isLocationAllowed,
|
||||
"buildLogFormatUpstream": buildLogFormatUpstream,
|
||||
"buildDenyVariable": buildDenyVariable,
|
||||
"getenv": os.Getenv,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"trimSpace": strings.TrimSpace,
|
||||
"toUpper": strings.ToUpper,
|
||||
"toLower": strings.ToLower,
|
||||
"formatIP": formatIP,
|
||||
"quote": quote,
|
||||
"buildNextUpstream": buildNextUpstream,
|
||||
"getIngressInformation": getIngressInformation,
|
||||
"serverConfig": func(all config.TemplateConfig, server *ingress.Server) interface{} {
|
||||
return struct{ First, Second interface{} }{all, server}
|
||||
},
|
||||
|
@ -177,6 +179,8 @@ var (
|
|||
"opentracingPropagateContext": opentracingPropagateContext,
|
||||
"buildCustomErrorLocationsPerServer": buildCustomErrorLocationsPerServer,
|
||||
"shouldLoadModSecurityModule": shouldLoadModSecurityModule,
|
||||
"buildHTTPListener": buildHTTPListener,
|
||||
"buildHTTPSListener": buildHTTPSListener,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -208,6 +212,21 @@ func formatIP(input string) string {
|
|||
return fmt.Sprintf("[%s]", input)
|
||||
}
|
||||
|
||||
func quote(input interface{}) string {
|
||||
var inputStr string
|
||||
switch input := input.(type) {
|
||||
case string:
|
||||
inputStr = input
|
||||
break
|
||||
case fmt.Stringer:
|
||||
inputStr = input.String()
|
||||
break
|
||||
default:
|
||||
inputStr = fmt.Sprintf("%v", input)
|
||||
}
|
||||
return fmt.Sprintf("%q", inputStr)
|
||||
}
|
||||
|
||||
func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool {
|
||||
if !disableLuaRestyWAF && len(mode) > 0 {
|
||||
return true
|
||||
|
@ -216,19 +235,26 @@ func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func buildLuaSharedDictionaries(s interface{}, disableLuaRestyWAF bool) string {
|
||||
func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF bool) string {
|
||||
var out []string
|
||||
|
||||
cfg, ok := c.(config.Configuration)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
|
||||
return ""
|
||||
}
|
||||
servers, ok := s.([]*ingress.Server)
|
||||
if !ok {
|
||||
klog.Errorf("expected an '[]*ingress.Server' type but %T was returned", s)
|
||||
return ""
|
||||
}
|
||||
|
||||
out := []string{
|
||||
"lua_shared_dict configuration_data 15M",
|
||||
"lua_shared_dict certificate_data 16M",
|
||||
for name, size := range cfg.LuaSharedDicts {
|
||||
out = append(out, fmt.Sprintf("lua_shared_dict %s %dM", name, size))
|
||||
}
|
||||
|
||||
if !disableLuaRestyWAF {
|
||||
// TODO: there must be a better place for this
|
||||
if _, ok := cfg.LuaSharedDicts["waf_storage"]; !ok && !disableLuaRestyWAF {
|
||||
luaRestyWAFEnabled := func() bool {
|
||||
for _, server := range servers {
|
||||
for _, location := range server.Locations {
|
||||
|
@ -244,38 +270,23 @@ func buildLuaSharedDictionaries(s interface{}, disableLuaRestyWAF bool) string {
|
|||
}
|
||||
}
|
||||
|
||||
return strings.Join(out, ";\n\r") + ";"
|
||||
return strings.Join(out, ";\n") + ";\n"
|
||||
}
|
||||
|
||||
func buildResolversForLua(res interface{}, disableIpv6 interface{}) string {
|
||||
nss, ok := res.([]net.IP)
|
||||
func luaConfigurationRequestBodySize(c interface{}) string {
|
||||
cfg, ok := c.(config.Configuration)
|
||||
if !ok {
|
||||
klog.Errorf("expected a '[]net.IP' type but %T was returned", res)
|
||||
return ""
|
||||
}
|
||||
no6, ok := disableIpv6.(bool)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'bool' type but %T was returned", disableIpv6)
|
||||
return ""
|
||||
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
|
||||
return "100" // just a default number
|
||||
}
|
||||
|
||||
if len(nss) == 0 {
|
||||
return ""
|
||||
size := cfg.LuaSharedDicts["configuration_data"]
|
||||
if size < cfg.LuaSharedDicts["certificate_data"] {
|
||||
size = cfg.LuaSharedDicts["certificate_data"]
|
||||
}
|
||||
size = size + 1
|
||||
|
||||
r := []string{}
|
||||
for _, ns := range nss {
|
||||
if ing_net.IsIPV6(ns) {
|
||||
if no6 {
|
||||
continue
|
||||
}
|
||||
r = append(r, fmt.Sprintf("\"[%v]\"", ns))
|
||||
} else {
|
||||
r = append(r, fmt.Sprintf("\"%v\"", ns))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(r, ", ")
|
||||
return fmt.Sprintf("%d", size)
|
||||
}
|
||||
|
||||
// configForLua returns some general configuration as Lua table represented as string
|
||||
|
@ -314,7 +325,7 @@ func locationConfigForLua(l interface{}, s interface{}, a interface{}) string {
|
|||
return "{}"
|
||||
}
|
||||
|
||||
forceSSLRedirect := location.Rewrite.ForceSSLRedirect || (len(server.SSLCert.PemFileName) > 0 && location.Rewrite.SSLRedirect)
|
||||
forceSSLRedirect := location.Rewrite.ForceSSLRedirect || (server.SSLCert != nil && location.Rewrite.SSLRedirect)
|
||||
forceSSLRedirect = forceSSLRedirect && !isLocationInLocationList(l, all.Cfg.NoTLSRedirectLocations)
|
||||
|
||||
return fmt.Sprintf(`{
|
||||
|
@ -497,6 +508,9 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string {
|
|||
case "AJP":
|
||||
proto = ""
|
||||
proxyPass = "ajp_pass"
|
||||
case "FCGI":
|
||||
proto = ""
|
||||
proxyPass = "fastcgi_pass"
|
||||
}
|
||||
|
||||
upstreamName := "upstream_balancer"
|
||||
|
@ -1088,3 +1102,169 @@ func shouldLoadModSecurityModule(c interface{}, s interface{}) bool {
|
|||
// Not enabled globally nor via annotation on a location, no need to load the module.
|
||||
return false
|
||||
}
|
||||
|
||||
func buildHTTPListener(t interface{}, s interface{}) string {
|
||||
var out []string
|
||||
|
||||
tc, ok := t.(config.TemplateConfig)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'config.TemplateConfig' type but %T was returned", t)
|
||||
return ""
|
||||
}
|
||||
|
||||
hostname, ok := s.(string)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'string' type but %T was returned", s)
|
||||
return ""
|
||||
}
|
||||
|
||||
addrV4 := []string{""}
|
||||
if len(tc.Cfg.BindAddressIpv4) > 0 {
|
||||
addrV4 = tc.Cfg.BindAddressIpv4
|
||||
}
|
||||
|
||||
co := commonListenOptions(tc, hostname)
|
||||
|
||||
out = append(out, httpListener(addrV4, co, tc)...)
|
||||
|
||||
if !tc.IsIPV6Enabled {
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
addrV6 := []string{"[::]"}
|
||||
if len(tc.Cfg.BindAddressIpv6) > 0 {
|
||||
addrV6 = tc.Cfg.BindAddressIpv6
|
||||
}
|
||||
|
||||
out = append(out, httpListener(addrV6, co, tc)...)
|
||||
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
func buildHTTPSListener(t interface{}, s interface{}) string {
|
||||
var out []string
|
||||
|
||||
tc, ok := t.(config.TemplateConfig)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'config.TemplateConfig' type but %T was returned", t)
|
||||
return ""
|
||||
}
|
||||
|
||||
hostname, ok := s.(string)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'string' type but %T was returned", s)
|
||||
return ""
|
||||
}
|
||||
|
||||
/*
|
||||
if server.SSLCert == nil && server.Hostname != "_" {
|
||||
return ""
|
||||
}
|
||||
*/
|
||||
|
||||
co := commonListenOptions(tc, hostname)
|
||||
|
||||
addrV4 := []string{""}
|
||||
if len(tc.Cfg.BindAddressIpv4) > 0 {
|
||||
addrV4 = tc.Cfg.BindAddressIpv4
|
||||
}
|
||||
|
||||
out = append(out, httpsListener(addrV4, co, tc)...)
|
||||
|
||||
if !tc.IsIPV6Enabled {
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
addrV6 := []string{"[::]"}
|
||||
if len(tc.Cfg.BindAddressIpv6) > 0 {
|
||||
addrV6 = tc.Cfg.BindAddressIpv6
|
||||
}
|
||||
|
||||
out = append(out, httpsListener(addrV6, co, tc)...)
|
||||
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
func commonListenOptions(template config.TemplateConfig, hostname string) string {
|
||||
var out []string
|
||||
|
||||
if template.Cfg.UseProxyProtocol {
|
||||
out = append(out, "proxy_protocol")
|
||||
}
|
||||
|
||||
if hostname != "_" {
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// setup options that are valid only once per port
|
||||
|
||||
out = append(out, "default_server")
|
||||
|
||||
if template.Cfg.ReusePort {
|
||||
out = append(out, "reuseport")
|
||||
}
|
||||
|
||||
out = append(out, fmt.Sprintf("backlog=%v", template.BacklogSize))
|
||||
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
func httpListener(addresses []string, co string, tc config.TemplateConfig) []string {
|
||||
out := make([]string, 0)
|
||||
for _, address := range addresses {
|
||||
l := make([]string, 0)
|
||||
l = append(l, "listen")
|
||||
|
||||
if address == "" {
|
||||
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.HTTP))
|
||||
} else {
|
||||
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.HTTP))
|
||||
}
|
||||
|
||||
l = append(l, co)
|
||||
l = append(l, ";")
|
||||
out = append(out, strings.Join(l, " "))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func httpsListener(addresses []string, co string, tc config.TemplateConfig) []string {
|
||||
out := make([]string, 0)
|
||||
for _, address := range addresses {
|
||||
l := make([]string, 0)
|
||||
l = append(l, "listen")
|
||||
|
||||
if tc.IsSSLPassthroughEnabled {
|
||||
if address == "" {
|
||||
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.SSLProxy))
|
||||
} else {
|
||||
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.SSLProxy))
|
||||
}
|
||||
|
||||
l = append(l, "proxy_protocol")
|
||||
} else {
|
||||
if address == "" {
|
||||
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.HTTPS))
|
||||
} else {
|
||||
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.HTTPS))
|
||||
}
|
||||
|
||||
if tc.Cfg.UseProxyProtocol {
|
||||
l = append(l, "proxy_protocol")
|
||||
}
|
||||
}
|
||||
|
||||
l = append(l, co)
|
||||
l = append(l, "ssl")
|
||||
|
||||
if tc.Cfg.UseHTTP2 {
|
||||
l = append(l, "http2")
|
||||
}
|
||||
|
||||
l = append(l, ";")
|
||||
out = append(out, strings.Join(l, " "))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -17,20 +17,20 @@ limitations under the License.
|
|||
package template
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||
|
@ -39,8 +39,18 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
|
||||
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
"k8s.io/ingress-nginx/internal/nginx"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// the default value of nginx.TemplatePath assumes the template exists in
|
||||
// the root filesystem and not in the rootfs directory
|
||||
path, err := filepath.Abs(filepath.Join("../../../../rootfs/", nginx.TemplatePath))
|
||||
if err == nil {
|
||||
nginx.TemplatePath = path
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// TODO: add tests for SSLPassthrough
|
||||
tmplFuncTestcases = map[string]struct {
|
||||
|
@ -168,7 +178,14 @@ proxy_pass http://upstream_balancer;`,
|
|||
func TestBuildLuaSharedDictionaries(t *testing.T) {
|
||||
invalidType := &ingress.Ingress{}
|
||||
expected := ""
|
||||
actual := buildLuaSharedDictionaries(invalidType, true)
|
||||
|
||||
// config lua dict
|
||||
cfg := config.Configuration{
|
||||
LuaSharedDicts: map[string]int{
|
||||
"configuration_data": 10, "certificate_data": 20,
|
||||
},
|
||||
}
|
||||
actual := buildLuaSharedDictionaries(cfg, invalidType, true)
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
|
@ -184,20 +201,41 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
|
|||
Locations: []*ingress.Location{{Path: "/", LuaRestyWAF: luarestywaf.Config{}}},
|
||||
},
|
||||
}
|
||||
|
||||
configuration := buildLuaSharedDictionaries(servers, false)
|
||||
if !strings.Contains(configuration, "lua_shared_dict configuration_data") {
|
||||
// returns value from config
|
||||
configuration := buildLuaSharedDictionaries(cfg, servers, false)
|
||||
if !strings.Contains(configuration, "lua_shared_dict configuration_data 10M;\n") {
|
||||
t.Errorf("expected to include 'configuration_data' but got %s", configuration)
|
||||
}
|
||||
if !strings.Contains(configuration, "lua_shared_dict certificate_data 20M;\n") {
|
||||
t.Errorf("expected to include 'certificate_data' but got %s", configuration)
|
||||
}
|
||||
if strings.Contains(configuration, "waf_storage") {
|
||||
t.Errorf("expected to not include 'waf_storage' but got %s", configuration)
|
||||
}
|
||||
|
||||
servers[1].Locations[0].LuaRestyWAF = luarestywaf.Config{Mode: "ACTIVE"}
|
||||
configuration = buildLuaSharedDictionaries(servers, false)
|
||||
configuration = buildLuaSharedDictionaries(cfg, servers, false)
|
||||
if !strings.Contains(configuration, "lua_shared_dict waf_storage") {
|
||||
t.Errorf("expected to configure 'waf_storage', but got %s", configuration)
|
||||
}
|
||||
// test invalid config
|
||||
configuration = buildLuaSharedDictionaries(invalidType, servers, false)
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v' ", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaConfigurationRequestBodySize(t *testing.T) {
|
||||
cfg := config.Configuration{
|
||||
LuaSharedDicts: map[string]int{
|
||||
"configuration_data": 10, "certificate_data": 20,
|
||||
},
|
||||
}
|
||||
|
||||
size := luaConfigurationRequestBodySize(cfg)
|
||||
if "21" != size {
|
||||
t.Errorf("expected the size to be 20 but got: %v", size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatIP(t *testing.T) {
|
||||
|
@ -219,6 +257,21 @@ func TestFormatIP(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestQuote(t *testing.T) {
|
||||
cases := map[interface{}]string{
|
||||
"foo": `"foo"`,
|
||||
"\"foo\"": `"\"foo\""`,
|
||||
"foo\nbar": `"foo\nbar"`,
|
||||
10: `"10"`,
|
||||
}
|
||||
for input, output := range cases {
|
||||
actual := quote(input)
|
||||
if actual != output {
|
||||
t.Errorf("quote('%s'): expected '%v' but returned '%v'", input, output, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLocation(t *testing.T) {
|
||||
invalidType := &ingress.Ingress{}
|
||||
expected := "/"
|
||||
|
@ -408,16 +461,13 @@ func TestTemplateWithData(t *testing.T) {
|
|||
dat.ListenPorts = &config.ListenPorts{}
|
||||
}
|
||||
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs)
|
||||
ngxTpl, err := NewTemplate(nginx.TemplatePath)
|
||||
if err != nil {
|
||||
t.Errorf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
||||
dat.Cfg.DefaultSSLCertificate = &ingress.SSLCert{}
|
||||
|
||||
rt, err := ngxTpl.Write(dat)
|
||||
if err != nil {
|
||||
t.Errorf("invalid NGINX template: %v", err)
|
||||
|
@ -452,12 +502,7 @@ func BenchmarkTemplateWithData(b *testing.B) {
|
|||
b.Errorf("unexpected error unmarshalling json: %v", err)
|
||||
}
|
||||
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs)
|
||||
ngxTpl, err := NewTemplate(nginx.TemplatePath)
|
||||
if err != nil {
|
||||
b.Errorf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
@ -550,43 +595,6 @@ func TestBuildForwardedFor(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuildResolversForLua(t *testing.T) {
|
||||
|
||||
ipOne := net.ParseIP("192.0.0.1")
|
||||
ipTwo := net.ParseIP("2001:db8:1234:0000:0000:0000:0000:0000")
|
||||
ipList := []net.IP{ipOne, ipTwo}
|
||||
|
||||
invalidType := &ingress.Ingress{}
|
||||
expected := ""
|
||||
actual := buildResolversForLua(invalidType, false)
|
||||
|
||||
// Invalid Type for []net.IP
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
}
|
||||
|
||||
actual = buildResolversForLua(ipList, invalidType)
|
||||
|
||||
// Invalid Type for bool
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
}
|
||||
|
||||
expected = "\"192.0.0.1\", \"[2001:db8:1234::]\""
|
||||
actual = buildResolversForLua(ipList, false)
|
||||
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
}
|
||||
|
||||
expected = "\"192.0.0.1\""
|
||||
actual = buildResolversForLua(ipList, true)
|
||||
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildResolvers(t *testing.T) {
|
||||
ipOne := net.ParseIP("192.0.0.1")
|
||||
ipTwo := net.ParseIP("2001:db8:1234:0000:0000:0000:0000:0000")
|
||||
|
@ -874,6 +882,7 @@ func TestOpentracingPropagateContext(t *testing.T) {
|
|||
&ingress.Location{BackendProtocol: "GRPC"}: "opentracing_grpc_propagate_context",
|
||||
&ingress.Location{BackendProtocol: "GRPCS"}: "opentracing_grpc_propagate_context",
|
||||
&ingress.Location{BackendProtocol: "AJP"}: "opentracing_propagate_context",
|
||||
&ingress.Location{BackendProtocol: "FCGI"}: "opentracing_propagate_context",
|
||||
"not a location": "opentracing_propagate_context",
|
||||
}
|
||||
|
||||
|
|
|
@ -154,4 +154,8 @@ type Backend struct {
|
|||
// Modifies the HTTP version the proxy uses to interact with the backend.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
|
||||
ProxyHTTPVersion string `json:"proxy-http-version"`
|
||||
|
||||
// Sets the maximum temp file size when proxy-buffers capacity is exceeded.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_max_temp_file_size
|
||||
ProxyMaxTempFileSize string `json:"proxy-max-temp-file-size"`
|
||||
}
|
||||
|
|
|
@ -228,7 +228,7 @@ func (cm Controller) Collect(ch chan<- prometheus.Metric) {
|
|||
// SetSSLExpireTime sets the expiration time of SSL Certificates
|
||||
func (cm *Controller) SetSSLExpireTime(servers []*ingress.Server) {
|
||||
for _, s := range servers {
|
||||
if s.Hostname != "" && s.SSLCert.ExpireTime.Unix() > 0 {
|
||||
if s.Hostname != "" && s.SSLCert != nil && s.SSLCert.ExpireTime.Unix() > 0 {
|
||||
labels := make(prometheus.Labels, len(cm.labels)+1)
|
||||
for k, v := range cm.labels {
|
||||
labels[k] = v
|
||||
|
|
|
@ -80,13 +80,13 @@ func TestControllerCounters(t *testing.T) {
|
|||
servers := []*ingress.Server{
|
||||
{
|
||||
Hostname: "demo",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
ExpireTime: t1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hostname: "invalid",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
ExpireTime: time.Unix(0, 0),
|
||||
},
|
||||
},
|
||||
|
@ -135,13 +135,13 @@ func TestRemoveMetrics(t *testing.T) {
|
|||
servers := []*ingress.Server{
|
||||
{
|
||||
Hostname: "demo",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
ExpireTime: t1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hostname: "invalid",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
ExpireTime: time.Unix(0, 0),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,6 +26,9 @@ type Resolver interface {
|
|||
// GetDefaultBackend returns the backend that must be used as default
|
||||
GetDefaultBackend() defaults.Backend
|
||||
|
||||
// GetConfigMap searches for configmap containing the namespace and name usting the character /
|
||||
GetConfigMap(string) (*apiv1.ConfigMap, error)
|
||||
|
||||
// GetSecret searches for secrets containing the namespace and name using a the character /
|
||||
GetSecret(string) (*apiv1.Secret, error)
|
||||
|
||||
|
@ -48,8 +51,8 @@ type AuthSSLCert struct {
|
|||
Secret string `json:"secret"`
|
||||
// CAFileName contains the path to the secrets 'ca.crt'
|
||||
CAFileName string `json:"caFilename"`
|
||||
// PemSHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
|
||||
PemSHA string `json:"pemSha"`
|
||||
// CASHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
|
||||
CASHA string `json:"caSha"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two AuthSSLCert types
|
||||
|
@ -67,7 +70,7 @@ func (asslc1 *AuthSSLCert) Equal(assl2 *AuthSSLCert) bool {
|
|||
if asslc1.CAFileName != assl2.CAFileName {
|
||||
return false
|
||||
}
|
||||
if asslc1.PemSHA != assl2.PemSHA {
|
||||
if asslc1.CASHA != assl2.CASHA {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,11 @@ func (m Mock) GetDefaultBackend() defaults.Backend {
|
|||
return defaults.Backend{}
|
||||
}
|
||||
|
||||
// GetConfigMap searches for configmap containing the namespace and name usting the character /
|
||||
func (m Mock) GetConfigMap(string) (*apiv1.ConfigMap, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetSecret searches for secrets contenating the namespace and name using a the character /
|
||||
func (m Mock) GetSecret(string) (*apiv1.Secret, error) {
|
||||
return nil, nil
|
||||
|
|
|
@ -20,25 +20,36 @@ import (
|
|||
"crypto/x509"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// SSLCert describes a SSL certificate to be used in a server
|
||||
type SSLCert struct {
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Certificate *x509.Certificate `json:"certificate,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
|
||||
Certificate *x509.Certificate `json:"-"`
|
||||
|
||||
// CAFileName contains the path to the file with the root certificate
|
||||
CAFileName string `json:"caFileName"`
|
||||
|
||||
// CASHA contains the sha1 of the ca file.
|
||||
// This is used to detect changes in the secret that contains certificates
|
||||
CASHA string `json:"caSha"`
|
||||
|
||||
// PemFileName contains the path to the file with the certificate and key concatenated
|
||||
PemFileName string `json:"pemFileName"`
|
||||
|
||||
// PemSHA contains the sha1 of the pem file.
|
||||
// This is used to detect changes in the secret that contains the certificates
|
||||
// This is used to detect changes in the secret that contains certificates
|
||||
PemSHA string `json:"pemSha"`
|
||||
|
||||
// CN contains all the common names defined in the SSL certificate
|
||||
CN []string `json:"cn"`
|
||||
|
||||
// ExpiresTime contains the expiration of this SSL certificate in timestamp format
|
||||
ExpireTime time.Time `json:"expires"`
|
||||
|
||||
// Pem encoded certificate and key concatenated
|
||||
PemCertKey string `json:"pemCertKey,omitempty"`
|
||||
}
|
||||
|
@ -50,5 +61,5 @@ func (s SSLCert) GetObjectKind() schema.ObjectKind {
|
|||
|
||||
// HashInclude defines if a field should be used or not to calculate the hash
|
||||
func (s SSLCert) HashInclude(field string, v interface{}) (bool, error) {
|
||||
return (field != "PemSHA" && field != "ExpireTime"), nil
|
||||
return (field != "PemSHA" && field != "CASHA" && field != "ExpireTime"), nil
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 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 (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestGetObjectKindForSSLCert(t *testing.T) {
|
||||
fk := &SSLCert{
|
||||
ObjectMeta: metav1.ObjectMeta{},
|
||||
CAFileName: "ca_file",
|
||||
PemFileName: "pemfile",
|
||||
PemSHA: "pem_sha",
|
||||
CN: []string{},
|
||||
}
|
||||
|
||||
r := fk.GetObjectKind()
|
||||
if r == nil {
|
||||
t.Errorf("Returned nil but expected a valid ObjectKind")
|
||||
}
|
||||
}
|
|
@ -27,10 +27,12 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/annotations/authtls"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/connection"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
|
||||
|
@ -179,7 +181,7 @@ type Server struct {
|
|||
// the server or in the remote endpoint
|
||||
SSLPassthrough bool `json:"sslPassthrough"`
|
||||
// SSLCert describes the certificate that will be used on the server
|
||||
SSLCert SSLCert `json:"sslCert"`
|
||||
SSLCert *SSLCert `json:"sslCert"`
|
||||
// Locations list of URIs configured in the server.
|
||||
Locations []*Location `json:"locations,omitempty"`
|
||||
// Alias return the alias of the server name
|
||||
|
@ -230,7 +232,7 @@ type Location struct {
|
|||
// Backend describes the name of the backend to use.
|
||||
Backend string `json:"backend"`
|
||||
// Service describes the referenced services from the ingress
|
||||
Service *apiv1.Service `json:"service,omitempty"`
|
||||
Service *apiv1.Service `json:"-"`
|
||||
// Port describes to which port from the service
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
// Overwrite the Host header passed into the backend. Defaults to
|
||||
|
@ -297,7 +299,7 @@ type Location struct {
|
|||
ClientBodyBufferSize string `json:"clientBodyBufferSize,omitempty"`
|
||||
// DefaultBackend allows the use of a custom default backend for this location.
|
||||
// +optional
|
||||
DefaultBackend *apiv1.Service `json:"defaultBackend,omitempty"`
|
||||
DefaultBackend *apiv1.Service `json:"-"`
|
||||
// DefaultBackendUpstreamName is the upstream-formatted string for the name of
|
||||
// this location's custom default backend
|
||||
DefaultBackendUpstreamName string `json:"defaultBackendUpstreamName,omitempty"`
|
||||
|
@ -316,6 +318,9 @@ type Location struct {
|
|||
// BackendProtocol indicates which protocol should be used to communicate with the service
|
||||
// By default this is HTTP
|
||||
BackendProtocol string `json:"backend-protocol"`
|
||||
// FastCGI allows the ingress to act as a FastCGI client for a given location.
|
||||
// +optional
|
||||
FastCGI fastcgi.Config `json:"fastcgi,omitempty"`
|
||||
// CustomHTTPErrors specifies the error codes that should be intercepted.
|
||||
// +optional
|
||||
CustomHTTPErrors []int `json:"custom-http-errors"`
|
||||
|
@ -324,6 +329,9 @@ type Location struct {
|
|||
ModSecurity modsecurity.Config `json:"modsecurity"`
|
||||
// Satisfy dictates allow access if any or all is set
|
||||
Satisfy string `json:"satisfy"`
|
||||
// Mirror allows you to mirror traffic to a "test" backend
|
||||
// +optional
|
||||
Mirror mirror.Config `json:"mirror,omitempty"`
|
||||
}
|
||||
|
||||
// SSLPassthroughBackend describes a SSL upstream server configured
|
||||
|
@ -331,7 +339,7 @@ type Location struct {
|
|||
// The endpoints must provide the TLS termination exposing the required SSL certificate.
|
||||
// The ingress controller only pipes the underlying TCP connection
|
||||
type SSLPassthroughBackend struct {
|
||||
Service *apiv1.Service `json:"service,omitempty"`
|
||||
Service *apiv1.Service `json:"-"`
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
// Backend describes the endpoints to use.
|
||||
Backend string `json:"namespace,omitempty"`
|
||||
|
@ -348,7 +356,7 @@ type L4Service struct {
|
|||
// Endpoints active endpoints of the service
|
||||
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
||||
// k8s Service
|
||||
Service *apiv1.Service `json:"service,omitempty"`
|
||||
Service *apiv1.Service `json:"-"`
|
||||
}
|
||||
|
||||
// L4Backend describes the kubernetes service behind L4 Ingress service
|
||||
|
@ -369,8 +377,8 @@ type ProxyProtocol struct {
|
|||
|
||||
// Ingress holds the definition of an Ingress plus its annotations
|
||||
type Ingress struct {
|
||||
networking.Ingress
|
||||
ParsedAnnotations *annotations.Ingress
|
||||
networking.Ingress `json:"-"`
|
||||
ParsedAnnotations *annotations.Ingress `json:"parsedAnnotations"`
|
||||
}
|
||||
|
||||
// GeneralConfig holds the definition of lua general configuration data
|
||||
|
|
|
@ -266,7 +266,7 @@ func (s1 *Server) Equal(s2 *Server) bool {
|
|||
if s1.SSLPassthrough != s2.SSLPassthrough {
|
||||
return false
|
||||
}
|
||||
if !(&s1.SSLCert).Equal(&s2.SSLCert) {
|
||||
if !(s1.SSLCert).Equal(s2.SSLCert) {
|
||||
return false
|
||||
}
|
||||
if s1.Alias != s2.Alias {
|
||||
|
@ -401,6 +401,10 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if !(&l1.FastCGI).Equal(&l2.FastCGI) {
|
||||
return false
|
||||
}
|
||||
|
||||
match := compareInts(l1.CustomHTTPErrors, l2.CustomHTTPErrors)
|
||||
if !match {
|
||||
return false
|
||||
|
@ -418,6 +422,14 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if l1.Mirror.URI != l2.Mirror.URI {
|
||||
return false
|
||||
}
|
||||
|
||||
if l1.Mirror.RequestBody != l2.Mirror.RequestBody {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -504,7 +516,7 @@ func (s1 *SSLCert) Equal(s2 *SSLCert) bool {
|
|||
if s1 == nil || s2 == nil {
|
||||
return false
|
||||
}
|
||||
if s1.PemFileName != s2.PemFileName {
|
||||
if s1.CASHA != s2.CASHA {
|
||||
return false
|
||||
}
|
||||
if s1.PemSHA != s2.PemSHA {
|
||||
|
|
|
@ -122,13 +122,13 @@ func NetworkingIngressAvailable(client clientset.Interface) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
serverVersion, _ := client.Discovery().ServerVersion()
|
||||
serverVersion, err := client.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
klog.Errorf("unexpected error parsing Kubernetes version: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
runningVersion, _ := version.ParseGeneric(serverVersion.String())
|
||||
runningVersion, err := version.ParseGeneric(serverVersion.String())
|
||||
if err != nil {
|
||||
klog.Errorf("unexpected error parsing running Kubernetes version: %v", err)
|
||||
return false
|
||||
|
|
|
@ -27,8 +27,10 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -51,6 +53,22 @@ const (
|
|||
fakeCertificateName = "default-fake-certificate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
_, err := os.Stat(file.DefaultSSLDirectory)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(file.DefaultSSLDirectory, file.ReadWriteByUser)
|
||||
if err != nil {
|
||||
klog.Fatalf("Unexpected error checking for default SSL directory: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
klog.Fatalf("Unexpected error checking for default SSL directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// getPemFileName returns absolute file path and file name of pem cert related to given fullSecretName
|
||||
func getPemFileName(fullSecretName string) (string, string) {
|
||||
pemName := fmt.Sprintf("%v.pem", fullSecretName)
|
||||
|
@ -164,88 +182,58 @@ func CreateCACert(ca []byte) (*ingress.SSLCert, error) {
|
|||
|
||||
// StoreSSLCertOnDisk creates a .pem file with content PemCertKey from the given sslCert
|
||||
// and sets relevant remaining fields of sslCert object
|
||||
func StoreSSLCertOnDisk(fs file.Filesystem, name string, sslCert *ingress.SSLCert) error {
|
||||
func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) {
|
||||
pemFileName, _ := getPemFileName(name)
|
||||
|
||||
pemFile, err := fs.Create(pemFileName)
|
||||
err := ioutil.WriteFile(pemFileName, []byte(sslCert.PemCertKey), file.ReadWriteByUser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create PEM certificate file %v: %v", pemFileName, err)
|
||||
}
|
||||
defer pemFile.Close()
|
||||
|
||||
_, err = pemFile.Write([]byte(sslCert.PemCertKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write data to PEM file %v: %v", pemFileName, err)
|
||||
return "", fmt.Errorf("could not create PEM certificate file %v: %v", pemFileName, err)
|
||||
}
|
||||
|
||||
sslCert.PemFileName = pemFileName
|
||||
sslCert.PemSHA = file.SHA1(pemFileName)
|
||||
|
||||
return nil
|
||||
return pemFileName, nil
|
||||
}
|
||||
|
||||
// ConfigureCACertWithCertAndKey appends ca into existing PEM file consisting of cert and key
|
||||
// and sets relevant fields in sslCert object
|
||||
func ConfigureCACertWithCertAndKey(fs file.Filesystem, name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||
func ConfigureCACertWithCertAndKey(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||
err := verifyPemCertAgainstRootCA(sslCert.Certificate, ca)
|
||||
if err != nil {
|
||||
oe := fmt.Sprintf("failed to verify certificate chain: \n\t%s\n", err)
|
||||
return errors.New(oe)
|
||||
}
|
||||
|
||||
certAndKey, err := fs.ReadFile(sslCert.PemFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read file %v for writing additional CA chains: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
|
||||
f, err := fs.Create(sslCert.PemFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create PEM file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(certAndKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write cert and key bundle to cert file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
|
||||
_, err = f.Write([]byte("\n"))
|
||||
_, err = buffer.Write([]byte(sslCert.PemCertKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
|
||||
_, err = f.Write(ca)
|
||||
_, err = buffer.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
|
||||
_, err = buffer.Write(ca)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write ca data to cert file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
|
||||
sslCert.CAFileName = sslCert.PemFileName
|
||||
// since we updated sslCert.PemFileName we need to recalculate the checksum
|
||||
sslCert.PemSHA = file.SHA1(sslCert.PemFileName)
|
||||
|
||||
return nil
|
||||
return ioutil.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0644)
|
||||
}
|
||||
|
||||
// ConfigureCACert is similar to ConfigureCACertWithCertAndKey but it creates a separate file
|
||||
// for CA cert and writes only ca into it and then sets relevant fields in sslCert
|
||||
func ConfigureCACert(fs file.Filesystem, name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||
func ConfigureCACert(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||
caName := fmt.Sprintf("ca-%v.pem", name)
|
||||
fileName := fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, caName)
|
||||
|
||||
f, err := fs.Create(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write CA file %v: %v", fileName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(ca)
|
||||
err := ioutil.WriteFile(fileName, ca, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write CA file %v: %v", fileName, err)
|
||||
}
|
||||
|
||||
sslCert.PemFileName = fileName
|
||||
sslCert.CAFileName = fileName
|
||||
sslCert.PemSHA = file.SHA1(fileName)
|
||||
|
||||
klog.V(3).Infof("Created CA Certificate for Authentication: %v", fileName)
|
||||
|
||||
|
@ -319,10 +307,10 @@ func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddre
|
|||
}
|
||||
|
||||
// AddOrUpdateDHParam creates a dh parameters file with the specified name
|
||||
func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, error) {
|
||||
func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
|
||||
pemFileName, pemName := getPemFileName(name)
|
||||
|
||||
tempPemFile, err := fs.TempFile(file.DefaultSSLDirectory, pemName)
|
||||
tempPemFile, err := ioutil.TempFile(file.DefaultSSLDirectory, pemName)
|
||||
|
||||
klog.V(3).Infof("Creating temp file %v for DH param: %v", tempPemFile.Name(), pemName)
|
||||
if err != nil {
|
||||
|
@ -339,9 +327,9 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
|
|||
return "", fmt.Errorf("could not close temp pem file %v: %v", tempPemFile.Name(), err)
|
||||
}
|
||||
|
||||
defer fs.RemoveAll(tempPemFile.Name())
|
||||
defer os.Remove(tempPemFile.Name())
|
||||
|
||||
pemCerts, err := fs.ReadFile(tempPemFile.Name())
|
||||
pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -356,7 +344,7 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
|
|||
return "", fmt.Errorf("certificate %v contains invalid data", name)
|
||||
}
|
||||
|
||||
err = fs.Rename(tempPemFile.Name(), pemFileName)
|
||||
err = os.Rename(tempPemFile.Name(), pemFileName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempPemFile.Name(), pemFileName, err)
|
||||
}
|
||||
|
@ -366,7 +354,7 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
|
|||
|
||||
// GetFakeSSLCert creates a Self Signed Certificate
|
||||
// Based in the code https://golang.org/src/crypto/tls/generate_cert.go
|
||||
func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert {
|
||||
func GetFakeSSLCert() *ingress.SSLCert {
|
||||
cert, key := getFakeHostSSLCert("ingress.local")
|
||||
|
||||
sslCert, err := CreateSSLCert(cert, key)
|
||||
|
@ -374,11 +362,14 @@ func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert {
|
|||
klog.Fatalf("unexpected error creating fake SSL Cert: %v", err)
|
||||
}
|
||||
|
||||
err = StoreSSLCertOnDisk(fs, fakeCertificateName, sslCert)
|
||||
path, err := StoreSSLCertOnDisk(fakeCertificateName, sslCert)
|
||||
if err != nil {
|
||||
klog.Fatalf("unexpected error storing fake SSL Cert: %v", err)
|
||||
}
|
||||
|
||||
sslCert.PemFileName = path
|
||||
sslCert.PemSHA = file.SHA1(path)
|
||||
|
||||
return sslCert
|
||||
}
|
||||
|
||||
|
@ -478,7 +469,6 @@ func IsValidHostname(hostname string, commonNames []string) bool {
|
|||
type TLSListener struct {
|
||||
certificatePath string
|
||||
keyPath string
|
||||
fs file.Filesystem
|
||||
certificate *tls.Certificate
|
||||
err error
|
||||
lock sync.Mutex
|
||||
|
@ -487,14 +477,9 @@ type TLSListener struct {
|
|||
// NewTLSListener watches changes to th certificate and key paths
|
||||
// and reloads it whenever it changes
|
||||
func NewTLSListener(certificate, key string) *TLSListener {
|
||||
fs, err := file.NewLocalFS()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to instanciate certificate: %v", err))
|
||||
}
|
||||
l := TLSListener{
|
||||
certificatePath: certificate,
|
||||
keyPath: key,
|
||||
fs: fs,
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
l.load()
|
||||
|
@ -519,12 +504,12 @@ func (tl *TLSListener) TLSConfig() *tls.Config {
|
|||
|
||||
func (tl *TLSListener) load() {
|
||||
klog.Infof("loading tls certificate from certificate path %s and key path %s", tl.certificatePath, tl.keyPath)
|
||||
certBytes, err := tl.fs.ReadFile(tl.certificatePath)
|
||||
certBytes, err := ioutil.ReadFile(tl.certificatePath)
|
||||
if err != nil {
|
||||
tl.certificate = nil
|
||||
tl.err = err
|
||||
}
|
||||
keyBytes, err := tl.fs.ReadFile(tl.keyPath)
|
||||
keyBytes, err := ioutil.ReadFile(tl.keyPath)
|
||||
if err != nil {
|
||||
tl.certificate = nil
|
||||
tl.err = err
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
|
@ -38,9 +39,6 @@ import (
|
|||
"time"
|
||||
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
)
|
||||
|
||||
// generateRSACerts generates a self signed certificate using a self generated ca
|
||||
|
@ -71,8 +69,6 @@ func generateRSACerts(host string) (*keyPair, *keyPair, error) {
|
|||
}
|
||||
|
||||
func TestStoreSSLCertOnDisk(t *testing.T) {
|
||||
fs := newFS(t)
|
||||
|
||||
cert, _, err := generateRSACerts("echoheaders")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
|
@ -88,13 +84,13 @@ func TestStoreSSLCertOnDisk(t *testing.T) {
|
|||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
}
|
||||
|
||||
err = StoreSSLCertOnDisk(fs, name, sslCert)
|
||||
_, err = StoreSSLCertOnDisk(name, sslCert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error storing SSL certificate: %v", err)
|
||||
}
|
||||
|
||||
if sslCert.PemFileName == "" {
|
||||
t.Fatalf("expected path to pem file but returned empty")
|
||||
if sslCert.PemCertKey == "" {
|
||||
t.Fatalf("expected a pem certificate returned empty")
|
||||
}
|
||||
|
||||
if len(sslCert.CN) == 0 {
|
||||
|
@ -107,8 +103,6 @@ func TestStoreSSLCertOnDisk(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCACert(t *testing.T) {
|
||||
fs := newFS(t)
|
||||
|
||||
cert, CA, err := generateRSACerts("echoheaders")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
|
@ -125,16 +119,14 @@ func TestCACert(t *testing.T) {
|
|||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
}
|
||||
|
||||
err = StoreSSLCertOnDisk(fs, name, sslCert)
|
||||
path, err := StoreSSLCertOnDisk(name, sslCert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error storing SSL certificate: %v", err)
|
||||
}
|
||||
|
||||
if sslCert.CAFileName != "" {
|
||||
t.Fatalf("expected CA file name to be empty")
|
||||
}
|
||||
sslCert.CAFileName = path
|
||||
|
||||
err = ConfigureCACertWithCertAndKey(fs, name, ca, sslCert)
|
||||
err = ConfigureCACertWithCertAndKey(name, ca, sslCert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error configuring CA certificate: %v", err)
|
||||
}
|
||||
|
@ -145,9 +137,7 @@ func TestCACert(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetFakeSSLCert(t *testing.T) {
|
||||
fs := newFS(t)
|
||||
|
||||
sslCert := GetFakeSSLCert(fs)
|
||||
sslCert := GetFakeSSLCert()
|
||||
|
||||
if len(sslCert.PemCertKey) == 0 {
|
||||
t.Fatalf("expected PemCertKey to not be empty")
|
||||
|
@ -171,8 +161,6 @@ func TestGetFakeSSLCert(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigureCACert(t *testing.T) {
|
||||
fs := newFS(t)
|
||||
|
||||
cn := "demo-ca"
|
||||
_, ca, err := generateRSACerts(cn)
|
||||
if err != nil {
|
||||
|
@ -191,7 +179,7 @@ func TestConfigureCACert(t *testing.T) {
|
|||
t.Fatalf("expected Certificate to be set")
|
||||
}
|
||||
|
||||
err = ConfigureCACert(fs, cn, c, sslCert)
|
||||
err = ConfigureCACert(cn, c, sslCert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
}
|
||||
|
@ -200,14 +188,6 @@ func TestConfigureCACert(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func newFS(t *testing.T) file.Filesystem {
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating filesystem: %v", err)
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
func TestCreateSSLCert(t *testing.T) {
|
||||
cert, _, err := generateRSACerts("echoheaders")
|
||||
if err != nil {
|
||||
|
@ -360,19 +340,26 @@ func encodeCertPEM(cert *x509.Certificate) []byte {
|
|||
return pem.EncodeToMemory(&block)
|
||||
}
|
||||
|
||||
func fakeCertificate(t *testing.T, fs filesystem.Filesystem) []byte {
|
||||
func newFakeCertificate(t *testing.T) ([]byte, string, string) {
|
||||
cert, key := getFakeHostSSLCert("localhost")
|
||||
fd, err := fs.Create("/key.crt")
|
||||
|
||||
certFile, err := ioutil.TempFile("", "crt-")
|
||||
if err != nil {
|
||||
t.Errorf("failed to write test key: %v", err)
|
||||
}
|
||||
fd.Write(cert)
|
||||
fd, err = fs.Create("/key.key")
|
||||
|
||||
certFile.Write(cert)
|
||||
defer certFile.Close()
|
||||
|
||||
keyFile, err := ioutil.TempFile("", "key-")
|
||||
if err != nil {
|
||||
t.Errorf("failed to write test key: %v", err)
|
||||
}
|
||||
fd.Write(key)
|
||||
return cert
|
||||
|
||||
keyFile.Write(key)
|
||||
defer keyFile.Close()
|
||||
|
||||
return cert, certFile.Name(), keyFile.Name()
|
||||
}
|
||||
|
||||
func dialTestServer(port string, rootCertificates ...[]byte) error {
|
||||
|
@ -386,6 +373,7 @@ func dialTestServer(port string, rootCertificates ...[]byte) error {
|
|||
resp, err := tls.Dial("tcp", "localhost:"+port, &tls.Config{
|
||||
RootCAs: roots,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -396,13 +384,11 @@ func dialTestServer(port string, rootCertificates ...[]byte) error {
|
|||
}
|
||||
|
||||
func TestTLSKeyReloader(t *testing.T) {
|
||||
fs := filesystem.NewFakeFs()
|
||||
cert := fakeCertificate(t, fs)
|
||||
cert, certFile, keyFile := newFakeCertificate(t)
|
||||
|
||||
watcher := TLSListener{
|
||||
certificatePath: "/key.crt",
|
||||
keyPath: "/key.key",
|
||||
fs: fs,
|
||||
certificatePath: certFile,
|
||||
keyPath: keyFile,
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
watcher.load()
|
||||
|
@ -427,19 +413,22 @@ func TestTLSKeyReloader(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("with a new certificate", func(t *testing.T) {
|
||||
newCert := fakeCertificate(t, fs)
|
||||
cert, certFile, keyFile = newFakeCertificate(t)
|
||||
t.Run("when the certificate is not reloaded", func(t *testing.T) {
|
||||
if dialTestServer(port, newCert) == nil {
|
||||
if dialTestServer(port, cert) == nil {
|
||||
t.Errorf("TLS dial should fail")
|
||||
}
|
||||
})
|
||||
// simulate watch.NewFileWatcher to call the load function
|
||||
watcher.load()
|
||||
t.Run("when the certificate is reloaded", func(t *testing.T) {
|
||||
if err := dialTestServer(port, newCert); err != nil {
|
||||
t.Errorf("TLS dial should succeed, got error: %v", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
//TODO: fix
|
||||
/*
|
||||
// simulate watch.NewFileWatcher to call the load function
|
||||
watcher.load()
|
||||
t.Run("when the certificate is reloaded", func(t *testing.T) {
|
||||
if err := dialTestServer(port, cert); err != nil {
|
||||
t.Errorf("TLS dial should succeed, got error: %v", err)
|
||||
}
|
||||
})
|
||||
*/
|
||||
})
|
||||
}
|
||||
|
|
|
@ -23,12 +23,17 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tv42/httpunix"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// TemplatePath path of the NGINX template
|
||||
var TemplatePath = "/etc/nginx/template/nginx.tmpl"
|
||||
|
||||
// PID defines the location of the pid file used by NGINX
|
||||
var PID = "/tmp/nginx.pid"
|
||||
|
||||
|
@ -50,11 +55,17 @@ var StreamSocket = "/tmp/ingress-stream.sock"
|
|||
|
||||
var statusLocation = "nginx-status"
|
||||
|
||||
var httpClient *http.Client
|
||||
|
||||
func init() {
|
||||
httpClient = buildUnixSocketClient(HealthCheckTimeout)
|
||||
}
|
||||
|
||||
// NewGetStatusRequest creates a new GET request to the internal NGINX status server
|
||||
func NewGetStatusRequest(path string) (int, []byte, error) {
|
||||
url := fmt.Sprintf("http+unix://%v%v", statusLocation, path)
|
||||
url := fmt.Sprintf("%v://%v%v", httpunix.Scheme, statusLocation, path)
|
||||
|
||||
res, err := buildUnixSocketClient(HealthCheckTimeout).Get(url)
|
||||
res, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
@ -70,14 +81,14 @@ func NewGetStatusRequest(path string) (int, []byte, error) {
|
|||
|
||||
// NewPostStatusRequest creates a new POST request to the internal NGINX status server
|
||||
func NewPostStatusRequest(path, contentType string, data interface{}) (int, []byte, error) {
|
||||
url := fmt.Sprintf("http+unix://%v%v", statusLocation, path)
|
||||
url := fmt.Sprintf("%v://%v%v", httpunix.Scheme, statusLocation, path)
|
||||
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
res, err := buildUnixSocketClient(HealthCheckTimeout).Post(url, contentType, bytes.NewReader(buf))
|
||||
res, err := httpClient.Post(url, contentType, bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
@ -112,11 +123,11 @@ func GetServerBlock(conf string, host string) (string, error) {
|
|||
|
||||
// ReadNginxConf reads the nginx configuration file into a string
|
||||
func ReadNginxConf() (string, error) {
|
||||
return ReadFileToString("/etc/nginx/nginx.conf")
|
||||
return readFileToString("/etc/nginx/nginx.conf")
|
||||
}
|
||||
|
||||
// ReadFileToString reads any file into a string
|
||||
func ReadFileToString(path string) (string, error) {
|
||||
// readFileToString reads any file into a string
|
||||
func readFileToString(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -142,3 +153,21 @@ func buildUnixSocketClient(timeout time.Duration) *http.Client {
|
|||
Transport: u,
|
||||
}
|
||||
}
|
||||
|
||||
// Version return details about NGINX
|
||||
func Version() string {
|
||||
flag := "-v"
|
||||
|
||||
if klog.V(2) {
|
||||
flag = "-V"
|
||||
}
|
||||
|
||||
cmd := exec.Command("nginx", flag)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
klog.Errorf("unexpected error obtaining NGINX version: %v", err)
|
||||
return "N/A"
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ nav:
|
|||
- Custom errors: "user-guide/custom-errors.md"
|
||||
- Default backend: "user-guide/default-backend.md"
|
||||
- Exposing TCP and UDP services: "user-guide/exposing-tcp-udp-services.md"
|
||||
- Exposing FCGI services: "user-guide/fcgi-services.md"
|
||||
- Regular expressions in paths: user-guide/ingress-path-matching.md
|
||||
- External Articles: "user-guide/external-articles.md"
|
||||
- Miscellaneous: "user-guide/miscellaneous.md"
|
||||
|
@ -86,3 +87,4 @@ nav:
|
|||
- Rewrite: "examples/rewrite/README.md"
|
||||
- Static IPs: "examples/static-ip/README.md"
|
||||
- TLS termination: "examples/tls-termination/README.md"
|
||||
- Pod Security Policy (PSP): "examples/psp/README.md"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mkdocs-material~=4.0.2
|
||||
mkdocs-material~=4.4.0
|
||||
mkdocs~=1.0.4
|
||||
pymdown-extensions~=6.0
|
||||
pygments~=2.3.1
|
||||
|
|
|
@ -24,7 +24,8 @@ RUN clean-install \
|
|||
|
||||
COPY --chown=www-data:www-data . /
|
||||
|
||||
RUN cp /usr/local/openresty/nginx/conf/mime.types /etc/nginx/mime.types
|
||||
RUN cp /usr/local/openresty/nginx/conf/mime.types /etc/nginx/mime.types \
|
||||
&& cp /usr/local/openresty/nginx/conf/fastcgi_params /etc/nginx/fastcgi_params
|
||||
RUN ln -s /usr/local/openresty/nginx/modules /etc/nginx/modules
|
||||
|
||||
# Add LuaRocks paths
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
local ngx_balancer = require("ngx.balancer")
|
||||
local cjson = require("cjson.safe")
|
||||
local util = require("util")
|
||||
local dns_util = require("util.dns")
|
||||
local dns_lookup = require("util.dns").lookup
|
||||
local configuration = require("configuration")
|
||||
local round_robin = require("balancer.round_robin")
|
||||
local chash = require("balancer.chash")
|
||||
|
@ -52,7 +52,7 @@ local function resolve_external_names(original_backend)
|
|||
local backend = util.deepcopy(original_backend)
|
||||
local endpoints = {}
|
||||
for _, endpoint in ipairs(backend.endpoints) do
|
||||
local ips = dns_util.resolve(endpoint.address)
|
||||
local ips = dns_lookup(endpoint.address)
|
||||
for _, ip in ipairs(ips) do
|
||||
table.insert(endpoints, { address = ip, port = endpoint.port })
|
||||
end
|
||||
|
@ -190,6 +190,10 @@ local function route_to_alternative_balancer(balancer)
|
|||
end
|
||||
|
||||
local function get_balancer()
|
||||
if ngx.ctx.balancer then
|
||||
return ngx.ctx.balancer
|
||||
end
|
||||
|
||||
local backend_name = ngx.var.proxy_upstream_name
|
||||
|
||||
local balancer = balancers[backend_name]
|
||||
|
@ -201,9 +205,11 @@ local function get_balancer()
|
|||
local alternative_backend_name = balancer.alternative_backends[1]
|
||||
ngx.var.proxy_alternative_upstream_name = alternative_backend_name
|
||||
|
||||
return balancers[alternative_backend_name]
|
||||
balancer = balancers[alternative_backend_name]
|
||||
end
|
||||
|
||||
ngx.ctx.balancer = balancer
|
||||
|
||||
return balancer
|
||||
end
|
||||
|
||||
|
@ -260,6 +266,7 @@ if _TEST then
|
|||
_M.get_implementation = get_implementation
|
||||
_M.sync_backend = sync_backend
|
||||
_M.route_to_alternative_balancer = route_to_alternative_balancer
|
||||
_M.get_balancer = get_balancer
|
||||
end
|
||||
|
||||
return _M
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
-- /finagle-core/src/main/scala/com/twitter/finagle/loadbalancer/PeakEwma.scala
|
||||
|
||||
|
||||
local resty_lock = require("resty.lock")
|
||||
local util = require("util")
|
||||
local split = require("util.split")
|
||||
|
||||
|
@ -13,10 +14,36 @@ local ngx_log = ngx.log
|
|||
local INFO = ngx.INFO
|
||||
|
||||
local DECAY_TIME = 10 -- this value is in seconds
|
||||
local LOCK_KEY = ":ewma_key"
|
||||
local PICK_SET_SIZE = 2
|
||||
|
||||
local ewma_lock, ewma_lock_err = resty_lock:new("balancer_ewma_locks", {timeout = 0, exptime = 0.1})
|
||||
if not ewma_lock then
|
||||
error(ewma_lock_err)
|
||||
end
|
||||
|
||||
local _M = { name = "ewma" }
|
||||
|
||||
local function lock(upstream)
|
||||
local _, err = ewma_lock:lock(upstream .. LOCK_KEY)
|
||||
if err then
|
||||
if err ~= "timeout" then
|
||||
ngx.log(ngx.ERR, string.format("EWMA Balancer failed to lock: %s", tostring(err)))
|
||||
end
|
||||
end
|
||||
|
||||
return err
|
||||
end
|
||||
|
||||
local function unlock()
|
||||
local ok, err = ewma_lock:unlock()
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, string.format("EWMA Balancer failed to unlock: %s", tostring(err)))
|
||||
end
|
||||
|
||||
return err
|
||||
end
|
||||
|
||||
local function decay_ewma(ewma, last_touched_at, rtt, now)
|
||||
local td = now - last_touched_at
|
||||
td = (td > 0) and td or 0
|
||||
|
@ -26,28 +53,55 @@ local function decay_ewma(ewma, last_touched_at, rtt, now)
|
|||
return ewma
|
||||
end
|
||||
|
||||
local function get_or_update_ewma(self, upstream, rtt, update)
|
||||
local ewma = self.ewma[upstream] or 0
|
||||
local function store_stats(upstream, ewma, now)
|
||||
local success, err, forcible = ngx.shared.balancer_ewma_last_touched_at:set(upstream, now)
|
||||
if not success then
|
||||
ngx.log(ngx.WARN, "balancer_ewma_last_touched_at:set failed " .. err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.WARN, "balancer_ewma_last_touched_at:set valid items forcibly overwritten")
|
||||
end
|
||||
|
||||
success, err, forcible = ngx.shared.balancer_ewma:set(upstream, ewma)
|
||||
if not success then
|
||||
ngx.log(ngx.WARN, "balancer_ewma:set failed " .. err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.WARN, "balancer_ewma:set valid items forcibly overwritten")
|
||||
end
|
||||
end
|
||||
|
||||
local function get_or_update_ewma(upstream, rtt, update)
|
||||
local lock_err = nil
|
||||
if update then
|
||||
lock_err = lock(upstream)
|
||||
end
|
||||
local ewma = ngx.shared.balancer_ewma:get(upstream) or 0
|
||||
if lock_err ~= nil then
|
||||
return ewma, lock_err
|
||||
end
|
||||
|
||||
local now = ngx.now()
|
||||
local last_touched_at = self.ewma_last_touched_at[upstream] or 0
|
||||
local last_touched_at = ngx.shared.balancer_ewma_last_touched_at:get(upstream) or 0
|
||||
ewma = decay_ewma(ewma, last_touched_at, rtt, now)
|
||||
|
||||
if not update then
|
||||
return ewma, nil
|
||||
end
|
||||
|
||||
self.ewma[upstream] = ewma
|
||||
self.ewma_last_touched_at[upstream] = now
|
||||
store_stats(upstream, ewma, now)
|
||||
|
||||
unlock()
|
||||
|
||||
return ewma, nil
|
||||
end
|
||||
|
||||
|
||||
local function score(self, upstream)
|
||||
local function score(upstream)
|
||||
-- Original implementation used names
|
||||
-- Endpoints don't have names, so passing in IP:Port as key instead
|
||||
local upstream_name = upstream.address .. ":" .. upstream.port
|
||||
return get_or_update_ewma(self, upstream_name, 0, false)
|
||||
return get_or_update_ewma(upstream_name, 0, false)
|
||||
end
|
||||
|
||||
-- implementation similar to https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
|
||||
|
@ -63,12 +117,12 @@ local function shuffle_peers(peers, k)
|
|||
-- peers[1 .. k] will now contain a randomly selected k from #peers
|
||||
end
|
||||
|
||||
local function pick_and_score(self, peers, k)
|
||||
local function pick_and_score(peers, k)
|
||||
shuffle_peers(peers, k)
|
||||
local lowest_score_index = 1
|
||||
local lowest_score = score(self, peers[lowest_score_index])
|
||||
local lowest_score = score(peers[lowest_score_index])
|
||||
for i = 2, k do
|
||||
local new_score = score(self, peers[i])
|
||||
local new_score = score(peers[i])
|
||||
if new_score < lowest_score then
|
||||
lowest_score_index, lowest_score = i, new_score
|
||||
end
|
||||
|
@ -76,6 +130,31 @@ local function pick_and_score(self, peers, k)
|
|||
return peers[lowest_score_index], lowest_score
|
||||
end
|
||||
|
||||
-- slow_start_ewma is something we use to avoid sending too many requests
|
||||
-- to the newly introduced endpoints. We currently use average ewma values
|
||||
-- of existing endpoints.
|
||||
local function calculate_slow_start_ewma(self)
|
||||
local total_ewma = 0
|
||||
local endpoints_count = 0
|
||||
|
||||
for _, endpoint in pairs(self.peers) do
|
||||
local endpoint_string = endpoint.address .. ":" .. endpoint.port
|
||||
local ewma = ngx.shared.balancer_ewma:get(endpoint_string)
|
||||
|
||||
if ewma then
|
||||
endpoints_count = endpoints_count + 1
|
||||
total_ewma = total_ewma + ewma
|
||||
end
|
||||
end
|
||||
|
||||
if endpoints_count == 0 then
|
||||
ngx.log(ngx.INFO, "no ewma value exists for the endpoints")
|
||||
return nil
|
||||
end
|
||||
|
||||
return total_ewma / endpoints_count
|
||||
end
|
||||
|
||||
function _M.balance(self)
|
||||
local peers = self.peers
|
||||
local endpoint, ewma_score = peers[1], -1
|
||||
|
@ -83,7 +162,7 @@ function _M.balance(self)
|
|||
if #peers > 1 then
|
||||
local k = (#peers < PICK_SET_SIZE) and #peers or PICK_SET_SIZE
|
||||
local peer_copy = util.deepcopy(peers)
|
||||
endpoint, ewma_score = pick_and_score(self, peer_copy, k)
|
||||
endpoint, ewma_score = pick_and_score(peer_copy, k)
|
||||
end
|
||||
|
||||
ngx.var.balancer_ewma_score = ewma_score
|
||||
|
@ -92,7 +171,7 @@ function _M.balance(self)
|
|||
return endpoint.address .. ":" .. endpoint.port
|
||||
end
|
||||
|
||||
function _M.after_balance(self)
|
||||
function _M.after_balance(_)
|
||||
local response_time = tonumber(split.get_first_value(ngx.var.upstream_response_time)) or 0
|
||||
local connect_time = tonumber(split.get_first_value(ngx.var.upstream_connect_time)) or 0
|
||||
local rtt = connect_time + response_time
|
||||
|
@ -101,30 +180,41 @@ function _M.after_balance(self)
|
|||
if util.is_blank(upstream) then
|
||||
return
|
||||
end
|
||||
get_or_update_ewma(self, upstream, rtt, true)
|
||||
|
||||
get_or_update_ewma(upstream, rtt, true)
|
||||
end
|
||||
|
||||
function _M.sync(self, backend)
|
||||
self.traffic_shaping_policy = backend.trafficShapingPolicy
|
||||
self.alternative_backends = backend.alternativeBackends
|
||||
local normalized_endpoints_added, normalized_endpoints_removed = util.diff_endpoints(self.peers, backend.endpoints)
|
||||
|
||||
local changed = not util.deep_compare(self.peers, backend.endpoints)
|
||||
if not changed then
|
||||
if #normalized_endpoints_added == 0 and #normalized_endpoints_removed == 0 then
|
||||
ngx.log(ngx.INFO, "endpoints did not change for backend " .. tostring(backend.name))
|
||||
return
|
||||
end
|
||||
|
||||
ngx_log(INFO, string_format("[%s] peers have changed for backend %s", self.name, backend.name))
|
||||
|
||||
self.traffic_shaping_policy = backend.trafficShapingPolicy
|
||||
self.alternative_backends = backend.alternativeBackends
|
||||
self.peers = backend.endpoints
|
||||
self.ewma = {}
|
||||
self.ewma_last_touched_at = {}
|
||||
|
||||
for _, endpoint_string in ipairs(normalized_endpoints_removed) do
|
||||
ngx.shared.balancer_ewma:delete(endpoint_string)
|
||||
ngx.shared.balancer_ewma_last_touched_at:delete(endpoint_string)
|
||||
end
|
||||
|
||||
local slow_start_ewma = calculate_slow_start_ewma(self)
|
||||
if slow_start_ewma ~= nil then
|
||||
local now = ngx.now()
|
||||
for _, endpoint_string in ipairs(normalized_endpoints_added) do
|
||||
store_stats(endpoint_string, slow_start_ewma, now)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function _M.new(self, backend)
|
||||
local o = {
|
||||
peers = backend.endpoints,
|
||||
ewma = {},
|
||||
ewma_last_touched_at = {},
|
||||
traffic_shaping_policy = backend.trafficShapingPolicy,
|
||||
alternative_backends = backend.alternativeBackends,
|
||||
}
|
||||
|
|
|
@ -4,9 +4,7 @@ local cjson = require("cjson.safe")
|
|||
local configuration_data = ngx.shared.configuration_data
|
||||
local certificate_data = ngx.shared.certificate_data
|
||||
|
||||
local _M = {
|
||||
nameservers = {}
|
||||
}
|
||||
local _M = {}
|
||||
|
||||
function _M.get_backends_data()
|
||||
return configuration_data:get("backends")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
local ngx_balancer = require("ngx.balancer")
|
||||
local cjson = require("cjson.safe")
|
||||
local util = require("util")
|
||||
local dns_util = require("util.dns")
|
||||
local dns_lookup = require("util.dns").lookup
|
||||
local configuration = require("tcp_udp_configuration")
|
||||
local round_robin = require("balancer.round_robin")
|
||||
|
||||
|
@ -34,7 +34,7 @@ local function resolve_external_names(original_backend)
|
|||
local backend = util.deepcopy(original_backend)
|
||||
local endpoints = {}
|
||||
for _, endpoint in ipairs(backend.endpoints) do
|
||||
local ips = dns_util.resolve(endpoint.address)
|
||||
local ips = dns_lookup(endpoint.address)
|
||||
for _, ip in ipairs(ips) do
|
||||
table.insert(endpoints, {address = ip, port = endpoint.port})
|
||||
end
|
||||
|
|
|
@ -1,91 +1,151 @@
|
|||
local util = require("util")
|
||||
|
||||
local original_ngx = ngx
|
||||
local function reset_ngx()
|
||||
_G.ngx = original_ngx
|
||||
end
|
||||
|
||||
local function mock_ngx(mock)
|
||||
local _ngx = mock
|
||||
setmetatable(_ngx, { __index = ngx })
|
||||
_G.ngx = _ngx
|
||||
end
|
||||
|
||||
local function flush_all_ewma_stats()
|
||||
ngx.shared.balancer_ewma:flush_all()
|
||||
ngx.shared.balancer_ewma_last_touched_at:flush_all()
|
||||
end
|
||||
|
||||
local function store_ewma_stats(endpoint_string, ewma, touched_at)
|
||||
ngx.shared.balancer_ewma:set(endpoint_string, ewma)
|
||||
ngx.shared.balancer_ewma_last_touched_at:set(endpoint_string, touched_at)
|
||||
end
|
||||
|
||||
local function assert_ewma_stats(endpoint_string, ewma, touched_at)
|
||||
assert.are.equals(ewma, ngx.shared.balancer_ewma:get(endpoint_string))
|
||||
assert.are.equals(touched_at, ngx.shared.balancer_ewma_last_touched_at:get(endpoint_string))
|
||||
end
|
||||
|
||||
|
||||
describe("Balancer ewma", function()
|
||||
local balancer_ewma = require("balancer.ewma")
|
||||
local ngx_now = 1543238266
|
||||
local backend, instance
|
||||
|
||||
before_each(function()
|
||||
mock_ngx({ now = function() return ngx_now end, var = { balancer_ewma_score = -1 } })
|
||||
|
||||
backend = {
|
||||
name = "namespace-service-port", ["load-balance"] = "ewma",
|
||||
endpoints = {
|
||||
{ address = "10.10.10.1", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
{ address = "10.10.10.2", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
{ address = "10.10.10.3", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
}
|
||||
}
|
||||
store_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
|
||||
store_ewma_stats("10.10.10.2:8080", 0.3, ngx_now - 5)
|
||||
store_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
|
||||
|
||||
instance = balancer_ewma:new(backend)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
reset_ngx()
|
||||
flush_all_ewma_stats()
|
||||
end)
|
||||
|
||||
describe("after_balance()", function()
|
||||
local ngx_now = 1543238266
|
||||
_G.ngx.now = function() return ngx_now end
|
||||
_G.ngx.var = { upstream_response_time = "0.25", upstream_connect_time = "0.02", upstream_addr = "10.184.7.40:8080" }
|
||||
|
||||
it("updates EWMA stats", function()
|
||||
local backend = {
|
||||
name = "my-dummy-backend", ["load-balance"] = "ewma",
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
|
||||
}
|
||||
local instance = balancer_ewma:new(backend)
|
||||
ngx.var = { upstream_addr = "10.10.10.2:8080", upstream_connect_time = "0.02", upstream_response_time = "0.1" }
|
||||
|
||||
instance:after_balance()
|
||||
assert.equal(0.27, instance.ewma[ngx.var.upstream_addr])
|
||||
assert.equal(ngx_now, instance.ewma_last_touched_at[ngx.var.upstream_addr])
|
||||
|
||||
local weight = math.exp(-5 / 10)
|
||||
local expected_ewma = 0.3 * weight + 0.12 * (1.0 - weight)
|
||||
|
||||
assert.are.equals(expected_ewma, ngx.shared.balancer_ewma:get(ngx.var.upstream_addr))
|
||||
assert.are.equals(ngx_now, ngx.shared.balancer_ewma_last_touched_at:get(ngx.var.upstream_addr))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("balance()", function()
|
||||
it("returns single endpoint when the given backend has only one endpoint", function()
|
||||
local backend = {
|
||||
name = "my-dummy-backend", ["load-balance"] = "ewma",
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
|
||||
}
|
||||
local instance = balancer_ewma:new(backend)
|
||||
local single_endpoint_backend = util.deepcopy(backend)
|
||||
table.remove(single_endpoint_backend.endpoints, 3)
|
||||
table.remove(single_endpoint_backend.endpoints, 2)
|
||||
local single_endpoint_instance = balancer_ewma:new(single_endpoint_backend)
|
||||
|
||||
local peer = instance:balance()
|
||||
assert.equal("10.184.7.40:8080", peer)
|
||||
local peer = single_endpoint_instance:balance()
|
||||
|
||||
assert.are.equals("10.10.10.1:8080", peer)
|
||||
assert.are.equals(-1, ngx.var.balancer_ewma_score)
|
||||
end)
|
||||
|
||||
it("picks the endpoint with lowest score when there two of them", function()
|
||||
local backend = {
|
||||
name = "my-dummy-backend", ["load-balance"] = "ewma",
|
||||
endpoints = {
|
||||
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
{ address = "10.184.97.100", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
}
|
||||
}
|
||||
local instance = balancer_ewma:new(backend)
|
||||
instance.ewma = { ["10.184.7.40:8080"] = 0.5, ["10.184.97.100:8080"] = 0.3 }
|
||||
instance.ewma_last_touched_at = { ["10.184.7.40:8080"] = ngx.now(), ["10.184.97.100:8080"] = ngx.now() }
|
||||
it("picks the endpoint with lowest decayed score", function()
|
||||
local two_endpoints_backend = util.deepcopy(backend)
|
||||
table.remove(two_endpoints_backend.endpoints, 2)
|
||||
local two_endpoints_instance = balancer_ewma:new(two_endpoints_backend)
|
||||
|
||||
local peer = instance:balance()
|
||||
assert.equal("10.184.97.100:8080", peer)
|
||||
local peer = two_endpoints_instance:balance()
|
||||
|
||||
-- even though 10.10.10.1:8080 has a lower ewma score
|
||||
-- algorithm picks 10.10.10.3:8080 because its decayed score is even lower
|
||||
assert.equal("10.10.10.3:8080", peer)
|
||||
assert.are.equals(0.16240233988393523723, ngx.var.balancer_ewma_score)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("sync()", function()
|
||||
local backend, instance
|
||||
|
||||
before_each(function()
|
||||
backend = {
|
||||
name = "my-dummy-backend", ["load-balance"] = "ewma",
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
|
||||
}
|
||||
instance = balancer_ewma:new(backend)
|
||||
end)
|
||||
|
||||
it("does nothing when endpoints do not change", function()
|
||||
local new_backend = {
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
|
||||
}
|
||||
|
||||
instance:sync(new_backend)
|
||||
end)
|
||||
|
||||
it("updates endpoints", function()
|
||||
local new_backend = {
|
||||
endpoints = {
|
||||
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
{ address = "10.184.97.100", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
}
|
||||
}
|
||||
|
||||
instance:sync(new_backend)
|
||||
assert.are.same(new_backend.endpoints, instance.peers)
|
||||
end)
|
||||
|
||||
it("resets stats", function()
|
||||
it("does not reset stats when endpoints do not change", function()
|
||||
local new_backend = util.deepcopy(backend)
|
||||
new_backend.endpoints[1].maxFails = 3
|
||||
|
||||
instance:sync(new_backend)
|
||||
|
||||
assert.are.same(new_backend.endpoints, instance.peers)
|
||||
|
||||
assert_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
|
||||
assert_ewma_stats("10.10.10.2:8080", 0.3, ngx_now - 5)
|
||||
assert_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
|
||||
end)
|
||||
|
||||
it("updates peers, deletes stats for old endpoints and sets average ewma score to new ones", function()
|
||||
local new_backend = util.deepcopy(backend)
|
||||
|
||||
-- existing endpoint 10.10.10.2 got deleted
|
||||
-- and replaced with 10.10.10.4
|
||||
new_backend.endpoints[2].address = "10.10.10.4"
|
||||
-- and there's one new extra endpoint
|
||||
table.insert(new_backend.endpoints, { address = "10.10.10.5", port = "8080", maxFails = 0, failTimeout = 0 })
|
||||
|
||||
instance:sync(new_backend)
|
||||
|
||||
assert.are.same(new_backend.endpoints, instance.peers)
|
||||
|
||||
assert_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
|
||||
assert_ewma_stats("10.10.10.2:8080", nil, nil)
|
||||
assert_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
|
||||
|
||||
local slow_start_ewma = (0.2 + 1.2) / 2
|
||||
assert_ewma_stats("10.10.10.4:8080", slow_start_ewma, ngx_now)
|
||||
assert_ewma_stats("10.10.10.5:8080", slow_start_ewma, ngx_now)
|
||||
end)
|
||||
|
||||
it("does not set slow_start_ewma when there is no existing ewma", function()
|
||||
local new_backend = util.deepcopy(backend)
|
||||
table.insert(new_backend.endpoints, { address = "10.10.10.4", port = "8080", maxFails = 0, failTimeout = 0 })
|
||||
|
||||
-- when the LB algorithm instance is just instantiated it won't have any
|
||||
-- ewma value set for the initial endpoints (because it has not processed any request yet),
|
||||
-- this test is trying to simulate that by flushing existing ewma values
|
||||
flush_all_ewma_stats()
|
||||
|
||||
instance:sync(new_backend)
|
||||
|
||||
assert_ewma_stats("10.10.10.1:8080", nil, nil)
|
||||
assert_ewma_stats("10.10.10.2:8080", nil, nil)
|
||||
assert_ewma_stats("10.10.10.3:8080", nil, nil)
|
||||
assert_ewma_stats("10.10.10.4:8080", nil, nil)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -33,7 +33,7 @@ local function reset_backends()
|
|||
backends = {
|
||||
{
|
||||
name = "access-router-production-web-80", port = "80", secure = false,
|
||||
secureCACert = { secret = "", caFilename = "", pemSha = "" },
|
||||
secureCACert = { secret = "", caFilename = "", caSha = "" },
|
||||
sslPassthrough = false,
|
||||
endpoints = {
|
||||
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
|
@ -49,7 +49,7 @@ local function reset_backends()
|
|||
},
|
||||
},
|
||||
{ name = "my-dummy-app-1", ["load-balance"] = "round_robin", },
|
||||
{
|
||||
{
|
||||
name = "my-dummy-app-2", ["load-balance"] = "chash",
|
||||
upstreamHashByConfig = { ["upstream-hash-by"] = "$request_uri", },
|
||||
},
|
||||
|
@ -86,6 +86,44 @@ describe("Balancer", function()
|
|||
end)
|
||||
end)
|
||||
|
||||
describe("get_balancer()", function()
|
||||
it("always returns the same balancer for given request context", function()
|
||||
local backend = {
|
||||
name = "my-dummy-app-6", ["load-balance"] = "ewma",
|
||||
alternativeBackends = { "my-dummy-canary-app-6" },
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } },
|
||||
trafficShapingPolicy = {
|
||||
weight = 0,
|
||||
header = "",
|
||||
headerValue = "",
|
||||
cookie = ""
|
||||
},
|
||||
}
|
||||
local canary_backend = {
|
||||
name = "my-dummy-canary-app-6", ["load-balance"] = "ewma",
|
||||
alternativeBackends = { "my-dummy-canary-app-6" },
|
||||
endpoints = { { address = "11.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } },
|
||||
trafficShapingPolicy = {
|
||||
weight = 5,
|
||||
header = "",
|
||||
headerValue = "",
|
||||
cookie = ""
|
||||
},
|
||||
}
|
||||
|
||||
balancer.sync_backend(backend)
|
||||
balancer.sync_backend(canary_backend)
|
||||
|
||||
mock_ngx({ var = { proxy_upstream_name = backend.name } })
|
||||
|
||||
local expected = balancer.get_balancer()
|
||||
|
||||
for i = 1,50,1 do
|
||||
assert.are.same(expected, balancer.get_balancer())
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("route_to_alternative_balancer()", function()
|
||||
local backend, _balancer
|
||||
|
||||
|
@ -277,8 +315,7 @@ describe("Balancer", function()
|
|||
}
|
||||
}
|
||||
|
||||
local dns_helper = require("test/dns_helper")
|
||||
dns_helper.mock_dns_query({
|
||||
helpers.mock_resty_dns_query(nil, {
|
||||
{
|
||||
name = "example.com",
|
||||
address = "192.168.1.1",
|
||||
|
|
|
@ -115,15 +115,20 @@ describe("Configuration", function()
|
|||
end)
|
||||
|
||||
it("returns a status of 400", function()
|
||||
local original_io_open = _G.io.open
|
||||
_G.io.open = function(filename, extension) return false end
|
||||
assert.has_no.errors(configuration.call)
|
||||
assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST)
|
||||
_G.io.open = original_io_open
|
||||
end)
|
||||
|
||||
it("logs 'dynamic-configuration: unable to read valid request body to stderr'", function()
|
||||
local original_io_open = _G.io.open
|
||||
_G.io.open = function(filename, extension) return false end
|
||||
local s = spy.on(ngx, "log")
|
||||
assert.has_no.errors(configuration.call)
|
||||
assert.spy(s).was_called_with(ngx.ERR, "dynamic-configuration: unable to read valid request body")
|
||||
_G.io.open = original_io_open
|
||||
end)
|
||||
end)
|
||||
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
local _M = {}
|
||||
|
||||
local configuration = require("configuration")
|
||||
local resolver = require("resty.dns.resolver")
|
||||
local old_resolver_new = resolver.new
|
||||
|
||||
local function reset(nameservers)
|
||||
configuration.nameservers = nameservers or { "1.1.1.1" }
|
||||
end
|
||||
|
||||
function _M.mock_new(func, nameservers)
|
||||
reset(nameservers)
|
||||
resolver.new = func
|
||||
end
|
||||
|
||||
function _M.mock_dns_query(response, err)
|
||||
reset()
|
||||
resolver.new = function(self, options)
|
||||
local r = old_resolver_new(self, options)
|
||||
r.query = function(self, name, options, tries)
|
||||
return response, err
|
||||
end
|
||||
return r
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
50
rootfs/etc/nginx/lua/test/helpers.lua
Normal file
50
rootfs/etc/nginx/lua/test/helpers.lua
Normal file
|
@ -0,0 +1,50 @@
|
|||
local _M = {}
|
||||
|
||||
local resty_dns_resolver = require("resty.dns.resolver")
|
||||
|
||||
local original_resty_dns_resolver_new = resty_dns_resolver.new
|
||||
local original_io_open = io.open
|
||||
|
||||
function _M.with_resolv_conf(content, func)
|
||||
local new_resolv_conf_f = assert(io.tmpfile())
|
||||
new_resolv_conf_f:write(content)
|
||||
new_resolv_conf_f:seek("set", 0)
|
||||
|
||||
io.open = function(path, mode)
|
||||
if path ~= "/etc/resolv.conf" then
|
||||
error("expected '/etc/resolv.conf' as path but got: " .. tostring(path))
|
||||
end
|
||||
if mode ~= "r" then
|
||||
error("expected 'r' as mode but got: " .. tostring(mode))
|
||||
end
|
||||
|
||||
return new_resolv_conf_f, nil
|
||||
end
|
||||
|
||||
func()
|
||||
|
||||
io.open = original_io_open
|
||||
|
||||
if io.type(new_resolv_conf_f) ~= "closed file" then
|
||||
error("file was left open")
|
||||
end
|
||||
end
|
||||
|
||||
function _M.mock_resty_dns_new(func)
|
||||
resty_dns_resolver.new = func
|
||||
end
|
||||
|
||||
function _M.mock_resty_dns_query(mocked_host, response, err)
|
||||
resty_dns_resolver.new = function(self, options)
|
||||
local r = original_resty_dns_resolver_new(self, options)
|
||||
r.query = function(self, host, options, tries)
|
||||
if mocked_host and mocked_host ~= host then
|
||||
return error(tostring(host) .. " is not mocked")
|
||||
end
|
||||
return response, err
|
||||
end
|
||||
return r
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
|
@ -11,6 +11,7 @@ do
|
|||
-- if there's more constants need to be whitelisted for test runs, add here.
|
||||
local GLOBALS_ALLOWED_IN_TEST = {
|
||||
_TEST = true,
|
||||
helpers = true,
|
||||
}
|
||||
local newindex = function(table, key, value)
|
||||
rawset(table, key, value)
|
||||
|
@ -33,6 +34,8 @@ do
|
|||
setmetatable(_G, { __newindex = newindex })
|
||||
end
|
||||
|
||||
_G.helpers = require("test.helpers")
|
||||
_G._TEST = true
|
||||
|
||||
local ffi = require("ffi")
|
||||
local lua_ingress = require("lua_ingress")
|
||||
|
|
|
@ -1,43 +1,89 @@
|
|||
describe("resolve", function()
|
||||
local dns = require("util.dns")
|
||||
local dns_helper = require("test/dns_helper")
|
||||
local conf = [===[
|
||||
nameserver 1.2.3.4
|
||||
nameserver 4.5.6.7
|
||||
search ingress-nginx.svc.cluster.local svc.cluster.local cluster.local
|
||||
options ndots:5
|
||||
]===]
|
||||
|
||||
helpers.with_resolv_conf(conf, function()
|
||||
require("util.resolv_conf")
|
||||
end)
|
||||
|
||||
describe("dns.lookup", function()
|
||||
local dns, dns_lookup, spy_ngx_log
|
||||
|
||||
before_each(function()
|
||||
spy_ngx_log = spy.on(ngx, "log")
|
||||
dns = require("util.dns")
|
||||
dns_lookup = dns.lookup
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
package.loaded["util.dns"] = nil
|
||||
end)
|
||||
|
||||
it("sets correct nameservers", function()
|
||||
dns_helper.mock_new(function(self, options)
|
||||
helpers.mock_resty_dns_new(function(self, options)
|
||||
assert.are.same({ nameservers = { "1.2.3.4", "4.5.6.7" }, retrans = 5, timeout = 2000 }, options)
|
||||
return nil, ""
|
||||
end, { "1.2.3.4", "4.5.6.7" })
|
||||
dns.resolve("example.com")
|
||||
end)
|
||||
dns_lookup("example.com")
|
||||
end)
|
||||
|
||||
it("returns host when an error happens", function()
|
||||
local s_ngx_log = spy.on(ngx, "log")
|
||||
describe("when there's an error", function()
|
||||
it("returns host when resolver can not be instantiated", function()
|
||||
helpers.mock_resty_dns_new(function(...) return nil, "an error" end)
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to instantiate the resolver: an error")
|
||||
end)
|
||||
|
||||
dns_helper.mock_new(function(...) return nil, "an error" end)
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to instantiate the resolver: an error")
|
||||
it("returns host when the query returns nil", function()
|
||||
helpers.mock_resty_dns_query(nil, nil, "oops!")
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
|
||||
end)
|
||||
|
||||
dns_helper.mock_dns_query(nil, "oops!")
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
|
||||
it("returns host when the query returns empty answer", function()
|
||||
helpers.mock_resty_dns_query(nil, {})
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno A record resolved\nno AAAA record resolved")
|
||||
end)
|
||||
|
||||
dns_helper.mock_dns_query({ errcode = 1, errstr = "format error" })
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nserver returned error code: 1: format error\nserver returned error code: 1: format error")
|
||||
it("returns host when there's answer but with error", function()
|
||||
helpers.mock_resty_dns_query(nil, { errcode = 1, errstr = "format error" })
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\n" ..
|
||||
"server returned error code: 1: format error\nserver returned error code: 1: format error")
|
||||
end)
|
||||
|
||||
dns_helper.mock_dns_query({})
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno record resolved\nno record resolved")
|
||||
it("retuns host when there's answer but no A/AAAA record in it", function()
|
||||
helpers.mock_resty_dns_query(nil, { { name = "example.com", cname = "sub.example.com", ttl = 60 } })
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno A record resolved\nno AAAA record resolved")
|
||||
end)
|
||||
|
||||
dns_helper.mock_dns_query({ { name = "example.com", cname = "sub.example.com", ttl = 60 } })
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno record resolved\nno record resolved")
|
||||
it("returns host when the query returns nil and number of dots is not less than configured ndots", function()
|
||||
helpers.mock_resty_dns_query(nil, nil, "oops!")
|
||||
assert.are.same({ "a.b.c.d.example.com" }, dns_lookup("a.b.c.d.example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
|
||||
end)
|
||||
|
||||
it("returns host when the query returns nil for a fully qualified domain", function()
|
||||
helpers.mock_resty_dns_query("example.com.", nil, "oops!")
|
||||
assert.are.same({ "example.com." }, dns_lookup("example.com."))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
|
||||
end)
|
||||
end)
|
||||
|
||||
it("resolves all A records of given host, caches them with minimal ttl and returns from cache next time", function()
|
||||
dns_helper.mock_dns_query({
|
||||
it("returns answer from cache if it exists without doing actual DNS query", function()
|
||||
dns._cache:set("example.com", { "192.168.1.1" })
|
||||
assert.are.same({ "192.168.1.1" }, dns_lookup("example.com"))
|
||||
end)
|
||||
|
||||
it("resolves a fully qualified domain without looking at resolv.conf search and caches result", function()
|
||||
helpers.mock_resty_dns_query("example.com.", {
|
||||
{
|
||||
name = "example.com",
|
||||
name = "example.com.",
|
||||
address = "192.168.1.1",
|
||||
ttl = 3600,
|
||||
},
|
||||
|
@ -47,28 +93,43 @@ describe("resolve", function()
|
|||
ttl = 60,
|
||||
}
|
||||
})
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns_lookup("example.com."))
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns._cache:get("example.com."))
|
||||
end)
|
||||
|
||||
local lrucache = require("resty.lrucache")
|
||||
local old_lrucache_new = lrucache.new
|
||||
lrucache.new = function(...)
|
||||
local cache = old_lrucache_new(...)
|
||||
it("starts with host itself when number of dots is not less than configured ndots", function()
|
||||
local host = "a.b.c.d.example.com"
|
||||
helpers.mock_resty_dns_query(host, { { name = host, address = "192.168.1.1", ttl = 3600, } } )
|
||||
|
||||
local old_set = cache.set
|
||||
cache.set = function(self, key, value, ttl)
|
||||
assert.equal("example.com", key)
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, value)
|
||||
assert.equal(60, ttl)
|
||||
return old_set(self, key, value, ttl)
|
||||
end
|
||||
assert.are.same({ "192.168.1.1" }, dns_lookup(host))
|
||||
assert.are.same({ "192.168.1.1" }, dns._cache:get(host))
|
||||
end)
|
||||
|
||||
return cache
|
||||
end
|
||||
it("starts with first search entry when number of dots is less than configured ndots", function()
|
||||
local host = "example.com.ingress-nginx.svc.cluster.local"
|
||||
helpers.mock_resty_dns_query(host, { { name = host, address = "192.168.1.1", ttl = 3600, } } )
|
||||
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns.resolve("example.com"))
|
||||
assert.are.same({ "192.168.1.1" }, dns_lookup(host))
|
||||
assert.are.same({ "192.168.1.1" }, dns._cache:get(host))
|
||||
end)
|
||||
|
||||
dns_helper.mock_new(function(...)
|
||||
error("expected to short-circuit and return response from cache")
|
||||
end)
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns.resolve("example.com"))
|
||||
it("it caches with minimal ttl", function()
|
||||
helpers.mock_resty_dns_query("example.com.", {
|
||||
{
|
||||
name = "example.com.",
|
||||
address = "192.168.1.1",
|
||||
ttl = 3600,
|
||||
},
|
||||
{
|
||||
name = "example.com.",
|
||||
address = "1.2.3.4",
|
||||
ttl = 60,
|
||||
}
|
||||
})
|
||||
|
||||
local spy_cache_set = spy.on(dns._cache, "set")
|
||||
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns_lookup("example.com."))
|
||||
assert.spy(spy_cache_set).was_called_with(match.is_table(), "example.com.", { "192.168.1.1", "1.2.3.4" }, 60)
|
||||
end)
|
||||
end)
|
||||
|
|
64
rootfs/etc/nginx/lua/test/util/resolv_conf_test.lua
Normal file
64
rootfs/etc/nginx/lua/test/util/resolv_conf_test.lua
Normal file
|
@ -0,0 +1,64 @@
|
|||
local original_io_open = io.open
|
||||
|
||||
describe("resolv_conf", function()
|
||||
after_each(function()
|
||||
package.loaded["util.resolv_conf"] = nil
|
||||
io.open = original_io_open
|
||||
end)
|
||||
|
||||
it("errors when file can not be opened", function()
|
||||
io.open = function(...)
|
||||
return nil, "file does not exist"
|
||||
end
|
||||
|
||||
assert.has_error(function() require("util.resolv_conf") end, "could not open /etc/resolv.conf: file does not exist")
|
||||
end)
|
||||
|
||||
it("opens '/etc/resolv.conf' with mode 'r'", function()
|
||||
io.open = function(path, mode)
|
||||
assert.are.same("/etc/resolv.conf", path)
|
||||
assert.are.same("r", mode)
|
||||
|
||||
return original_io_open(path, mode)
|
||||
end
|
||||
|
||||
assert.has_no.errors(function() require("util.resolv_conf") end)
|
||||
end)
|
||||
|
||||
it("correctly parses resolv.conf", function()
|
||||
local conf = [===[
|
||||
# This is a comment
|
||||
nameserver 10.96.0.10
|
||||
nameserver 10.96.0.99
|
||||
search ingress-nginx.svc.cluster.local svc.cluster.local cluster.local
|
||||
options ndots:5
|
||||
]===]
|
||||
|
||||
helpers.with_resolv_conf(conf, function()
|
||||
local resolv_conf = require("util.resolv_conf")
|
||||
assert.are.same({
|
||||
nameservers = { "10.96.0.10", "10.96.0.99" },
|
||||
search = { "ingress-nginx.svc.cluster.local", "svc.cluster.local", "cluster.local" },
|
||||
ndots = 5,
|
||||
}, resolv_conf)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("ignores options that it does not understand", function()
|
||||
local conf = [===[
|
||||
nameserver 10.96.0.10
|
||||
search example.com
|
||||
options debug
|
||||
options ndots:3
|
||||
]===]
|
||||
|
||||
helpers.with_resolv_conf(conf, function()
|
||||
local resolv_conf = require("util.resolv_conf")
|
||||
assert.are.same({
|
||||
nameservers = { "10.96.0.10" },
|
||||
search = { "example.com" },
|
||||
ndots = 3,
|
||||
}, resolv_conf)
|
||||
end)
|
||||
end)
|
||||
end)
|
|
@ -12,24 +12,87 @@ end
|
|||
describe("lua_ngx_var", function()
|
||||
local util = require("util")
|
||||
|
||||
before_each(function()
|
||||
mock_ngx({ var = { remote_addr = "192.168.1.1", [1] = "nginx/regexp/1/group/capturing" } })
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
reset_ngx()
|
||||
package.loaded["monitor"] = nil
|
||||
end)
|
||||
|
||||
it("returns value of nginx var by key", function()
|
||||
assert.equal("192.168.1.1", util.lua_ngx_var("$remote_addr"))
|
||||
describe("lua_ngx_var", function()
|
||||
before_each(function()
|
||||
mock_ngx({ var = { remote_addr = "192.168.1.1", [1] = "nginx/regexp/1/group/capturing" } })
|
||||
end)
|
||||
|
||||
it("returns value of nginx var by key", function()
|
||||
assert.equal("192.168.1.1", util.lua_ngx_var("$remote_addr"))
|
||||
end)
|
||||
|
||||
it("returns value of nginx var when key is number", function()
|
||||
assert.equal("nginx/regexp/1/group/capturing", util.lua_ngx_var("$1"))
|
||||
end)
|
||||
|
||||
it("returns nil when variable is not defined", function()
|
||||
assert.equal(nil, util.lua_ngx_var("$foo_bar"))
|
||||
end)
|
||||
end)
|
||||
|
||||
it("returns value of nginx var when key is number", function()
|
||||
assert.equal("nginx/regexp/1/group/capturing", util.lua_ngx_var("$1"))
|
||||
end)
|
||||
describe("diff_endpoints", function()
|
||||
it("returns removed and added endpoints", function()
|
||||
local old = {
|
||||
{ address = "10.10.10.1", port = "8080" },
|
||||
{ address = "10.10.10.2", port = "8080" },
|
||||
{ address = "10.10.10.3", port = "8080" },
|
||||
}
|
||||
local new = {
|
||||
{ address = "10.10.10.1", port = "8080" },
|
||||
{ address = "10.10.10.2", port = "8081" },
|
||||
{ address = "11.10.10.2", port = "8080" },
|
||||
{ address = "11.10.10.3", port = "8080" },
|
||||
}
|
||||
local expected_added = { "10.10.10.2:8081", "11.10.10.2:8080", "11.10.10.3:8080" }
|
||||
table.sort(expected_added)
|
||||
local expected_removed = { "10.10.10.2:8080", "10.10.10.3:8080" }
|
||||
table.sort(expected_removed)
|
||||
|
||||
it("returns nil when variable is not defined", function()
|
||||
assert.equal(nil, util.lua_ngx_var("$foo_bar"))
|
||||
local added, removed = util.diff_endpoints(old, new)
|
||||
table.sort(added)
|
||||
table.sort(removed)
|
||||
|
||||
assert.are.same(expected_added, added)
|
||||
assert.are.same(expected_removed, removed)
|
||||
end)
|
||||
|
||||
it("returns empty results for empty inputs", function()
|
||||
local added, removed = util.diff_endpoints({}, {})
|
||||
|
||||
assert.are.same({}, added)
|
||||
assert.are.same({}, removed)
|
||||
end)
|
||||
|
||||
it("returns empty results for same inputs", function()
|
||||
local old = {
|
||||
{ address = "10.10.10.1", port = "8080" },
|
||||
{ address = "10.10.10.2", port = "8080" },
|
||||
{ address = "10.10.10.3", port = "8080" },
|
||||
}
|
||||
local new = util.deepcopy(old)
|
||||
|
||||
local added, removed = util.diff_endpoints(old, new)
|
||||
|
||||
assert.are.same({}, added)
|
||||
assert.are.same({}, removed)
|
||||
end)
|
||||
|
||||
it("handles endpoints with nil attribute", function()
|
||||
local old = {
|
||||
{ address = nil, port = "8080" },
|
||||
{ address = "10.10.10.2", port = "8080" },
|
||||
{ address = "10.10.10.3", port = "8080" },
|
||||
}
|
||||
local new = util.deepcopy(old)
|
||||
new[2].port = nil
|
||||
|
||||
local added, removed = util.diff_endpoints(old, new)
|
||||
assert.are.same({ "10.10.10.2:nil" }, added)
|
||||
assert.are.same({ "10.10.10.2:8080" }, removed)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
local string_len = string.len
|
||||
local string_sub = string.sub
|
||||
local string_format = string.format
|
||||
|
||||
local _M = {}
|
||||
|
||||
|
@ -26,6 +27,44 @@ function _M.lua_ngx_var(ngx_var)
|
|||
return ngx.var[var_name]
|
||||
end
|
||||
|
||||
-- normalize_endpoints takes endpoints as an array of endpoint objects
|
||||
-- and returns a table where keys are string that's endpoint.address .. ":" .. endpoint.port
|
||||
-- and values are all true
|
||||
local function normalize_endpoints(endpoints)
|
||||
local normalized_endpoints = {}
|
||||
|
||||
for _, endpoint in pairs(endpoints) do
|
||||
local endpoint_string = string_format("%s:%s", endpoint.address, endpoint.port)
|
||||
normalized_endpoints[endpoint_string] = true
|
||||
end
|
||||
|
||||
return normalized_endpoints
|
||||
end
|
||||
|
||||
-- diff_endpoints compares old and new
|
||||
-- and as a first argument returns what endpoints are in new
|
||||
-- but are not in old, and as a second argument it returns
|
||||
-- what endpoints are in old but are in new.
|
||||
-- Both return values are normalized (ip:port).
|
||||
function _M.diff_endpoints(old, new)
|
||||
local endpoints_added, endpoints_removed = {}, {}
|
||||
local normalized_old, normalized_new = normalize_endpoints(old), normalize_endpoints(new)
|
||||
|
||||
for endpoint_string, _ in pairs(normalized_old) do
|
||||
if not normalized_new[endpoint_string] then
|
||||
table.insert(endpoints_removed, endpoint_string)
|
||||
end
|
||||
end
|
||||
|
||||
for endpoint_string, _ in pairs(normalized_new) do
|
||||
if not normalized_old[endpoint_string] then
|
||||
table.insert(endpoints_added, endpoint_string)
|
||||
end
|
||||
end
|
||||
|
||||
return endpoints_added, endpoints_removed
|
||||
end
|
||||
|
||||
-- this implementation is taken from
|
||||
-- https://web.archive.org/web/20131225070434/http://snippets.luacode.org/snippets/Deep_Comparison_of_Two_Values_3
|
||||
-- and modified for use in this project
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue