Compare commits
98
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
685d084af7
|
||
|
|
9261a48983
|
||
|
|
b040f97149
|
||
|
|
602e2da73c
|
||
|
|
31bf46404a
|
||
|
|
ee1c2f905d
|
||
|
|
b413f2a5f8
|
||
|
|
305ac4f3d3
|
||
|
|
8018ea4bf9
|
||
|
|
ef50dc921f
|
||
|
|
9bc3415111
|
||
|
|
4cf88d674c
|
||
|
|
5ae90e4796
|
||
|
|
8d36dc2f9b
|
||
|
|
6260533b6d
|
||
|
|
60b710c6a8 | ||
|
|
8397222003 | ||
|
|
999aeb7d0b | ||
|
|
9057156d28 | ||
|
|
2c67714a03 | ||
|
|
84afe28bee | ||
|
|
6512da7cc1 | ||
|
|
fab7297623 | ||
|
|
ca2ba767d9 | ||
|
|
f32a2a6955 | ||
|
|
463c7af871 | ||
|
|
981196a5e8 | ||
|
|
ae5d4624b2 | ||
|
|
8f85c320af | ||
|
|
32ad2a1b02 | ||
|
|
b0477a25ff | ||
|
|
81117751ca | ||
|
|
8cfc740455 | ||
|
|
525d517fe1 | ||
|
|
a3d5e0710e | ||
|
|
3fd1ae5bb3 | ||
|
|
f8d0d8e82b | ||
|
|
46cf7c460e | ||
|
|
afd62f23e5 | ||
|
|
e42bc298a4 | ||
|
|
78f596fbda | ||
|
|
c3cc2107c6 | ||
|
|
ac180cfc10 | ||
|
|
ff91a56fc4 | ||
|
|
c3afe55262 | ||
|
|
8c148120fe | ||
|
|
c1993b9df0 | ||
|
|
a0ee8250aa | ||
|
|
e2db8a25bd | ||
|
|
92779f3e57 | ||
|
|
b46c77461d | ||
|
|
bcab10a478 | ||
|
|
664e361ab4 | ||
|
|
d08787902a | ||
|
|
a752f65658 | ||
|
|
e50040883f | ||
|
|
55a46992d4 | ||
|
|
7b087168cf | ||
|
|
a9d663438f | ||
|
|
0e7b15b329 | ||
|
|
c1d3107418 | ||
|
|
962bb57b60 | ||
|
|
ea27069eb9 | ||
|
|
00f8afd743 | ||
|
|
6093383cb3 | ||
|
|
0d58e17954 | ||
|
|
1ceeca80e6 | ||
|
|
126f3ab928 | ||
|
|
65cbea0505 | ||
|
|
6365328d04 | ||
|
|
c085ebed05 | ||
|
|
668bbcf4ed | ||
|
|
4b47fc607c | ||
|
|
cdf761b8a3 | ||
|
|
559027b4f0 | ||
|
|
901e889ace | ||
|
|
e84a000b92 | ||
|
|
88d8bb648f | ||
|
|
1bb1f02b1d | ||
|
|
2f49b8455a | ||
|
|
df2240acbd | ||
|
|
c241c455c5 | ||
|
|
90a5d945c2 | ||
|
|
273c611d43 | ||
|
|
ee3b7ce368 | ||
|
|
7140e40477 | ||
|
|
8c8476381d | ||
|
|
25ccf44179 | ||
|
|
d30828a3f2 | ||
|
|
6d7a29ade6 | ||
|
|
1686ded5d5 | ||
|
|
9a5e1e333d | ||
|
|
e3712152d2 | ||
|
|
53b12b6c23 | ||
|
|
5367e0efb1 | ||
|
|
77445d0d9e | ||
|
|
34de0b10a6 | ||
|
|
344a4835e5 |
No files matched your search
@@ -0,0 +1,13 @@
|
||||
# Git status should be ignored
|
||||
.git
|
||||
|
||||
# .dockerignore should be ignored
|
||||
.dockerignore
|
||||
|
||||
# Almost all of the reweave directory
|
||||
# should be ignored
|
||||
reweave/build/Dockerfile
|
||||
reweave/tools/
|
||||
reweave/scans/
|
||||
reweave/Makefile
|
||||
reweave/*.md
|
||||
@@ -0,0 +1,67 @@
|
||||
# Build the images the cluster runs (weave-kube, weave-npc) and push them to
|
||||
# this Gitea's container registry. CI stops here: rolling them out is the
|
||||
# CD side's job, not this workflow's.
|
||||
#
|
||||
# Tags are the full commit SHA, so a tag never moves: rebuilding a commit
|
||||
# pushes the same name again instead of changing what an existing name means.
|
||||
# linux/amd64 only for now (every node is amd64).
|
||||
name: images
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "website/**"
|
||||
- "docs/**"
|
||||
- "**.md"
|
||||
workflow_dispatch:
|
||||
|
||||
# The job token can't log in to the registry (no package scope there), so
|
||||
# the push uses the reweave-ci bot's token from the org secret REGISTRY_TOKEN.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
images:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
REGISTRY: git.levk.dev
|
||||
IMAGE_PREFIX: git.levk.dev/reweave
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0 # tags, for the version string
|
||||
submodules: true # tools/ (weaveworks/build-tools): make all builds tools/runner
|
||||
|
||||
- name: Log in to the registry
|
||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY" -u reweave-ci --password-stdin
|
||||
|
||||
- name: Build and push (linux/amd64)
|
||||
run: |
|
||||
set -eu
|
||||
sha=$(git rev-parse HEAD)
|
||||
version=$(git describe --tags --always | sed 's/^v//')
|
||||
for pair in weavekubeimage:weave-kube weavenpcimage:weave-npc; do
|
||||
target=${pair%%:*}
|
||||
image="$IMAGE_PREFIX/${pair#*:}:$sha"
|
||||
docker buildx build --push --progress=plain \
|
||||
--platform=linux/amd64 \
|
||||
--target="$target" \
|
||||
--build-arg=WEAVE_VERSION="$version" \
|
||||
--build-arg=revision="$sha" \
|
||||
--build-arg=imageversion="$version" \
|
||||
-f reweave/build/Dockerfile \
|
||||
-t "$image" \
|
||||
.
|
||||
echo "pushed $image"
|
||||
done
|
||||
|
||||
# The images live in the registry now; the runner keeps only a bounded build cache. Without this the
|
||||
# runner's 10Gi Docker volume fills up after a handful of builds (no space left on device mid-build).
|
||||
- name: Free the runner's disk
|
||||
if: always()
|
||||
run: |
|
||||
set -u
|
||||
docker image ls --format '{{.Repository}}:{{.Tag}}' | grep "^$IMAGE_PREFIX/" | xargs -r docker image rm -f
|
||||
docker image prune -f
|
||||
docker builder prune -f --keep-storage 4GB
|
||||
docker system df
|
||||
@@ -0,0 +1,53 @@
|
||||
# Workflow for building and deploying a Jekyll site to GitHub Pages
|
||||
name: Build using Jekyll, deploy to GitHub Pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the master branch, if changes found in
|
||||
# the website directory
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths: ["website/**"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
- name: Build with Jekyll
|
||||
uses: actions/jekyll-build-pages@v1
|
||||
with:
|
||||
source: ./website/
|
||||
destination: ./_site
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -73,6 +73,3 @@ test/images/network-tester/webserver
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
prog/net-plugin/rootfs/
|
||||
|
||||
# Website
|
||||
site-build
|
||||
@@ -1 +0,0 @@
|
||||
2.6.2
|
||||
@@ -1,3 +1,65 @@
|
||||
## Release 2.9.0
|
||||
|
||||
This release has upgraded dependencies based on CVE recommendations.
|
||||
|
||||
Also, from this release onwards, all weave images will use the `iptables-nft` backend for `iptables` by default. To use the legacy mode, set the environment variable `IPTABLES_BACKEND` to `legacy`. To do this for the Docker plugin, export the variable before using the `weave` script. For the Kubernetes addon, set the variable in the manifest.
|
||||
|
||||
## Release 2.8.10
|
||||
|
||||
This release bumps the Go version to 1.23.1 and the alpine base image to version 3.20.3 as per CVE recommendations.
|
||||
|
||||
## Release 2.8.9
|
||||
|
||||
This release bumps the Go version to 1.22.2, the alpine base image to version 3.20.2, and upgrades a dependency as per CVE recommendations.
|
||||
|
||||
## Release 2.8.8
|
||||
|
||||
The alpine base image used by `weave-kube` and `weave-npc` has been upgraded to version 3.20.0 in this release. This removes several CVEs.
|
||||
|
||||
## Release 2.8.7
|
||||
|
||||
This release has upgraded dependencies based on CVE recommendations.
|
||||
|
||||
Also, this release does not provides images for the `ppc64le` and `s390x` architectures, because a crucial upstream dependency (libpcap-0.8, from the debian buster respository) does not provide binaries for those architectures any more.
|
||||
|
||||
## Release 2.8.6
|
||||
|
||||
From this release onwards, the Docker V2 Plugin will be published for all supported architectures. It will be named using the pattern `rajchaudhuri/net-plugin:<version>-<arch>`. The latest version will also be published as `rajchaudhuri/net-plugin:latest_release-<arch>`. Supported architectures are `amd64`,`arm`,`arm64`,`ppc64le` and `s390x`. It can be installed on a Swarm-mode-enabled Docker host as follows:
|
||||
|
||||
```bash
|
||||
ARCH=$(docker version -f '{{.Server.Arch}}')
|
||||
docker plugin install rajchaudhuri/net-plugin:latest_release-$ARCH
|
||||
```
|
||||
|
||||
## Release 2.8.5
|
||||
|
||||
This release has upgraded dependencies based on CVE recommendations.
|
||||
|
||||
* Build with Go version 1.21.9
|
||||
* A dependency has been updated
|
||||
|
||||
## Release 2.8.4
|
||||
|
||||
The alpine base image used by `weave-kube` and `weave-npc` was upgraded to version 3.19.1 in the previous release. In this version, the default iptables backend is nftables, and the legacy backend is not included. Our scripts and programs assume legacy as the default backend, and change to nft if autodetected, or if we ask for it. So, the build Dockerfile (reweave/build/Dockerfile) was changed to also install the Alpine `iptables-legacy` package , and change the `iptables-{save,restore}` symbolic links to point to the legacy backend by default.
|
||||
|
||||
The `weave-kube` and `weave-npc` images can now log traces if the environment WEAVE_DEBUG is set in the manifest.
|
||||
|
||||
## Release 2.8.3
|
||||
|
||||
The docker API client version, used by the proxy package and the weaveutil command, was bumped from 1.18 to 1.24. As of March 2024, Docker API versions below 1.24 are deprecated. This means that the minimum supported Docker version is now 1.12.0.
|
||||
|
||||
Provision was made in weaveutil program and the weave script to override the API version used, via the environment variable `DOCKER_API_VERSION`. The same variable is used by standard docker clients.
|
||||
|
||||
## Release 2.8.2
|
||||
|
||||
This is the first release since forking from weaveworks/weave. While no new functionality has been added, there have been many changes.
|
||||
|
||||
* Build with Go version 1.21.6
|
||||
* Dependencies, especially ones with associated CVEs, have been updated
|
||||
* libCNI has been upgraded to v1.1.2
|
||||
* A new, Docker-only build process has been created, which produces proper multi-arch images
|
||||
* A vulnerability scanning process has been created using [grype](https://github.com/anchore/grype)
|
||||
|
||||
## Release 2.8.1
|
||||
|
||||
Fixes a problem introduced in 2.8.0 for machines whose ID is in /etc/machine-id #3886
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
# History of the ReWeave project
|
||||
|
||||
## How it started
|
||||
|
||||
In June 2022, Weave Net had not been updated for a year. Problems were starting to appear in the field. In particular, the last published images on the Docker Hub ([weaveworks/weave-kube](https://hub.docker.com/r/weaveworks/weave-kube) and [weaveworks/weave-npc](https://hub.docker.com/r/weaveworks/weave-npc), v2.8.1) had issues supporting multiple processor architectures, and security scanners showed multiple vulnerabilities.
|
||||
|
||||
A call went out from Weaveworks to get the community more involved in maintaining it. After some discussion on GitHub issues and e-mail, and even a few online meetings, things were not moving forward.
|
||||
|
||||
## ReWeave begins
|
||||
|
||||
Finally, in March 2023, this fork was created, with the following goals in mind:
|
||||
|
||||
* Update dependencies, especially ones with security vulnerabilities
|
||||
* Make minimal code changes _only_ when required by updating dependencies
|
||||
* Create true multi-arch images using modern tools
|
||||
* Create a new build process to automate all this
|
||||
* Do all this with _minimal_ changes to the existing codebase. Keep all new things in the `reweave` folder.
|
||||
|
||||
These goals were achieved. Details can be found in the [reweave](reweave/README.md) directory. A [pull request](https://github.com/weaveworks/weave/pull/3996) was submitted on the weaveworks repo, with the aim of getting a new official release out.
|
||||
|
||||
## Weaveworks ends
|
||||
|
||||
On February 5th, 2024, Weaveworks CEO Alexis Richardson announced via [LinkedIn](https://www.linkedin.com/posts/richardsonalexis_hi-everyone-i-am-very-sad-to-announce-activity-7160295096825860096-ZS67/) and [Twitter](https://twitter.com/monadic/status/1754530336120140116) that Weaveworks is winding down.
|
||||
|
||||
So, a decision was taken to maintain this fork independently.
|
||||
|
||||
## We're forked
|
||||
|
||||
Two major changes were introduced at this point:
|
||||
|
||||
* The module name was changed to `github.com/rajch/weave` (previously `github.com/weaveworks/weave`)
|
||||
* The default registry account for publishing images was changed to `docker.io/rajchaudhuri` (previously `docker.io/weaveworks`)
|
||||
|
||||
In addition, the old repo structure and codebase is not longer sacrosanct. Things can be moved around, new code can be added outside the `reweave` directory, old code can be modified or deleted as necessary.
|
||||
|
||||
The version numbers will continue from where Weaveworks left off.
|
||||
|
||||
## New Goals
|
||||
|
||||
The old goals, listed above (except the last one), remain the priority. In addition, this project aims to:
|
||||
|
||||
* Remove dependencies on Weaveworks infrastructure, starting with telemetry (what weaveworks called checkpoint)
|
||||
* Publish new releases regularly, duly security scanned
|
||||
* Provide supporting infrastructure, such as weave's famous one-line installation, where possible
|
||||
@@ -1,441 +0,0 @@
|
||||
.DEFAULT: all
|
||||
.PHONY: all exes testrunner update tests lint publish-one-arch $(PUBLISH) clean clean-bin clean-work-dir prerequisites build run-smoketests
|
||||
|
||||
# If you can use docker without being root, you can do "make SUDO="
|
||||
SUDO=$(shell docker info >/dev/null 2>&1 || echo "sudo -E")
|
||||
BUILD_IN_CONTAINER=true
|
||||
RM=--rm
|
||||
RUN_FLAGS=-ti
|
||||
COVERAGE=
|
||||
|
||||
# This specifies the architecture we're building for
|
||||
ARCH?=amd64
|
||||
|
||||
# We're using QEMU to be able to build Docker images for other architectures
|
||||
# from amd64 machines.
|
||||
QEMU_VERSION=v2.7.0
|
||||
|
||||
# A list of all supported architectures here. Should be named as Go is naming platforms
|
||||
# All supported architectures must have an "ifeq" block below that customizes the parameters
|
||||
ALL_ARCHITECTURES=amd64 arm arm64 ppc64le s390x
|
||||
ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64,linux/ppc64le,linux/s390x
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=x86_64
|
||||
|
||||
# The name of the alpine baseimage to use as the base for weave images
|
||||
ALPINE_BASEIMAGE?=alpine:3.10
|
||||
|
||||
# The extension for the made images
|
||||
# Specifying none means for example weaveworks/weave:latest
|
||||
ARCH_EXT?=
|
||||
|
||||
# The name of the gcc we're using for compiling C code
|
||||
CC=gcc
|
||||
|
||||
# Optional parameters that can be passed when linking C code into the Go binary
|
||||
CGO_LDFLAGS=
|
||||
endif
|
||||
ifeq ($(ARCH),arm)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=arm32v6/alpine:3.8
|
||||
|
||||
# arm images have the -arm suffix, for instance weaveworks/weave-arm:latest
|
||||
ARCH_EXT?=-arm
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=arm-linux-gnueabihf-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=arm
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),arm64)
|
||||
# Use the arm 32-bit docker client variant
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=arm64v8/alpine:3.8
|
||||
|
||||
# arm64 images have the -arm64 suffix, for instance weaveworks/weave-arm64:latest
|
||||
ARCH_EXT?=-arm64
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=aarch64-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=aarch64
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm64 are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),ppc64le)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=ppc64le
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=ppc64le/alpine:3.8
|
||||
|
||||
# ppc64le images have the -ppc64le suffix, for instance weaveworks/weave-ppc64le:latest
|
||||
ARCH_EXT?=-ppc64le
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=powerpc64le-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=ppc64le
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for ppc64le are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),s390x)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=s390x
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=s390x/alpine:3.6
|
||||
|
||||
# s390x images have the -s390x suffix, for instance weaveworks/weave-s390x:latest
|
||||
ARCH_EXT?=-s390x
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=s390x-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=s390x
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for s390x are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
|
||||
# The name of the user that this Makefile should produce image artifacts for. Can/should be overridden
|
||||
DOCKERHUB_USER?=weaveworks
|
||||
# The default version that's chosen when pushing the images. Can/should be overridden
|
||||
GIT_REVISION=$(shell git rev-parse HEAD)
|
||||
WEAVE_VERSION?=git-$(shell git rev-parse --short=12 HEAD)
|
||||
# Docker Store does not allow the "latest" tag.
|
||||
NET_PLUGIN_LATEST=latest_release
|
||||
|
||||
# Paths to all relevant binaries that should be compiled
|
||||
WEAVER_EXE=prog/weaver/weaver
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
KUBEUTILS_EXE=prog/kube-utils/kube-utils
|
||||
WEAVENPC_EXE=prog/weave-npc/weave-npc
|
||||
WEAVEWAIT_EXE=prog/weavewait/weavewait
|
||||
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
|
||||
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
|
||||
WEAVEUTIL_EXE=prog/weaveutil/weaveutil
|
||||
RUNNER_EXE=tools/runner/runner
|
||||
# Get this from https://github.com/estesp/manifest-tool/releases
|
||||
MANIFEST_TOOL_CMD=manifest-tool
|
||||
TEST_TLS_EXE=test/tls/tls
|
||||
NETWORKTESTER_EXE=test/images/network-tester/webserver
|
||||
|
||||
# All binaries together in a list
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(NETWORKTESTER_EXE)
|
||||
|
||||
# These stamp files are used to mark the current state of the build; whether an image has been built or not
|
||||
BUILD_UPTODATE=.build.uptodate
|
||||
WEAVER_UPTODATE=.weaver$(ARCH_EXT).uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec$(ARCH_EXT).uptodate
|
||||
PLUGIN_UPTODATE=.net-plugin$(ARCH_EXT).uptodate
|
||||
WEAVEKUBE_UPTODATE=.weavekube$(ARCH_EXT).uptodate
|
||||
WEAVENPC_UPTODATE=.weavenpc$(ARCH_EXT).uptodate
|
||||
WEAVEDB_UPTODATE=.weavedb.uptodate
|
||||
NETWORKTESTER_UPTODATE=.networktester$(ARCH_EXT).uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE) $(NETWORKTESTER_UPTODATE)
|
||||
|
||||
# The names of the images. Note that the images for other architectures than amd64 have a suffix in the image name.
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave$(ARCH_EXT)
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec$(ARCH_EXT)
|
||||
WEAVEKUBE_IMAGE=$(DOCKERHUB_USER)/weave-kube$(ARCH_EXT)
|
||||
WEAVENPC_IMAGE=$(DOCKERHUB_USER)/weave-npc$(ARCH_EXT)
|
||||
BUILD_IMAGE=weaveworks/weavebuild
|
||||
WEAVEDB_IMAGE=$(DOCKERHUB_USER)/weavedb
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/net-plugin
|
||||
NETWORKTESTER_IMAGE=$(DOCKERHUB_USER)/network-tester
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(WEAVEKUBE_IMAGE) $(WEAVENPC_IMAGE) $(WEAVEDB_IMAGE) $(NETWORKTESTER_IMAGE)
|
||||
|
||||
PLUGIN_WORK_DIR="prog/net-plugin/rootfs"
|
||||
PLUGIN_BUILD_IMG="plugin-builder"
|
||||
|
||||
PUBLISH=publish_weave publish_weaveexec publish_weave-kube publish_weave-npc
|
||||
PUSH_ML=push_ml_weave push_ml_weaveexec push_ml_weave-kube push_ml_weave-npc
|
||||
|
||||
WEAVE_EXPORT=weave$(ARCH_EXT).tar.gz
|
||||
|
||||
# The flags we are passing to go build. -extldflags -static for making a static binary,
|
||||
# -X main.version for telling the Go binary which version it is,
|
||||
# -s -w drop symbol tables used by debuggers (not Go's internal symbol info)
|
||||
# -tags "osusergo netgo" to use native Go UID and DNS implementations
|
||||
BUILD_FLAGS=-ldflags "-extldflags -static -X main.version=$(WEAVE_VERSION) -s -w" -tags "osusergo netgo"
|
||||
|
||||
PACKAGE_BASE=$(shell go list -e ./)
|
||||
|
||||
all: $(WEAVE_EXPORT)
|
||||
testrunner: $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go db/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVER_EXE): api/*.go plugin/*.go plugin/*/*
|
||||
$(WEAVER_EXE): proxy/*.go
|
||||
$(WEAVEUTIL_EXE): prog/weaveutil/*.go net/*.go plugin/net/*.go plugin/ipam/*.go db/*.go
|
||||
$(SIGPROXY_EXE): prog/sigproxy/*.go
|
||||
$(KUBEUTILS_EXE): prog/kube-utils/*.go
|
||||
$(WEAVENPC_EXE): prog/weave-npc/*.go npc/*.go npc/*/*.go
|
||||
$(TEST_TLS_EXE): test/tls/*.go
|
||||
$(RUNNER_EXE): tools/runner/*.go
|
||||
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
|
||||
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
|
||||
$(NETWORKTESTER_EXE): test/images/network-tester/*.go
|
||||
tests: tools/.git
|
||||
lint: tools/.git
|
||||
|
||||
ifeq ($(BUILD_IN_CONTAINER),true)
|
||||
|
||||
# This make target compiles all binaries inside of the weaveworks/build container
|
||||
# It bind-mounts the source into the container and passes all important variables
|
||||
exes $(EXES) tests lint: $(BUILD_UPTODATE)
|
||||
$(SUDO) docker run $(RM) $(RUN_FLAGS) \
|
||||
-v $(shell pwd):/go/src/github.com/weaveworks/weave \
|
||||
-e GOARCH=$(ARCH) -e CGO_ENABLED=1 -e GOOS=linux -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW -e DEBUG \
|
||||
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) CC=$(CC) QEMUARCH=$(QEMUARCH) CGO_LDFLAGS=$(CGO_LDFLAGS) $@
|
||||
touch $@
|
||||
|
||||
else
|
||||
|
||||
exes: $(EXES)
|
||||
|
||||
$(WEAVER_EXE):
|
||||
ifeq ($(COVERAGE),true)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | grep -v "^$(PACKAGE_BASE)/vendor/" | paste -s -d,))
|
||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||
else
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
endif
|
||||
|
||||
$(WEAVEUTIL_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(NETWORKTESTER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "osusergo netgo iface mcast" -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_NOMCAST_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "osusergo netgo iface" -o $@ ./$(@D)
|
||||
|
||||
# These programs need a separate rule as they fail the netgo check in
|
||||
# the main build stanza due to not importing net package
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
tests:
|
||||
./tools/test -no-go-get -netgo -timeout 8m
|
||||
|
||||
lint:
|
||||
./tools/lint -nocomment -notestpackage
|
||||
|
||||
endif
|
||||
|
||||
# This rule makes sure the build image is up-to-date.
|
||||
# It also makes sure the multiarch hooks are reqistered in the kernel so the QEMU emulation works
|
||||
$(BUILD_UPTODATE): build/* tools/.git
|
||||
$(SUDO) docker build -t $(BUILD_IMAGE) build/
|
||||
$(SUDO) docker tag $(BUILD_IMAGE) $(BUILD_IMAGE):$(shell tools/image-tag)
|
||||
$(SUDO) docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
touch $@
|
||||
|
||||
# Creates the Dockerfile.your-user-here file from the template
|
||||
# Also replaces all placeholders with real values
|
||||
# If the architecture is amd64, it deletes all CROSS_BUILD lines
|
||||
# but otherwise, it only removes the "CROSS_BUILD_" placeholder and downloads QEMU
|
||||
%/Dockerfile.$(DOCKERHUB_USER): %/Dockerfile.template
|
||||
echo "DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)"
|
||||
sed -e "s|DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)|g" $^ > $@
|
||||
ifeq ($(ARCH),amd64)
|
||||
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
|
||||
sed -i.bak "/CROSS_BUILD_/d" $@
|
||||
else
|
||||
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for ARM and ppc64le binaries in the kernel
|
||||
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMU_VERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(shell dirname $@)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/qemu-$(QEMUARCH)-static.sha256sum
|
||||
sed -i.bak "s/CROSS_BUILD_//g" $@
|
||||
endif
|
||||
|
||||
# The targets below builds the weave images
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) weave $(WEAVEUTIL_EXE)
|
||||
cp $(WEAVEUTIL_EXE) prog/weaver/weaveutil
|
||||
cp weave prog/weaver/weave
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
|
||||
touch $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexec/symlink $(SIGPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
touch $@
|
||||
|
||||
# Builds Docker plugin.
|
||||
$(PLUGIN_UPTODATE): prog/net-plugin/launch.sh prog/net-plugin/config.json $(WEAVER_UPTODATE)
|
||||
-$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG) 2>/dev/null
|
||||
$(SUDO) docker create --name=$(PLUGIN_BUILD_IMG) $(WEAVER_IMAGE) true
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
mkdir $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker export $(PLUGIN_BUILD_IMG) | tar -x -C $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG)
|
||||
cp prog/net-plugin/launch.sh $(PLUGIN_WORK_DIR)/home/weave/launch.sh
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(WEAVE_VERSION) prog/net-plugin
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) prog/net-plugin
|
||||
touch $@
|
||||
|
||||
$(WEAVEKUBE_UPTODATE): prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) prog/weave-kube/launch.sh $(KUBEUTILS_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(KUBEUTILS_EXE) prog/weave-kube/
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEKUBE_IMAGE) prog/weave-kube
|
||||
touch $@
|
||||
|
||||
$(WEAVENPC_UPTODATE): prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) $(WEAVENPC_EXE) prog/weave-npc/ulogd.conf
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVENPC_IMAGE) prog/weave-npc
|
||||
touch $@
|
||||
|
||||
$(WEAVEDB_UPTODATE): prog/weavedb/Dockerfile
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -t $(WEAVEDB_IMAGE) prog/weavedb
|
||||
touch $@
|
||||
|
||||
$(NETWORKTESTER_UPTODATE): test/images/network-tester/Dockerfile $(NETWORKTESTER_EXE)
|
||||
$(SUDO) docker build -t $(NETWORKTESTER_IMAGE) test/images/network-tester
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||
|
||||
tools/.git:
|
||||
git submodule update --init
|
||||
|
||||
# CODE FOR PUBLISHING THE IMAGES
|
||||
|
||||
# Push plugin
|
||||
plugin_publish: $(PLUGIN_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(WEAVE_VERSION)
|
||||
# "$(NET_PLUGIN_LATEST)" means "stable release" here, so only push that if explicitly told to
|
||||
ifeq ($(UPDATE_LATEST),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST)
|
||||
endif
|
||||
|
||||
# This target first runs "make publish" for each architecture
|
||||
# Then it pushes the manifest lists
|
||||
publish: $(addprefix sub-publish-,$(ALL_ARCHITECTURES)) $(WEAVEDB_UPTODATE)
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(PUSH_ML)
|
||||
ifeq ($(PUBLISH_WEAVEDB),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/weavedb:latest
|
||||
endif
|
||||
|
||||
sub-publish-%:
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) clean-bin
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(IMAGES_UPTODATE)
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) publish-one-arch
|
||||
|
||||
publish-one-arch: $(PUBLISH)
|
||||
|
||||
# This rule handles the pushing of the built images
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
# Tag :latest with the real version
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*$(ARCH_EXT) $(DOCKERHUB_USER)/$*$(ARCH_EXT):$(WEAVE_VERSION)
|
||||
# Push the image with the arch suffix for arm, arm64 and ppc64le, and without suffix for amd64
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*$(ARCH_EXT)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# If the architecture is amd64, add the -amd64 suffix.
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION)
|
||||
# If the version is latest, tag the -amd64 with latest as well
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:latest
|
||||
endif
|
||||
# Push the image with the -amd64-suffix so BINARY-ARCH-named images exist for all arches, so manifest lists may be made and ARCH is replaceable in scripts
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*-amd64
|
||||
endif
|
||||
|
||||
# This target pushes an image, and if UPDATE_LATEST is anything but false, also updates the latest tag
|
||||
# It takes one parameter, the image name
|
||||
push_%:
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
# This target pushes a manifest list; it takes one parameter, the image name.
|
||||
# The variable UPDATE_LATEST controls whether it updates the versioned tag and/or the latest tag
|
||||
$(PUSH_ML): push_ml_%:
|
||||
ifneq ($(UPDATE_LATEST),latest-only)
|
||||
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:$(WEAVE_VERSION) --target $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
endif
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
# Push the manifest list to :latest as well
|
||||
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:latest --target $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
clean-work-dir:
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
|
||||
clean-bin:
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
|
||||
find prog -type f -name "Dockerfile.*" -not -name "Dockerfile.template" -print | xargs rm -f
|
||||
find prog -type f -name "*qemu-*" -print | xargs rm -f
|
||||
go clean ./...
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE) weave*.tar.gz .cache
|
||||
|
||||
clean: clean-bin clean-work-dir
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(BUILD_IMAGE)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE)
|
||||
|
||||
build:
|
||||
$(MAKE)
|
||||
|
||||
run-smoketests: all testrunner
|
||||
cd test && ./setup.sh && ./run_all.sh
|
||||
|
||||
integration-tests: all testrunner
|
||||
# Usage:
|
||||
# $ make \
|
||||
# NAME="<prefix used to name VMs and other resources>" \
|
||||
# PROVIDER="<provider among {vagrant|gcp|aws|do}>" \
|
||||
# NUM_HOSTS="<# test machines>" \
|
||||
# PLAYBOOK="<filename>" \
|
||||
# RUNNER_ARGS="<...>" \
|
||||
# TESTS="<...>" \ # Can be set to only run one or a few tests instead of the full test suite.
|
||||
# DOCKER_VERSION=<...> \
|
||||
# KUBERNETES_VERSION=<...> \
|
||||
# KUBERNETES_CNI_VERSION=<...> \
|
||||
# <...> # See also run-integration-test.sh for all variables and individual functions.
|
||||
# integration-tests
|
||||
#
|
||||
RUNNER_ARGS="-parallel" ./test/run-integration-tests.sh
|
||||
|
||||
# This target is only intended for use in Netlify CI environment for generating preview pages on feature branches and pull requests.
|
||||
# We need to obtain website repository (checked out under `site-build`) and place `site` directory into the context (`site-build/_weave_net_docs`).
|
||||
# We then run make in `site-build` and Netlify will publish the output (`site-build/_site`).
|
||||
netlify-site-preview:
|
||||
@mkdir -p site-build
|
||||
@curl --user $(WEBSITE_GITHUB_USER) --silent 'https://codeload.github.com/weaveworks/website-next/tar.gz/$(WEBSITE_BRANCH)' \
|
||||
| tar --extract --gunzip --directory site-build --strip 1
|
||||
@cp -r site site-build/_weave_net_docs
|
||||
@$(MAKE) -C site-build netlify_ensure_install
|
||||
@$(MAKE) -C site-build BUILD_ENV=netlify
|
||||
@@ -1,41 +1,36 @@
|
||||
# Weave Net - Weaving Containers into Applications
|
||||
|
||||
[](https://circleci.com/gh/weaveworks/weave)
|
||||
[](https://coveralls.io/r/weaveworks/weave)
|
||||
[](https://goreportcard.com/report/github.com/weaveworks/weave)
|
||||
[](https://hub.docker.com/r/weaveworks/weave/)
|
||||
|
||||
# About Weaveworks
|
||||
|
||||
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control Docker containers.
|
||||
|
||||
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, with over 8 million downloads to date. Weave Net enables you to get started with Docker clusters and portable apps in a fraction of the time required by other solutions.
|
||||
|
||||
## Weave Cloud:
|
||||
|
||||
[Weave Cloud](https://www.weave.works/product/cloud/) is software-as-a-service that simplifies deployment, monitoring and management for containers and microservices. It extends and complements popular orchestrators, and enables developers and DevOps to realize faster deployments, insightful monitoring, visualization and networking.
|
||||
|
||||
Weave Cloud is built using these Open Source projects: [Weave Scope](https://www.weave.works/products/weave-scope/), a powerful container visualization tool that automatically maps Docker containers and their interactions, [Weave Cortex](https://github.com/weaveworks/cortex), a horizontally-scalable version of Prometheus, and [Weave Flux](https://www.weave.works/products/weave-flux/), a continuous deployment tool that works with Kubernetes.
|
||||
|
||||
# Weave Net
|
||||
|
||||
Weave Net creates a virtual network that connects Docker containers across multiple hosts and enables their automatic discovery. With Weave Net, portable microservices-based applications consisting of multiple containers can run anywhere: on one host, multiple hosts or even across cloud providers and data centers. Applications use the network just as if the containers were all plugged into the same network switch, without having to configure port mappings, ambassadors or links.
|
||||
This repository contains a fork of Weave Net, the first product developed by Weaveworks. Since Weaveworks has shut down, this repo aims to continue maintaining Weave Net, and to publish releases regularly.
|
||||
|
||||
Services provided by application containers on the Weave network can be exposed to the outside world, regardless of where they are running. Similarly, existing internal systems can be opened to accept connections from application containers irrespective of their location.
|
||||
[](https://goreportcard.com/report/github.com/rajch/weave)
|
||||
[](https://hub.docker.com/r/rajchaudhuri/weave-kube)
|
||||
[](https://github.com/rajch/weave/releases)
|
||||
[](reweave/scans/report.md)
|
||||
|
||||
## <a name="help"></a>Getting help
|
||||
The history of the fork can be found in [HISTORY.md](HISTORY.md).
|
||||
|
||||
If you have any questions about, feedback for or a problem with Weave Net:
|
||||
## Using Weave Net on Kubernetes
|
||||
|
||||
- Read [the Weave Net docs](https://www.weave.works/docs/net/latest/overview).
|
||||
- Invite yourself to the <a href="https://slack.weave.works/" target="_blank">Weave Users Slack</a>.
|
||||
- The channel for contributors and developers of Weave Net is [#net-dev](https://weave-community.slack.com/messages/net-dev/).
|
||||
- Join the <a href="https://www.meetup.com/pro/Weave/"> Weave User Group </a> and get invited to online talks, hands-on training and meetups in your area.
|
||||
- [File an issue](https://github.com/weaveworks/weave/issues/new).
|
||||
On a newly created Kubernetes cluster, the Weave Net CNI pluging can be installed by running the following command:
|
||||
|
||||
Your feedback is always welcome!
|
||||
```
|
||||
kubectl apply -f https://reweave.azurewebsites.net/k8s/v1.29/net.yaml
|
||||
```
|
||||
|
||||
## Further Information
|
||||
Replace `v1.29` with the version on Kubernetes on your cluster.
|
||||
|
||||
* [Installing Weave Net](https://www.weave.works/docs/net/latest/install/installing-weave)
|
||||
* [Weave Net Product Overview](https://www.weave.works/products/weave-net)
|
||||
That endpoint is provided by the companion project [weave-endpoint](https://github.com/rajch/weave-endpoint).
|
||||
|
||||
## Using Weave Net in other ways
|
||||
|
||||
Please refer to the [documentation](https://rajch.github.io/weave).
|
||||
|
||||
## Building Weave Net
|
||||
|
||||
Details can be found [here](reweave/BUILDING.md).
|
||||
|
||||
## Documentation status
|
||||
|
||||
The public documentation that used to exist in the `site` directory has been moved to the `original/site` directory. A new `website` directory has been created, and populated with the content of the `original/site` directory, rearranged and reformatted for being built with Jekyll and published to the GitHub pages site [https://rajch.github.io/weave](https://rajch.github.io/weave).
|
||||
|
||||
The documentation will now be maintained and published from the `website` directory exclusively.
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
At Weaveworks we take security very seriously, and value our close relationship with members of the security community.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
As an Open Source project, only the latest code has all fixes and security patches.
|
||||
If you require a higher level of commitment, please [contact Weaveworks](https://www.weave.works/contact/) to discuss.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To submit a security bug report please e-mail us at security@weave.works.
|
||||
|
||||
We also operate a Bug Bounty programme; see https://www.weave.works/bug-bounty/
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/rajch/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,65 +1,119 @@
|
||||
module github.com/weaveworks/weave
|
||||
module github.com/rajch/weave
|
||||
|
||||
go 1.14
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.7 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/andybalholm/go-bit v0.0.0-20150501193155-34813fe0e46a
|
||||
github.com/andybalholm/go-bit v1.0.1
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/aws/aws-sdk-go v1.13.22
|
||||
github.com/aws/aws-sdk-go v1.34.0
|
||||
github.com/boltdb/bolt v1.3.1
|
||||
github.com/containerd/continuity v0.0.0-20180322171221-3e8f2ea4b190 // indirect
|
||||
github.com/containernetworking/cni v0.5.2
|
||||
github.com/coreos/go-iptables v0.3.0
|
||||
github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible // indirect
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20180328155642-38588656727d+incompatible
|
||||
github.com/docker/go-connections v0.3.0 // indirect
|
||||
github.com/docker/go-units v0.3.2 // indirect
|
||||
github.com/containernetworking/cni v1.1.2
|
||||
github.com/containernetworking/plugins v1.2.0
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/docker/docker v25.0.6+incompatible
|
||||
github.com/docker/libnetwork v0.8.0-dev.2.0.20180212220201-119badbd737b
|
||||
github.com/docker/machine v0.14.0
|
||||
github.com/fsouza/go-dockerclient v1.2.0
|
||||
github.com/go-ini/ini v1.33.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/google/go-cmp v0.5.1 // indirect
|
||||
github.com/fsouza/go-dockerclient v1.10.0
|
||||
github.com/google/gopacket v1.1.16
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f // indirect
|
||||
github.com/gorilla/mux v1.6.1
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect
|
||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.3 // indirect
|
||||
github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
|
||||
github.com/miekg/dns v1.0.4
|
||||
github.com/onsi/ginkgo v1.14.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v0.1.1 // indirect
|
||||
github.com/opencontainers/selinux v1.6.0 // indirect
|
||||
github.com/miekg/dns v1.1.52
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/profile v1.2.1
|
||||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/spf13/cobra v0.0.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/vishvananda/netlink v1.0.0
|
||||
github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f
|
||||
github.com/weaveworks/common v0.0.0-20180321110636-ebab3a78900a
|
||||
github.com/weaveworks/go-checkpoint v0.0.0-20170503165305-ebbb8b0518ab
|
||||
github.com/weaveworks/go-odp v0.0.0-20181017121109-6b0aa22550d9
|
||||
github.com/weaveworks/mesh v0.0.0-20191105120815-58dbcc3e8e63
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384
|
||||
gopkg.in/ini.v1 v1.57.0 // indirect
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
k8s.io/api v0.18.6
|
||||
k8s.io/apimachinery v0.18.6
|
||||
k8s.io/client-go v0.18.6
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
|
||||
k8s.io/api v0.26.2
|
||||
k8s.io/apimachinery v0.26.2
|
||||
k8s.io/client-go v0.26.2
|
||||
)
|
||||
|
||||
require github.com/spf13/cobra v1.6.1
|
||||
|
||||
require github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/containerd v1.7.11 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jmespath/go-jmespath v0.3.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.3.0 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/safchain/ethtool v0.2.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
|
||||
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.10.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
@@ -1,221 +1,318 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.4.7 h1:vOvDiY/F1avSWlCWiKJjdYKz2jVjTK3pWPHndeG4OAY=
|
||||
github.com/Microsoft/go-winio v0.4.7/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
||||
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andybalholm/go-bit v0.0.0-20150501193155-34813fe0e46a h1:yE4/B7ng2G9tr5DdT/x2bj3u6p3Rze2Ha/61eChret8=
|
||||
github.com/andybalholm/go-bit v0.0.0-20150501193155-34813fe0e46a/go.mod h1:xPmFQkJW5/HS/ZMM6soMdODwdIOO9zs3yAhksnFQJE0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/andybalholm/go-bit v1.0.1 h1:GihKSzvvcJKJtMFvPov8ITgdDvHxHt5p5whGyptxzwc=
|
||||
github.com/andybalholm/go-bit v1.0.1/go.mod h1:ezUwZUxOwUxgSjWJxb7p3Lo5dTdBBvImVcb6EsHgg8I=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aws/aws-sdk-go v1.13.22 h1:S18o71mCjRUw9bZzw61k2xjZx3/XtzwJKdAXx5CUf6s=
|
||||
github.com/aws/aws-sdk-go v1.13.22/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/aws/aws-sdk-go v1.34.0 h1:brux2dRrlwCF5JhTL7MUT3WUwo9zfDHZZp3+g3Mvlmo=
|
||||
github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/containerd/continuity v0.0.0-20180322171221-3e8f2ea4b190 h1:xySrq2Xw0gX07PHM/LS8kuGpRd3hKDz+0/QvIJRILYk=
|
||||
github.com/containerd/continuity v0.0.0-20180322171221-3e8f2ea4b190/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containernetworking/cni v0.5.2 h1:/nFPNGJQu4yiNvXxH31qL05FIyGG5Y/p0YxuUutl+PE=
|
||||
github.com/containernetworking/cni v0.5.2/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/coreos/go-iptables v0.3.0 h1:UTQkjHl9rPwwtXZhXbY3T932cV9aUnKlSsZ7YGfJVXM=
|
||||
github.com/coreos/go-iptables v0.3.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw=
|
||||
github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ=
|
||||
github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw=
|
||||
github.com/containernetworking/plugins v1.2.0 h1:SWgg3dQG1yzUo4d9iD8cwSVh1VqI+bP7mkPDoSfP9VU=
|
||||
github.com/containernetworking/plugins v1.2.0/go.mod h1:/VjX4uHecW5vVimFa1wkG4s+r/s9qIfPdqlLF4TW8c4=
|
||||
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible h1:W1rhCpxfWMU0CaZSFWpmWfmB68zYZVksig+VC1ZbgI4=
|
||||
github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20180328155642-38588656727d+incompatible h1:C81UCfrC3AgecXLr1vYezUVtYqTPlizDRO67F/lRU2U=
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20180328155642-38588656727d+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o=
|
||||
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.3.2 h1:Kjm80apys7gTtfVmCvVY8gwu10uofaFSrmAKOVrtueE=
|
||||
github.com/docker/go-units v0.3.2/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg=
|
||||
github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libnetwork v0.8.0-dev.2.0.20180212220201-119badbd737b h1:u7WZJnaLG9o39ZmpCJEwuFM6nCBuUQL0y/AbyE5sbaM=
|
||||
github.com/docker/libnetwork v0.8.0-dev.2.0.20180212220201-119badbd737b/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
|
||||
github.com/docker/machine v0.14.0 h1:7zMwMAz0XbL27Q4NPqtW7I0wuhOaGFpq5WfvrUfZROM=
|
||||
github.com/docker/machine v0.14.0/go.mod h1:I8mPNDeK1uH+JTcUU7X0ZW8KiYz0jyAgNaeSJ1rCfDI=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsouza/go-dockerclient v1.2.0 h1:lmjnUbZFR+De5PfuXCk62hAIlkokNtD9CaGPR8Iu5UU=
|
||||
github.com/fsouza/go-dockerclient v1.2.0/go.mod h1:KpcjM623fQYE9MZiTGzKhjfxXAV9wbyX2C1cyRHfhl0=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-ini/ini v1.33.0 h1:/0Y2X+/6jgfPYl2LOihvxikDfznXMufz0Zkr3mW+7Zg=
|
||||
github.com/go-ini/ini v1.33.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsouza/go-dockerclient v1.10.0 h1:ppSBsbR60I1DFbV4Ag7LlHlHakHFRNLk9XakATW1yVQ=
|
||||
github.com/fsouza/go-dockerclient v1.10.0/go.mod h1:+iNzAW78AzClIBTZ6WFjkaMvOgz68GyCJ236b1opLTs=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.16 h1:u6Afvia5C5srlLcbTwpHaFW918asLYPxieziOaWwz8M=
|
||||
github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
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/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/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/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
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/context v0.0.0-20160226214623-1ea25387ff6f h1:9oNbS1z4rVpbnkHBdPZU4jo9bSmrLpII768arSyMFgk=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.1 h1:KOwqsTYZdeuMacU7CxjMNYEKeBvLbxW+psodrbcEa3A=
|
||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 h1:URgjUo+bs1KwatoNbwG0uCO4dHN4r1jsp4a5AGgHRjo=
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.5/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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73aD95/FU0XpVKXqNuTnEfXsLOYQ=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
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/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
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/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9 h1:tOtO8DXiNGj9NshRKHWiZuGlSldPFzFCFYhNtsKTBCs=
|
||||
github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.0.4 h1:Ec3LTJwwzqT1++63P12fhtdEbQhtPE7TBdD6rlhqrMM=
|
||||
github.com/miekg/dns v1.0.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
|
||||
github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
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/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
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/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
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/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY=
|
||||
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
|
||||
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 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE=
|
||||
github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -226,42 +323,59 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safchain/ethtool v0.2.0 h1:dILxMBqDnQfX192cCAPjZr9v2IgVXeElHPy435Z/IdE=
|
||||
github.com/safchain/ethtool v0.2.0/go.mod h1:WkKB1DnNtvsMlDmQ50sgwowDJV/hGbJSOvJoEXs1AJQ=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
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 v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cobra v0.0.1 h1:zZh3X5aZbdnoj+4XkaBxKfhO4ot82icYdhhREIAXIj8=
|
||||
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM=
|
||||
github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936 h1:J9gO8RJCAFlln1jsvRba/CWVUnMHwObklfxxjErl1uk=
|
||||
github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/weaveworks/common v0.0.0-20180321110636-ebab3a78900a h1:dIOYWkmKCq9tU3DY96p1D/8xXFWkqt9Jd1ipu6sTLSk=
|
||||
github.com/weaveworks/common v0.0.0-20180321110636-ebab3a78900a/go.mod h1:pSm+0KR57BG3pvGoJWFXJSAC7+sEPewcvdt5StevL3A=
|
||||
github.com/weaveworks/go-checkpoint v0.0.0-20170503165305-ebbb8b0518ab h1:mW+hgchD9qUUBqnuaDBj7BkcpFPk/FxeFcUFI5lvvUw=
|
||||
@@ -270,21 +384,72 @@ github.com/weaveworks/go-odp v0.0.0-20181017121109-6b0aa22550d9 h1:7OqrOqOJkneFG
|
||||
github.com/weaveworks/go-odp v0.0.0-20181017121109-6b0aa22550d9/go.mod h1:6/WClQKiVm/SQf6WJXv49l+9K9ONE2U8DYpeXEk3X7k=
|
||||
github.com/weaveworks/mesh v0.0.0-20191105120815-58dbcc3e8e63 h1:s0fUBZ8Vhtc3ruFmLIr3qVTQUb/j6ySkPLHoKKitHeM=
|
||||
github.com/weaveworks/mesh v0.0.0-20191105120815-58dbcc3e8e63/go.mod h1:RZebXKv56dax5zXcLIJZm1Awk28sx0XODXF94Z8WssY=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q=
|
||||
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
|
||||
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
|
||||
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
||||
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
|
||||
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/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/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -292,120 +457,316 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-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-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
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-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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-20181116152217-5ac8a444bdc5/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-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
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=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
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/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
||||
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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/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=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE=
|
||||
k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
|
||||
k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag=
|
||||
k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw=
|
||||
k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
|
||||
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ=
|
||||
k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU=
|
||||
k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ=
|
||||
k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
|
||||
k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI=
|
||||
k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU=
|
||||
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
|
||||
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk=
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
+2
-2
@@ -3,8 +3,8 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/api"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
type allocateResult struct {
|
||||
|
||||
+9
-8
@@ -9,13 +9,13 @@ import (
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/db"
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/ipam/ring"
|
||||
"github.com/weaveworks/weave/ipam/space"
|
||||
"github.com/weaveworks/weave/ipam/tracker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/db"
|
||||
"github.com/rajch/weave/ipam/paxos"
|
||||
"github.com/rajch/weave/ipam/ring"
|
||||
"github.com/rajch/weave/ipam/space"
|
||||
"github.com/rajch/weave/ipam/tracker"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
// Kinds of message we can unicast to other peers
|
||||
@@ -541,7 +541,8 @@ func (alloc *Allocator) annotatePeernames(names []mesh.PeerName) []string {
|
||||
// in the ring. Async.
|
||||
//
|
||||
// NB: the function is invoked by the gossip library routines and should be
|
||||
// registered manually.
|
||||
//
|
||||
// registered manually.
|
||||
func (alloc *Allocator) PeerGone(peerName mesh.PeerName) {
|
||||
alloc.debugf("PeerGone: peer %s", peerName)
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
"github.com/rajch/weave/api"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
"github.com/rajch/weave/testing/gossip"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+3
-3
@@ -5,9 +5,9 @@ import (
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/api"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
type claim struct {
|
||||
|
||||
+3
-3
@@ -8,9 +8,9 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/common/docker"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
func badRequest(w http.ResponseWriter, err error) {
|
||||
|
||||
+2
-2
@@ -12,9 +12,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
func HTTPPost(t *testing.T, url string) string {
|
||||
|
||||
+5
-4
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
// Entry represents entries around the ring
|
||||
@@ -74,8 +74,9 @@ func (es entries) get(token address.Address) (*entry, bool) {
|
||||
// Is token between entries at i and j?
|
||||
// NB i and j can overflow and will wrap
|
||||
// NBB this function does not work very well if there is only one
|
||||
// token on the ring; luckily an accurate answer is not needed
|
||||
// by the call sites in this case.
|
||||
//
|
||||
// token on the ring; luckily an accurate answer is not needed
|
||||
// by the call sites in this case.
|
||||
func (es entries) between(token address.Address, i, j int) bool {
|
||||
common.Assert(i < j)
|
||||
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@ import (
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
type OnUpdate func(prev, curr []address.Range, local bool)
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
type Space struct {
|
||||
|
||||
@@ -3,9 +3,9 @@ package space
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rajch/weave/net/address"
|
||||
wt "github.com/rajch/weave/testing"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func makeSpace(start address.Address, size address.Offset) *Space {
|
||||
|
||||
+3
-2
@@ -2,8 +2,9 @@ package ipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
|
||||
"github.com/rajch/weave/ipam/paxos"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/rajch/weave/net/address"
|
||||
"github.com/rajch/weave/testing/gossip"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
)
|
||||
|
||||
type mockMessage struct {
|
||||
|
||||
@@ -29,8 +29,8 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
type AWSVPCTracker struct {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
// LocalRangeTracker is an interface for tracking changes in the IPAM ring.
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Local is what this node publishes about itself, read from its interfaces.
|
||||
type Local struct {
|
||||
Addresses []net.IP
|
||||
WeaveIP net.IP
|
||||
WeaveMAC net.HardwareAddr
|
||||
PodNet *net.IPNet // the weave allocation range: the bridge address's network
|
||||
}
|
||||
|
||||
// overlay- and container-side interfaces: their addresses are never held ones
|
||||
var overlayPrefixes = []string{"weave", "datapath", "vxlan-", "veth", "docker", "cni"}
|
||||
|
||||
func overlay(name string) bool {
|
||||
for _, p := range overlayPrefixes {
|
||||
if strings.HasPrefix(name, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ReadLocal lists the global IPv4 addresses on host interfaces, and the weave bridge's address and MAC.
|
||||
func ReadLocal() (Local, error) {
|
||||
var l Local
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
for _, link := range links {
|
||||
name := link.Attrs().Name
|
||||
addrs, err := netlink.AddrList(link, unix.AF_INET)
|
||||
if err != nil {
|
||||
return l, fmt.Errorf("%s: %v", name, err)
|
||||
}
|
||||
if name == bridge {
|
||||
for _, a := range addrs {
|
||||
if a.IPNet != nil && a.IP.To4() != nil {
|
||||
l.WeaveIP = a.IP.To4()
|
||||
l.WeaveMAC = link.Attrs().HardwareAddr
|
||||
l.PodNet = &net.IPNet{IP: a.IP.Mask(a.Mask), Mask: a.Mask}
|
||||
break
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if overlay(name) || link.Attrs().Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if a.Scope == unix.RT_SCOPE_UNIVERSE && a.IP.To4() != nil && a.IP.IsGlobalUnicast() {
|
||||
l.Addresses = append(l.Addresses, a.IP.To4())
|
||||
}
|
||||
}
|
||||
}
|
||||
// an address inside the pod network is never a host one (whatever interface it is on)
|
||||
if l.PodNet != nil {
|
||||
kept := l.Addresses[:0]
|
||||
for _, ip := range l.Addresses {
|
||||
if !l.PodNet.Contains(ip) {
|
||||
kept = append(kept, ip)
|
||||
}
|
||||
}
|
||||
l.Addresses = kept
|
||||
}
|
||||
sort.Slice(l.Addresses, func(i, j int) bool { return l.Addresses[i].String() < l.Addresses[j].String() })
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Annotations is what this node's Node object should carry for l.
|
||||
func (l Local) Annotations() map[string]string {
|
||||
a := map[string]string{AnnAddresses: FormatAddresses(l.Addresses)}
|
||||
if l.WeaveIP != nil && l.WeaveMAC != nil {
|
||||
a[AnnWeave] = l.WeaveIP.String() + "," + l.WeaveMAC.String()
|
||||
}
|
||||
return a
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Package admit is the protocol between the weave CNI plugin and weave-npc's egress agent: at CNI ADD the
|
||||
// plugin tells the agent which pod got which IP, and the agent answers once the pod's egress rules are in
|
||||
// place, so an EgressService member's first packet already leaves from the Service's address (or not at all).
|
||||
//
|
||||
// It depends on the standard library only: the plugin binary must not grow a Kubernetes client.
|
||||
package admit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SocketPath is where the agent listens (a hostPath directory shared with the host, where the plugin runs).
|
||||
const SocketPath = "/run/weave-npc/egress.sock"
|
||||
|
||||
// Request is one pod's admission: the IP the plugin just allocated to it.
|
||||
type Request struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
// Response says whether the pod is an EgressService member (its rules are installed either way when 200).
|
||||
type Response struct {
|
||||
Member bool `json:"member"`
|
||||
}
|
||||
|
||||
// Timeout bounds one admission (the agent may have to fetch the pod from the API server).
|
||||
const Timeout = 20 * time.Second
|
||||
|
||||
// PodFromArgs reads the pod's namespace and name from CNI_ARGS ("K8S_POD_NAMESPACE=..;K8S_POD_NAME=..").
|
||||
func PodFromArgs(args string) (namespace, name string) {
|
||||
for _, kv := range strings.Split(args, ";") {
|
||||
k, v, ok := strings.Cut(kv, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "K8S_POD_NAMESPACE":
|
||||
namespace = v
|
||||
case "K8S_POD_NAME":
|
||||
name = v
|
||||
}
|
||||
}
|
||||
return namespace, name
|
||||
}
|
||||
|
||||
// Admit asks the agent at socket to admit req, and returns only once the agent has installed the pod's rules.
|
||||
func Admit(ctx context.Context, socket string, req Request) (Response, error) {
|
||||
var resp Response
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
client := &http.Client{Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
return d.DialContext(ctx, "unix", socket)
|
||||
},
|
||||
}}
|
||||
ctx, cancel := context.WithTimeout(ctx, Timeout)
|
||||
defer cancel()
|
||||
hreq, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://weave-npc/admit", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
hreq.Header.Set("Content-Type", "application/json")
|
||||
hresp, err := client.Do(hreq)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("egress admission (weave-npc at %s): %v", socket, err)
|
||||
}
|
||||
defer hresp.Body.Close()
|
||||
out, _ := io.ReadAll(io.LimitReader(hresp.Body, 1<<16))
|
||||
if hresp.StatusCode != http.StatusOK {
|
||||
return resp, fmt.Errorf("egress admission refused (%s): %s", hresp.Status, strings.TrimSpace(string(out)))
|
||||
}
|
||||
if err := json.Unmarshal(out, &resp); err != nil {
|
||||
return resp, fmt.Errorf("egress admission: bad answer: %v", err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
discovery "k8s.io/api/discovery/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/lb/admit"
|
||||
)
|
||||
|
||||
// Controller watches Services, EndpointSlices, Nodes, EgressServices and this node's Pods, and keeps this
|
||||
// node's part of the picture true. It also answers the CNI plugin's egress admissions.
|
||||
type Controller struct {
|
||||
self string
|
||||
client kubernetes.Interface
|
||||
egress rest.Interface
|
||||
dp *Datapath
|
||||
// readLocal is the node's own interfaces (swappable in tests)
|
||||
readLocal func() (Local, error)
|
||||
|
||||
services, slices, nodes, pods, egresses cache.Store
|
||||
synced []cache.InformerSynced
|
||||
egressSynced cache.InformerSynced // nil while the EgressService API is absent
|
||||
kick chan struct{}
|
||||
ready chan struct{} // closed once the first pass ran
|
||||
|
||||
mu sync.Mutex // one pass at a time (the loop, or an admission)
|
||||
lastProblems map[string]bool
|
||||
// Services whose status this node wrote since it started (so a restart rewrites ipMode once)
|
||||
written map[types.UID]string
|
||||
// admitted: IP -> pod the CNI plugin gave it; fetched: pods admitted before the pod informer had them
|
||||
admitted map[string]Admission
|
||||
fetched map[string]*coreapi.Pod
|
||||
at map[string]time.Time
|
||||
// lastEgress is what was last applied, reused while the EgressService caches are not synced (never wipe
|
||||
// members' rules on a partial view)
|
||||
lastEgress EgressDesired
|
||||
}
|
||||
|
||||
// Resync is how often the node re-reads its interfaces and re-applies everything even without API events.
|
||||
const Resync = 30 * time.Second
|
||||
|
||||
// NewController runs nothing yet; Run does. egress is the EgressService API client.
|
||||
func NewController(self string, client kubernetes.Interface, egress rest.Interface, dp *Datapath) *Controller {
|
||||
return &Controller{self: self, client: client, egress: egress, dp: dp, readLocal: ReadLocal,
|
||||
kick: make(chan struct{}, 1), ready: make(chan struct{}), lastProblems: map[string]bool{},
|
||||
written: map[types.UID]string{}, admitted: map[string]Admission{}, fetched: map[string]*coreapi.Pod{},
|
||||
at: map[string]time.Time{}}
|
||||
}
|
||||
|
||||
func (c *Controller) poke() {
|
||||
select {
|
||||
case c.kick <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func newInformer(c *Controller, lw cache.ListerWatcher, obj runtime.Object) (cache.Store, cache.Controller) {
|
||||
return cache.NewInformer(lw, obj, 0, cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(interface{}) { c.poke() },
|
||||
UpdateFunc: func(interface{}, interface{}) { c.poke() },
|
||||
DeleteFunc: func(interface{}) { c.poke() },
|
||||
})
|
||||
}
|
||||
|
||||
// egressAPI reports whether the EgressService API is served (the CRD is installed).
|
||||
func (c *Controller) egressAPI() (bool, error) {
|
||||
_, err := c.client.Discovery().ServerResourcesForGroupVersion(GroupVersion.String())
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if apierrors.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (c *Controller) startEgress(ctx context.Context) {
|
||||
var ctl cache.Controller
|
||||
c.egresses, ctl = newInformer(c, cache.NewListWatchFromClient(c.egress, EgressServices, "", fields.Everything()), &EgressService{})
|
||||
go ctl.Run(ctx.Done())
|
||||
c.egressSynced = ctl.HasSynced
|
||||
}
|
||||
|
||||
// Run blocks until ctx ends.
|
||||
func (c *Controller) Run(ctx context.Context) {
|
||||
var sc, ec, nc, pc cache.Controller
|
||||
c.services, sc = newInformer(c, cache.NewListWatchFromClient(c.client.CoreV1().RESTClient(), "services", "", fields.Everything()), &coreapi.Service{})
|
||||
c.slices, ec = newInformer(c, cache.NewListWatchFromClient(c.client.DiscoveryV1().RESTClient(), "endpointslices", "", fields.Everything()), &discovery.EndpointSlice{})
|
||||
c.nodes, nc = newInformer(c, cache.NewListWatchFromClient(c.client.CoreV1().RESTClient(), "nodes", "", fields.Everything()), &coreapi.Node{})
|
||||
c.pods, pc = newInformer(c, cache.NewListWatchFromClient(c.client.CoreV1().RESTClient(), "pods", "",
|
||||
fields.OneTermEqualSelector("spec.nodeName", c.self)), &coreapi.Pod{})
|
||||
for _, ctl := range []cache.Controller{sc, ec, nc, pc} {
|
||||
go ctl.Run(ctx.Done())
|
||||
c.synced = append(c.synced, ctl.HasSynced)
|
||||
}
|
||||
// with the API present, nothing is applied before EgressServices are known: an empty view would wipe
|
||||
// members' rules (their traffic would leave from the node); without it, there can be no members
|
||||
for {
|
||||
present, err := c.egressAPI()
|
||||
if err == nil {
|
||||
if present {
|
||||
c.startEgress(ctx)
|
||||
c.synced = append(c.synced, c.egressSynced)
|
||||
}
|
||||
break
|
||||
}
|
||||
common.Log.Warnf("lb: cannot tell whether the EgressService API is served (retrying): %v", err)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
}
|
||||
go c.serveAdmission(ctx)
|
||||
if !cache.WaitForCacheSync(ctx.Done(), c.synced...) {
|
||||
return
|
||||
}
|
||||
tick := time.NewTicker(Resync)
|
||||
defer tick.Stop()
|
||||
c.poke()
|
||||
first := true
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-tick.C:
|
||||
if c.egressSynced == nil {
|
||||
// the API may have been installed since
|
||||
if present, err := c.egressAPI(); err == nil && present {
|
||||
c.mu.Lock()
|
||||
c.startEgress(ctx)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}
|
||||
case <-c.kick:
|
||||
// let a burst of events settle into one pass
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
select {
|
||||
case <-c.kick:
|
||||
default:
|
||||
}
|
||||
}
|
||||
c.mu.Lock()
|
||||
err := c.sync(ctx)
|
||||
c.mu.Unlock()
|
||||
if err != nil {
|
||||
common.Log.Errorf("lb: %v", err)
|
||||
}
|
||||
if first {
|
||||
first = false
|
||||
close(c.ready)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// egressViewSynced: the EgressService view can be trusted (absent API: trivially empty; else synced).
|
||||
func (c *Controller) egressViewSynced() bool {
|
||||
return c.egressSynced == nil || c.egressSynced()
|
||||
}
|
||||
|
||||
// sync is one pass: publish this node, compute, apply, write statuses. Callers hold c.mu.
|
||||
func (c *Controller) sync(ctx context.Context) error {
|
||||
local, err := c.readLocal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading interfaces: %v", err)
|
||||
}
|
||||
if err := c.publish(ctx, local); err != nil {
|
||||
return err
|
||||
}
|
||||
if local.PodNet == nil {
|
||||
return fmt.Errorf("weave bridge has no address yet; not forwarding")
|
||||
}
|
||||
|
||||
var nodes []Node
|
||||
var problems []string
|
||||
for _, o := range c.nodes.List() {
|
||||
n, err := ParseNode(o.(*coreapi.Node))
|
||||
if err != nil {
|
||||
problems = append(problems, err.Error())
|
||||
}
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
var svcs []*coreapi.Service
|
||||
for _, o := range c.services.List() {
|
||||
svcs = append(svcs, o.(*coreapi.Service))
|
||||
}
|
||||
var slices []*discovery.EndpointSlice
|
||||
for _, o := range c.slices.List() {
|
||||
slices = append(slices, o.(*discovery.EndpointSlice))
|
||||
}
|
||||
d := Compute(c.self, nodes, svcs, slices)
|
||||
// rewrite once after start what an earlier run may have written without ipMode
|
||||
for _, f := range d.Forwards {
|
||||
if s := c.service(f.Namespace, f.Name); s != nil && c.written[s.UID] != f.Address.String() && !hasStatusFor(d, f) {
|
||||
d.Status = append(d.Status, Status{Namespace: f.Namespace, Name: f.Name, Address: f.Address})
|
||||
}
|
||||
}
|
||||
|
||||
e := c.lastEgress
|
||||
e.Status = nil // reusing the datapath, never the writes of an earlier view
|
||||
if c.egressViewSynced() {
|
||||
e = c.computeEgress(nodes, svcs)
|
||||
} else {
|
||||
problems = append(problems, "EgressServices not synced yet: keeping the egress rules last applied")
|
||||
}
|
||||
|
||||
current := map[string]bool{}
|
||||
for _, p := range append(append(problems, d.Problems...), e.Problems...) {
|
||||
current[p] = true
|
||||
if !c.lastProblems[p] {
|
||||
common.Log.Warnf("lb: %s", p)
|
||||
}
|
||||
}
|
||||
c.lastProblems = current
|
||||
|
||||
if err := c.dp.Apply(d, e, c.self, local); err != nil {
|
||||
return fmt.Errorf("applying datapath: %v", err)
|
||||
}
|
||||
c.lastEgress = e
|
||||
for _, st := range d.Status {
|
||||
if err := c.writeStatus(ctx, st); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, st := range e.Status {
|
||||
if err := PatchEgressStatus(ctx, c.egress, st.Namespace, st.Name, st.Status); err != nil {
|
||||
return fmt.Errorf("status of egress %s/%s: %v", st.Namespace, st.Name, err)
|
||||
}
|
||||
common.Log.Infof("lb: egress %s/%s: host %q, address %q, %d port blocks", st.Namespace, st.Name, st.Status.Host, st.Status.Address, len(st.Status.Blocks))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// computeEgress gathers this node's pods (informer, plus pods admitted before the informer had them) and computes.
|
||||
func (c *Controller) computeEgress(nodes []Node, svcs []*coreapi.Service) EgressDesired {
|
||||
var egresses []*EgressService
|
||||
if c.egresses != nil {
|
||||
for _, o := range c.egresses.List() {
|
||||
egresses = append(egresses, o.(*EgressService))
|
||||
}
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
var pods []*coreapi.Pod
|
||||
for _, o := range c.pods.List() {
|
||||
p := o.(*coreapi.Pod)
|
||||
seen[p.Namespace+"/"+p.Name] = true
|
||||
pods = append(pods, p)
|
||||
}
|
||||
// forget admissions the API has caught up with or that are long gone
|
||||
for ip, a := range c.admitted {
|
||||
key := a.Namespace + "/" + a.Name
|
||||
o, ok, _ := c.pods.GetByKey(key)
|
||||
caughtUp := ok && o.(*coreapi.Pod).Status.PodIP == ip
|
||||
if caughtUp || (!ok && time.Since(c.at[ip]) > 2*time.Minute) {
|
||||
delete(c.admitted, ip)
|
||||
delete(c.at, ip)
|
||||
}
|
||||
}
|
||||
for key, p := range c.fetched {
|
||||
stillAdmitted := false
|
||||
for _, a := range c.admitted {
|
||||
if a.Namespace+"/"+a.Name == key {
|
||||
stillAdmitted = true
|
||||
}
|
||||
}
|
||||
if !stillAdmitted {
|
||||
delete(c.fetched, key)
|
||||
continue
|
||||
}
|
||||
if !seen[key] {
|
||||
pods = append(pods, p)
|
||||
}
|
||||
}
|
||||
return ComputeEgress(c.self, nodes, svcs, egresses, pods, c.admitted)
|
||||
}
|
||||
|
||||
func hasStatusFor(d Desired, f Forward) bool {
|
||||
for _, s := range d.Status {
|
||||
if s.Namespace == f.Namespace && s.Name == f.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Controller) service(ns, name string) *coreapi.Service {
|
||||
o, ok, err := c.services.GetByKey(ns + "/" + name)
|
||||
if err != nil || !ok {
|
||||
return nil
|
||||
}
|
||||
return o.(*coreapi.Service)
|
||||
}
|
||||
|
||||
// serveAdmission answers the CNI plugin on admit.SocketPath: record the pod's IP, run a pass, answer once the
|
||||
// pod's rules are in place. A pod is refused (the plugin fails the ADD, kubelet retries) while this node cannot
|
||||
// yet tell whether it is a member.
|
||||
func (c *Controller) serveAdmission(ctx context.Context) {
|
||||
if err := os.MkdirAll(filepath.Dir(admit.SocketPath), 0o700); err != nil {
|
||||
common.Log.Errorf("lb: admission socket: %v", err)
|
||||
return
|
||||
}
|
||||
_ = os.Remove(admit.SocketPath)
|
||||
l, err := net.Listen("unix", admit.SocketPath)
|
||||
if err != nil {
|
||||
common.Log.Errorf("lb: admission socket: %v", err)
|
||||
return
|
||||
}
|
||||
_ = os.Chmod(admit.SocketPath, 0o600)
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/admit", func(w http.ResponseWriter, r *http.Request) {
|
||||
var req admit.Request
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<16)).Decode(&req); err != nil {
|
||||
http.Error(w, "bad request: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
member, code, err := c.admit(r.Context(), req)
|
||||
if err != nil {
|
||||
common.Log.Warnf("lb: admission of %s/%s (%s): %v", req.Namespace, req.Name, req.IP, err)
|
||||
http.Error(w, err.Error(), code)
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(admit.Response{Member: member})
|
||||
})
|
||||
srv := &http.Server{Handler: mux, ReadHeaderTimeout: 5 * time.Second}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
_ = srv.Close()
|
||||
}()
|
||||
if err := srv.Serve(l); err != nil && err != http.ErrServerClosed {
|
||||
common.Log.Errorf("lb: admission server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) admit(ctx context.Context, req admit.Request) (bool, int, error) {
|
||||
ip := net.ParseIP(req.IP).To4()
|
||||
if req.Namespace == "" || req.Name == "" || ip == nil {
|
||||
return false, http.StatusBadRequest, fmt.Errorf("need namespace, name and an IPv4 address")
|
||||
}
|
||||
select {
|
||||
case <-c.ready:
|
||||
case <-ctx.Done():
|
||||
return false, http.StatusServiceUnavailable, ctx.Err()
|
||||
case <-time.After(admit.Timeout - 5*time.Second):
|
||||
return false, http.StatusServiceUnavailable, fmt.Errorf("not ready yet")
|
||||
}
|
||||
key := req.Namespace + "/" + req.Name
|
||||
var pod *coreapi.Pod
|
||||
if o, ok, _ := c.pods.GetByKey(key); ok {
|
||||
pod = o.(*coreapi.Pod)
|
||||
} else {
|
||||
gctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
p, err := c.client.CoreV1().Pods(req.Namespace).Get(gctx, req.Name, metav1.GetOptions{})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return false, http.StatusServiceUnavailable, fmt.Errorf("fetching pod: %v", err)
|
||||
}
|
||||
pod = p
|
||||
}
|
||||
if pod.Spec.NodeName != c.self {
|
||||
return false, http.StatusConflict, fmt.Errorf("pod is bound to %q, not this node", pod.Spec.NodeName)
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if !c.egressViewSynced() {
|
||||
return false, http.StatusServiceUnavailable, fmt.Errorf("EgressServices not synced yet")
|
||||
}
|
||||
c.admitted[ip.String()] = Admission{Namespace: req.Namespace, Name: req.Name}
|
||||
c.at[ip.String()] = time.Now()
|
||||
if _, ok, _ := c.pods.GetByKey(key); !ok {
|
||||
c.fetched[key] = pod
|
||||
}
|
||||
if err := c.sync(ctx); err != nil {
|
||||
// with no EgressService and no member rules in place there is nothing to protect or to clean up for
|
||||
// this IP: a broken pass must not stop every pod on the node from starting
|
||||
if len(c.lastEgress.Members) == 0 && (c.egresses == nil || len(c.egresses.List()) == 0) {
|
||||
common.Log.Warnf("lb: admitting %s/%s despite a failed pass (no EgressServices): %v", req.Namespace, req.Name, err)
|
||||
return false, http.StatusOK, nil
|
||||
}
|
||||
return false, http.StatusServiceUnavailable, err
|
||||
}
|
||||
for _, m := range c.lastEgress.Members {
|
||||
if m.IP.Equal(ip) {
|
||||
return true, http.StatusOK, nil
|
||||
}
|
||||
}
|
||||
return false, http.StatusOK, nil
|
||||
}
|
||||
|
||||
// publish keeps this node's annotations equal to what its interfaces say.
|
||||
func (c *Controller) publish(ctx context.Context, local Local) error {
|
||||
want := local.Annotations()
|
||||
o, ok, err := c.nodes.GetByKey(c.self)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
have := o.(*coreapi.Node).Annotations
|
||||
same := true
|
||||
for k, v := range want {
|
||||
if have[k] != v {
|
||||
same = false
|
||||
}
|
||||
}
|
||||
if same {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
patch, err := json.Marshal(map[string]interface{}{"metadata": map[string]interface{}{"annotations": want}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.client.CoreV1().Nodes().Patch(ctx, c.self, types.MergePatchType, patch, metav1.PatchOptions{}); err != nil {
|
||||
return fmt.Errorf("publishing node annotations: %v", err)
|
||||
}
|
||||
common.Log.Infof("lb: node %s holds [%s]", c.self, want[AnnAddresses])
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeStatus sets (ipMode Proxy: kube-proxy leaves the address to us) or clears a Service's load-balancer status.
|
||||
func (c *Controller) writeStatus(ctx context.Context, st Status) error {
|
||||
ingress := []map[string]string{}
|
||||
if st.Address != nil {
|
||||
ingress = append(ingress, map[string]string{"ip": st.Address.String(), "ipMode": "Proxy"})
|
||||
}
|
||||
patch, err := json.Marshal(map[string]interface{}{"status": map[string]interface{}{"loadBalancer": map[string]interface{}{"ingress": ingress}}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := c.client.CoreV1().Services(st.Namespace).Patch(ctx, st.Name, types.MergePatchType, patch, metav1.PatchOptions{}, "status")
|
||||
if err != nil {
|
||||
return fmt.Errorf("status of %s/%s: %v", st.Namespace, st.Name, err)
|
||||
}
|
||||
if st.Address != nil {
|
||||
c.written[s.UID] = st.Address.String()
|
||||
common.Log.Infof("lb: %s/%s at %s", st.Namespace, st.Name, st.Address)
|
||||
} else {
|
||||
delete(c.written, s.UID)
|
||||
common.Log.Infof("lb: %s/%s has no address", st.Namespace, st.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// ForwardChain (nat, from PREROUTING) DNATs held addresses' ports to endpoints.
|
||||
ForwardChain = "REWEAVE-LB"
|
||||
// PostChain (nat, from POSTROUTING, ahead of weave's WEAVE) keeps the client's address on what ForwardChain DNATed.
|
||||
PostChain = "REWEAVE-LB-POST"
|
||||
// MarkChain (mangle, from PREROUTING) marks holders' connections and steers their replies.
|
||||
MarkChain = "REWEAVE-LB-MARK"
|
||||
// servicePrefix names the per-port chains ForwardChain jumps to.
|
||||
servicePrefix = "REWEAVE-LBS-"
|
||||
|
||||
markShift = 24
|
||||
markMask = uint32(0xff) << markShift
|
||||
// RulePriority is where the reply-steering rules sit: before the main table (32766).
|
||||
RulePriority = 9000
|
||||
// TableBase + mark is the routing table that sends replies to that mark's holder.
|
||||
TableBase = 52000
|
||||
|
||||
bridge = "weave"
|
||||
)
|
||||
|
||||
func markValue(m uint32) uint32 { return m << markShift }
|
||||
|
||||
// portChain is the per-(service, protocol, port) chain name: stable, short enough for iptables (28 chars).
|
||||
func portChain(f Forward, p Port) string {
|
||||
h := sha256.Sum256([]byte(fmt.Sprintf("%s/%s/%s/%d", f.Namespace, f.Name, p.Protocol, p.Port)))
|
||||
return servicePrefix + strings.ToUpper(hex.EncodeToString(h[:])[:12])
|
||||
}
|
||||
|
||||
// Rules is the full content of the chains this package owns, in iptables-restore form.
|
||||
type Rules struct {
|
||||
Nat, Mangle, Filter string
|
||||
}
|
||||
|
||||
// Render turns the desired state (load-balancer forwards and egress) into iptables-restore input for the nat,
|
||||
// mangle and filter tables. podNet is the weave allocation range (pods and bridges).
|
||||
// Each payload declares every chain it owns, so a restore with --noflush replaces exactly those.
|
||||
func Render(d Desired, e EgressDesired, self string, podNet *net.IPNet, stale []string) Rules {
|
||||
var nat bytes.Buffer
|
||||
nat.WriteString("*nat\n")
|
||||
for _, c := range []string{ForwardChain, PostChain, ClassChain} {
|
||||
fmt.Fprintf(&nat, ":%s - [0:0]\n", c)
|
||||
}
|
||||
var body bytes.Buffer
|
||||
seen := map[string]bool{}
|
||||
for _, f := range d.Forwards {
|
||||
a := f.Address.String()
|
||||
if seen[a] {
|
||||
continue
|
||||
}
|
||||
seen[a] = true
|
||||
// weave masquerades everything entering the pod network from outside unless it is for a local pod
|
||||
// (its expose rule); a connection we DNATed must keep the client's address, so it leaves the nat
|
||||
// table here. One from a pod (hairpin) is masqueraded to this node, so its reply comes back here.
|
||||
fmt.Fprintf(&body, "-A %s -m conntrack --ctstate DNAT --ctorigdst %s/32 -s %s -j MASQUERADE\n", PostChain, a, podNet)
|
||||
fmt.Fprintf(&body, "-A %s -m conntrack --ctstate DNAT --ctorigdst %s/32 -j ACCEPT\n", PostChain, a)
|
||||
}
|
||||
for _, f := range d.Forwards {
|
||||
for _, p := range f.Ports {
|
||||
c := portChain(f, p)
|
||||
fmt.Fprintf(&nat, ":%s - [0:0]\n", c)
|
||||
proto := strings.ToLower(string(p.Protocol))
|
||||
fmt.Fprintf(&body, "-A %s -d %s/32 -p %s -m %s --dport %d -m comment --comment %q -j %s\n",
|
||||
ForwardChain, f.Address, proto, proto, p.Port, f.Namespace+"/"+f.Name, c)
|
||||
n := len(p.Endpoints)
|
||||
for i, e := range p.Endpoints {
|
||||
// DNAT to an ip:port needs the protocol in the same rule
|
||||
if i < n-1 {
|
||||
// the i-th endpoint takes 1/(n-i) of what the earlier ones left: uniform overall
|
||||
fmt.Fprintf(&body, "-A %s -p %s -m statistic --mode random --probability %.10f -j DNAT --to-destination %s\n",
|
||||
c, proto, 1/float64(n-i), e)
|
||||
} else {
|
||||
fmt.Fprintf(&body, "-A %s -p %s -j DNAT --to-destination %s\n", c, proto, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, c := range stale {
|
||||
// chains of ports no longer forwarded: emptied here, deleted after the restore
|
||||
fmt.Fprintf(&nat, ":%s - [0:0]\n", c)
|
||||
}
|
||||
|
||||
var mangle bytes.Buffer
|
||||
mangle.WriteString("*mangle\n")
|
||||
for _, c := range []string{MarkChain, MSSChain} {
|
||||
fmt.Fprintf(&mangle, ":%s - [0:0]\n", c)
|
||||
}
|
||||
if len(d.Holders) > 0 {
|
||||
// replies of a holder's connection carry its mark: the policy rule routes them back to it
|
||||
fmt.Fprintf(&mangle, "-A %s -m conntrack --ctdir REPLY -m connmark ! --mark 0x0/0x%x -j CONNMARK --restore-mark --nfmask 0x%x --ctmask 0x%x\n",
|
||||
MarkChain, markMask, markMask, markMask)
|
||||
for _, h := range d.Holders {
|
||||
// a new connection to a pod here, arriving from the holder's bridge, from outside the pod network:
|
||||
// the holder DNATed it and must see its replies
|
||||
fmt.Fprintf(&mangle, "-A %s -i %s -m mac --mac-source %s ! -s %s -d %s -m conntrack --ctstate NEW -m comment --comment %q -j CONNMARK --set-xmark 0x%x/0x%x\n",
|
||||
MarkChain, bridge, h.Node.WeaveMAC, podNet, podNet, "holder "+h.Node.Name, markValue(h.Mark), markMask)
|
||||
}
|
||||
}
|
||||
|
||||
var filter bytes.Buffer
|
||||
filter.WriteString("*filter\n")
|
||||
for _, c := range []string{FwdChain, InChain} {
|
||||
fmt.Fprintf(&filter, ":%s - [0:0]\n", c)
|
||||
}
|
||||
renderEgress(e, self, podNet, &body, &mangle, &filter)
|
||||
|
||||
nat.Write(body.Bytes())
|
||||
nat.WriteString("COMMIT\n")
|
||||
mangle.WriteString("COMMIT\n")
|
||||
filter.WriteString("COMMIT\n")
|
||||
return Rules{Nat: nat.String(), Mangle: mangle.String(), Filter: filter.String()}
|
||||
}
|
||||
|
||||
// Datapath applies Desired to this node.
|
||||
type Datapath struct {
|
||||
ipt *iptables.IPTables
|
||||
// restore runs iptables-restore --noflush with the given input (swappable in tests)
|
||||
restore func(input string) error
|
||||
}
|
||||
|
||||
// NewDatapath uses the host's iptables (whichever backend the image's launch script selected).
|
||||
func NewDatapath(ipt *iptables.IPTables) *Datapath {
|
||||
return &Datapath{ipt: ipt, restore: func(input string) error {
|
||||
cmd := exec.Command("iptables-restore", "--noflush", "-w", "5")
|
||||
cmd.Stdin = strings.NewReader(input)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("iptables-restore: %v: %s", err, out)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
// Apply makes the node's iptables chains, tunnel, policy rules and routes exactly what d and e say.
|
||||
func (dp *Datapath) Apply(d Desired, e EgressDesired, self string, local Local) error {
|
||||
podNet := local.PodNet
|
||||
if err := dp.hooks(); err != nil {
|
||||
return err
|
||||
}
|
||||
// the tunnels must exist before the routes into them (iptables rules may name a device that does not exist yet)
|
||||
var peers []Node
|
||||
if e.Tunnelled(self) {
|
||||
peers = e.Peers(self)
|
||||
}
|
||||
if err := ensureTunnels(peers, local); err != nil {
|
||||
return err
|
||||
}
|
||||
existing, err := dp.ipt.ListChains("nat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
want := map[string]bool{}
|
||||
for _, f := range d.Forwards {
|
||||
for _, p := range f.Ports {
|
||||
want[portChain(f, p)] = true
|
||||
}
|
||||
}
|
||||
var stale []string
|
||||
for _, c := range existing {
|
||||
if strings.HasPrefix(c, servicePrefix) && !want[c] {
|
||||
stale = append(stale, c)
|
||||
}
|
||||
}
|
||||
r := Render(d, e, self, podNet, stale)
|
||||
// filter first: the guards are in place before the marks and SNATs that they guard
|
||||
for _, payload := range []string{r.Filter, r.Nat, r.Mangle} {
|
||||
if err := dp.restore(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, c := range stale {
|
||||
if err := dp.ipt.DeleteChain("nat", c); err != nil {
|
||||
return fmt.Errorf("deleting stale chain %s: %v", c, err)
|
||||
}
|
||||
}
|
||||
if err := dp.routes(d.Holders); err != nil {
|
||||
return err
|
||||
}
|
||||
return egressNet(e, self)
|
||||
}
|
||||
|
||||
// hooks makes sure the built-in chains jump to ours: first (kube-proxy's and weave's rules must not see these
|
||||
// connections first), except ClassChain, which must come after kube-proxy's KUBE-SERVICES (whose DNAT ends the
|
||||
// nat walk, so a connection to a Service is never classed as going outside).
|
||||
func (dp *Datapath) hooks() error {
|
||||
for _, h := range []struct {
|
||||
table, from, chain string
|
||||
last bool
|
||||
}{
|
||||
{"nat", "PREROUTING", ForwardChain, false},
|
||||
{"nat", "POSTROUTING", PostChain, false},
|
||||
{"nat", "PREROUTING", ClassChain, true},
|
||||
{"mangle", "PREROUTING", MarkChain, false},
|
||||
{"mangle", "FORWARD", MSSChain, false},
|
||||
{"filter", "FORWARD", FwdChain, false},
|
||||
{"filter", "INPUT", InChain, false},
|
||||
} {
|
||||
ok, err := dp.ipt.ChainExists(h.table, h.chain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
if err := dp.ipt.NewChain(h.table, h.chain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
rule := []string{"-m", "comment", "--comment", "reweave lb", "-j", h.chain}
|
||||
rules, err := dp.ipt.List(h.table, h.from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.last {
|
||||
ours, kube := -1, -1
|
||||
for i, r := range rules {
|
||||
if strings.HasSuffix(r, "-j "+h.chain) {
|
||||
ours = i
|
||||
}
|
||||
if strings.HasSuffix(r, "-j KUBE-SERVICES") {
|
||||
kube = i
|
||||
}
|
||||
}
|
||||
if ours > kube {
|
||||
continue
|
||||
}
|
||||
if err := dp.ipt.DeleteIfExists(h.table, h.from, rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dp.ipt.Append(h.table, h.from, rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
// rules[0] is the policy line; our jump must be rules[1]
|
||||
if len(rules) > 1 && strings.HasSuffix(rules[1], "-j "+h.chain) {
|
||||
continue
|
||||
}
|
||||
if err := dp.ipt.DeleteIfExists(h.table, h.from, rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dp.ipt.Insert(h.table, h.from, 1, rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// routes keeps one policy rule and one table per holder: marked replies go to the holder's bridge.
|
||||
func (dp *Datapath) routes(holders []Holder) error {
|
||||
link, err := netlink.LinkByName(bridge)
|
||||
if err != nil {
|
||||
if len(holders) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("weave bridge: %v", err)
|
||||
}
|
||||
want := map[int]Holder{}
|
||||
for _, h := range holders {
|
||||
want[TableBase+int(h.Mark)] = h
|
||||
}
|
||||
rules, err := netlink.RuleList(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
have := map[int]bool{}
|
||||
for _, r := range rules {
|
||||
if r.Priority != RulePriority || r.Table < TableBase || r.Table > TableBase+MaxMarks {
|
||||
continue
|
||||
}
|
||||
h, ok := want[r.Table]
|
||||
if ok && uint32(r.Mark) == markValue(h.Mark) && uint32(r.Mask) == markMask {
|
||||
have[r.Table] = true
|
||||
continue
|
||||
}
|
||||
r := r
|
||||
if err := netlink.RuleDel(&r); err != nil {
|
||||
return fmt.Errorf("removing stale rule to table %d: %v", r.Table, err)
|
||||
}
|
||||
}
|
||||
existing, err := routesByTable(TableBase+1, TableBase+MaxMarks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for table := TableBase + 1; table <= TableBase+MaxMarks; table++ {
|
||||
h, ok := want[table]
|
||||
if !ok {
|
||||
for _, rt := range existing[table] {
|
||||
if err := delRoute(rt); err != nil {
|
||||
return fmt.Errorf("removing stale route in table %d: %v", table, err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := netlink.RouteReplace(&netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)},
|
||||
Gw: h.Node.WeaveIP,
|
||||
Table: table,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("route to holder %s: %v", h.Node.Name, err)
|
||||
}
|
||||
if !have[table] {
|
||||
rule := netlink.NewRule()
|
||||
rule.Priority = RulePriority
|
||||
rule.Table = table
|
||||
rule.Mark = int(markValue(h.Mark))
|
||||
rule.Mask = int(markMask)
|
||||
if err := netlink.RuleAdd(rule); err != nil {
|
||||
return fmt.Errorf("rule for holder %s: %v", h.Node.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// delRoute deletes a route as RouteList returned it: a default route lists without Dst, which RouteDel refuses.
|
||||
func delRoute(rt netlink.Route) error {
|
||||
if rt.Dst == nil {
|
||||
rt.Dst = &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)}
|
||||
}
|
||||
return netlink.RouteDel(&rt)
|
||||
}
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Port blocks: every node gets its own source ports on an EgressService's address, so the holder can route a
|
||||
// reply back to the member's node by port alone (no state on the holder). The range lies above the default
|
||||
// ephemeral range (32768-60999), so it never meets the holder's own connections from the address.
|
||||
const (
|
||||
BlockBase = 61000
|
||||
BlockSize = 512
|
||||
BlockCount = 8 // 61000-65095
|
||||
)
|
||||
|
||||
// Admission is a pod the CNI plugin announced at ADD, before the API shows its IP.
|
||||
type Admission struct {
|
||||
Namespace, Name string
|
||||
}
|
||||
|
||||
// Member is one pod on this node that an EgressService selects, and where its outside traffic goes.
|
||||
type Member struct {
|
||||
IP net.IP
|
||||
Pod string // namespace/name
|
||||
Service string // namespace/name of its EgressService (empty if it is in several)
|
||||
// Unreachable: its outside traffic is dropped (no holder, no block, several EgressServices, ...)
|
||||
Unreachable bool
|
||||
Address net.IP // it leaves from Address:[From, To]
|
||||
From, To int32
|
||||
Holder Node // the node that emits it; Holder.Name == self: emitted here
|
||||
}
|
||||
|
||||
// BlockRoute is, on the holder, the replies to Address:[From, To] going back to Peer.
|
||||
type BlockRoute struct {
|
||||
Address net.IP
|
||||
From, To int32
|
||||
Peer Node
|
||||
}
|
||||
|
||||
// EgressStatusWrite is a status this node writes (the holder, or the first node by name to clear it).
|
||||
type EgressStatusWrite struct {
|
||||
Namespace, Name string
|
||||
Status EgressServiceStatus
|
||||
}
|
||||
|
||||
// EgressDesired is this node's part of every EgressService.
|
||||
type EgressDesired struct {
|
||||
Members []Member
|
||||
Routes []BlockRoute
|
||||
Status []EgressStatusWrite
|
||||
NodeAddrs []net.IP // the cluster's node addresses: members' traffic to them is not "outside"
|
||||
Problems []string
|
||||
}
|
||||
|
||||
// Tunnelled reports whether this node sends or receives anything over the egress tunnel.
|
||||
func (e EgressDesired) Tunnelled(self string) bool {
|
||||
if len(e.Routes) > 0 {
|
||||
return true
|
||||
}
|
||||
for _, m := range e.Members {
|
||||
if !m.Unreachable && m.Holder.Name != self {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Peers are the nodes this node exchanges tunnelled packets with (sorted by name, deduplicated).
|
||||
func (e EgressDesired) Peers(self string) []Node {
|
||||
seen := map[string]Node{}
|
||||
for _, r := range e.Routes {
|
||||
seen[r.Peer.Name] = r.Peer
|
||||
}
|
||||
for _, m := range e.Members {
|
||||
if !m.Unreachable && m.Holder.Name != self {
|
||||
seen[m.Holder.Name] = m.Holder
|
||||
}
|
||||
}
|
||||
out := make([]Node, 0, len(seen))
|
||||
for _, n := range seen {
|
||||
out = append(out, n)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
return out
|
||||
}
|
||||
|
||||
// AllocateBlocks keeps every valid block a node already has and gives each node without one the lowest free
|
||||
// block, in name order. A node beyond BlockCount gets none (its members are dropped, and that is reported).
|
||||
func AllocateBlocks(current []PortBlock, nodeNames []string) []PortBlock {
|
||||
present := map[string]bool{}
|
||||
for _, n := range nodeNames {
|
||||
present[n] = true
|
||||
}
|
||||
used := map[int]bool{}
|
||||
have := map[string]PortBlock{}
|
||||
for _, b := range current {
|
||||
k := int(b.From-BlockBase) / BlockSize
|
||||
valid := present[b.Node] && b.From >= BlockBase && int(b.From-BlockBase)%BlockSize == 0 &&
|
||||
k < BlockCount && b.To == b.From+BlockSize-1
|
||||
if !valid || used[k] {
|
||||
continue
|
||||
}
|
||||
if _, dup := have[b.Node]; dup {
|
||||
continue
|
||||
}
|
||||
used[k] = true
|
||||
have[b.Node] = b
|
||||
}
|
||||
names := append([]string(nil), nodeNames...)
|
||||
sort.Strings(names)
|
||||
for _, n := range names {
|
||||
if _, ok := have[n]; ok {
|
||||
continue
|
||||
}
|
||||
for k := 0; k < BlockCount; k++ {
|
||||
if !used[k] {
|
||||
used[k] = true
|
||||
from := int32(BlockBase + k*BlockSize)
|
||||
have[n] = PortBlock{Node: n, From: from, To: from + BlockSize - 1}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
out := make([]PortBlock, 0, len(have))
|
||||
for _, b := range have {
|
||||
out = append(out, b)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Node < out[j].Node })
|
||||
return out
|
||||
}
|
||||
|
||||
func sameStatus(a, b EgressServiceStatus) bool {
|
||||
if a.Host != b.Host || a.Address != b.Address || len(a.Blocks) != len(b.Blocks) {
|
||||
return false
|
||||
}
|
||||
for i := range a.Blocks {
|
||||
if a.Blocks[i] != b.Blocks[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// holderOf is S1's rule for a Service's address: the one node that holds spec.loadBalancerIP.
|
||||
func holderOf(s *coreapi.Service, nodes []Node) (*Node, net.IP) {
|
||||
want := net.ParseIP(s.Spec.LoadBalancerIP).To4()
|
||||
if want == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var hs []Node
|
||||
for _, n := range nodes {
|
||||
if n.Holds(want) {
|
||||
hs = append(hs, n)
|
||||
}
|
||||
}
|
||||
if len(hs) != 1 {
|
||||
return nil, want
|
||||
}
|
||||
return &hs[0], want
|
||||
}
|
||||
|
||||
// selects reports whether a Service's selector (non-empty) matches the labels.
|
||||
func selects(s *coreapi.Service, labels map[string]string) bool {
|
||||
if len(s.Spec.Selector) == 0 {
|
||||
return false
|
||||
}
|
||||
for k, v := range s.Spec.Selector {
|
||||
if labels[k] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ComputeEgress decides this node's part of every EgressService: the statuses it writes, the members it hosts and
|
||||
// where their traffic goes, and (as a holder) where replies to each node's port block go.
|
||||
// pods are this node's pods; admitted maps an IP to the pod the CNI plugin gave it (newer than the API may show).
|
||||
func ComputeEgress(self string, nodes []Node, services []*coreapi.Service, egresses []*EgressService,
|
||||
pods []*coreapi.Pod, admitted map[string]Admission) EgressDesired {
|
||||
var d EgressDesired
|
||||
sorted := append([]Node(nil), nodes...)
|
||||
sort.Slice(sorted, func(i, j int) bool { return sorted[i].Name < sorted[j].Name })
|
||||
byName := map[string]Node{}
|
||||
var names []string
|
||||
for _, n := range sorted {
|
||||
byName[n.Name] = n
|
||||
names = append(names, n.Name)
|
||||
d.NodeAddrs = append(d.NodeAddrs, n.Addresses...)
|
||||
}
|
||||
svcs := map[string]*coreapi.Service{}
|
||||
for _, s := range services {
|
||||
svcs[s.Namespace+"/"+s.Name] = s
|
||||
}
|
||||
es := append([]*EgressService(nil), egresses...)
|
||||
sort.Slice(es, func(i, j int) bool { return es[i].Namespace+"/"+es[i].Name < es[j].Namespace+"/"+es[j].Name })
|
||||
|
||||
// valid[key]: the EgressService is in force: its Service is served from an address one node holds, and the
|
||||
// holder has published that (status) - what members and holder both act on
|
||||
valid := map[string]bool{}
|
||||
for _, e := range es {
|
||||
key := e.Namespace + "/" + e.Name
|
||||
var want EgressServiceStatus
|
||||
var holder *Node
|
||||
var addr net.IP
|
||||
s := svcs[key]
|
||||
switch {
|
||||
case e.Spec.Emit != EmitHolder:
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("egress %s: spec.emit must be %q (the only mode so far), got %q", key, EmitHolder, e.Spec.Emit))
|
||||
case e.Spec.SourceIPBy != "" && e.Spec.SourceIPBy != SourceIPByLoadBalancerIP:
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("egress %s: spec.sourceIPBy must be %q, got %q", key, SourceIPByLoadBalancerIP, e.Spec.SourceIPBy))
|
||||
case s == nil:
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("egress %s: no Service %s", key, key))
|
||||
case !eligible(s):
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("egress %s: Service %s is not a LoadBalancer of class %s", key, key, Class))
|
||||
default:
|
||||
holder, addr = holderOf(s, sorted)
|
||||
if holder == nil {
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("egress %s: no single node holds %s; members' outside traffic is dropped", key, s.Spec.LoadBalancerIP))
|
||||
}
|
||||
}
|
||||
writer := ""
|
||||
if holder != nil {
|
||||
want = EgressServiceStatus{Host: holder.Name, Address: addr.String(), Blocks: AllocateBlocks(e.Status.Blocks, names)}
|
||||
writer = holder.Name
|
||||
if len(want.Blocks) < len(names) {
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("egress %s: only %d port blocks for %d nodes; members on nodes without one are dropped", key, BlockCount, len(names)))
|
||||
}
|
||||
} else if len(names) > 0 {
|
||||
writer = names[0]
|
||||
}
|
||||
if writer == self && !sameStatus(e.Status, want) {
|
||||
d.Status = append(d.Status, EgressStatusWrite{Namespace: e.Namespace, Name: e.Name, Status: want})
|
||||
}
|
||||
// in force only once the status in the API says what holds now (members and holder read the same thing)
|
||||
if holder != nil && e.Status.Host == holder.Name && e.Status.Address == addr.String() {
|
||||
valid[key] = true
|
||||
if holder.Name == self {
|
||||
for _, b := range e.Status.Blocks {
|
||||
if b.Node == self {
|
||||
continue
|
||||
}
|
||||
peer, ok := byName[b.Node]
|
||||
if !ok || peer.WeaveIP == nil {
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("egress %s: node %s has no weave bridge published; its members' replies cannot reach it", key, b.Node))
|
||||
continue
|
||||
}
|
||||
d.Routes = append(d.Routes, BlockRoute{Address: addr, From: b.From, To: b.To, Peer: peer})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this node's pods, one per IP: the CNI plugin's latest word wins, then live pods over terminating ones
|
||||
type candidate struct {
|
||||
pod *coreapi.Pod
|
||||
ip string
|
||||
}
|
||||
byIP := map[string]*coreapi.Pod{}
|
||||
podByKey := map[string]*coreapi.Pod{}
|
||||
for _, p := range pods {
|
||||
podByKey[p.Namespace+"/"+p.Name] = p
|
||||
}
|
||||
var cands []candidate
|
||||
for _, p := range pods {
|
||||
if p.Spec.NodeName != self || p.Spec.HostNetwork || p.Status.Phase == coreapi.PodSucceeded || p.Status.Phase == coreapi.PodFailed {
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(p.Status.PodIP).To4(); ip != nil {
|
||||
cands = append(cands, candidate{p, ip.String()})
|
||||
}
|
||||
}
|
||||
for ip, a := range admitted {
|
||||
if p := podByKey[a.Namespace+"/"+a.Name]; p != nil && p.Spec.NodeName == self && !p.Spec.HostNetwork {
|
||||
cands = append(cands, candidate{p, ip})
|
||||
}
|
||||
}
|
||||
for _, c := range cands {
|
||||
if a, ok := admitted[c.ip]; ok && (a.Namespace != c.pod.Namespace || a.Name != c.pod.Name) {
|
||||
continue // the IP was given to another pod since
|
||||
}
|
||||
cur := byIP[c.ip]
|
||||
if cur == nil || (cur.DeletionTimestamp != nil && c.pod.DeletionTimestamp == nil) ||
|
||||
(cur.DeletionTimestamp == nil) == (c.pod.DeletionTimestamp == nil) && c.pod.CreationTimestamp.After(cur.CreationTimestamp.Time) {
|
||||
byIP[c.ip] = c.pod
|
||||
}
|
||||
}
|
||||
ips := make([]string, 0, len(byIP))
|
||||
for ip := range byIP {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
sort.Strings(ips)
|
||||
for _, ip := range ips {
|
||||
p := byIP[ip]
|
||||
var in []*EgressService
|
||||
for _, e := range es {
|
||||
if e.Namespace != p.Namespace {
|
||||
continue
|
||||
}
|
||||
if s := svcs[e.Namespace+"/"+e.Name]; s != nil && selects(s, p.Labels) {
|
||||
in = append(in, e)
|
||||
}
|
||||
}
|
||||
if len(in) == 0 {
|
||||
continue
|
||||
}
|
||||
m := Member{IP: net.ParseIP(ip).To4(), Pod: p.Namespace + "/" + p.Name, Unreachable: true}
|
||||
if len(in) > 1 {
|
||||
keys := make([]string, len(in))
|
||||
for i, e := range in {
|
||||
keys[i] = e.Namespace + "/" + e.Name
|
||||
}
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("pod %s is selected by several EgressServices (%s); its outside traffic is dropped", m.Pod, strings.Join(keys, ", ")))
|
||||
d.Members = append(d.Members, m)
|
||||
continue
|
||||
}
|
||||
e := in[0]
|
||||
m.Service = e.Namespace + "/" + e.Name
|
||||
if valid[m.Service] {
|
||||
holder := byName[e.Status.Host]
|
||||
for _, b := range e.Status.Blocks {
|
||||
if b.Node == self && (holder.Name == self || holder.WeaveIP != nil) {
|
||||
m.Unreachable = false
|
||||
m.Address = net.ParseIP(e.Status.Address).To4()
|
||||
m.From, m.To = b.From, b.To
|
||||
m.Holder = holder
|
||||
}
|
||||
}
|
||||
if m.Unreachable {
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("pod %s: egress %s has no port block for %s (or its holder's bridge is unknown); its outside traffic is dropped", m.Pod, m.Service, self))
|
||||
}
|
||||
}
|
||||
d.Members = append(d.Members, m)
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// EgressService (reweave.levk.dev/v1alpha1) makes the pods a LoadBalancer Service selects leave the cluster
|
||||
// from the Service's address. It is named after the Service (same namespace, same name). Its semantics follow
|
||||
// OVN-Kubernetes' EgressService (k8s.ovn.org/v1, sourceIPBy: LoadBalancerIP) where they agree; they differ in
|
||||
// that membership is every pod the Service's selector matches (by pod IP, never by readiness), the holder is
|
||||
// the node that holds the address (no election), and a member's traffic to the outside leaves as the address
|
||||
// or not at all.
|
||||
type EgressService struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Spec EgressServiceSpec `json:"spec"`
|
||||
Status EgressServiceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// EgressServiceSpec is what the operator asks for.
|
||||
type EgressServiceSpec struct {
|
||||
// SourceIPBy is LoadBalancerIP: members leave from the Service's load-balancer address (the only mode in v1).
|
||||
SourceIPBy string `json:"sourceIPBy,omitempty"`
|
||||
// Emit is where members' packets leave the cluster. Explicit, no default: Holder (tunnelled to the node
|
||||
// holding the address, which emits them) is the only mode in v1.
|
||||
Emit string `json:"emit"`
|
||||
}
|
||||
|
||||
// EgressServiceStatus is written by the holder alone.
|
||||
type EgressServiceStatus struct {
|
||||
// Host is the node that holds the address and emits members' packets.
|
||||
Host string `json:"host,omitempty"`
|
||||
// Address is the address members leave from.
|
||||
Address string `json:"address,omitempty"`
|
||||
// Blocks gives every node its own source ports on Address, so the holder routes replies back by port.
|
||||
Blocks []PortBlock `json:"blocks,omitempty"`
|
||||
}
|
||||
|
||||
// PortBlock is the source-port range [From, To] on the address that one node's members use.
|
||||
type PortBlock struct {
|
||||
Node string `json:"node"`
|
||||
From int32 `json:"from"`
|
||||
To int32 `json:"to"`
|
||||
}
|
||||
|
||||
// EgressServiceList is a list of EgressServices.
|
||||
type EgressServiceList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []EgressService `json:"items"`
|
||||
}
|
||||
|
||||
const (
|
||||
// Group and Version of the EgressService API.
|
||||
Group = "reweave.levk.dev"
|
||||
Version = "v1alpha1"
|
||||
// EgressServices is the resource name.
|
||||
EgressServices = "egressservices"
|
||||
|
||||
SourceIPByLoadBalancerIP = "LoadBalancerIP"
|
||||
EmitHolder = "Holder"
|
||||
)
|
||||
|
||||
// GroupVersion of the EgressService API.
|
||||
var GroupVersion = schema.GroupVersion{Group: Group, Version: Version}
|
||||
|
||||
// DeepCopyObject implements runtime.Object.
|
||||
func (e *EgressService) DeepCopyObject() runtime.Object { return e.DeepCopy() }
|
||||
|
||||
// DeepCopy copies the whole object.
|
||||
func (e *EgressService) DeepCopy() *EgressService {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
out := *e
|
||||
e.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Status.Blocks = append([]PortBlock(nil), e.Status.Blocks...)
|
||||
return &out
|
||||
}
|
||||
|
||||
// DeepCopyObject implements runtime.Object.
|
||||
func (l *EgressServiceList) DeepCopyObject() runtime.Object {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
out := *l
|
||||
l.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
out.Items = make([]EgressService, len(l.Items))
|
||||
for i := range l.Items {
|
||||
out.Items[i] = *l.Items[i].DeepCopy()
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
func addKnownTypes(s *runtime.Scheme) error {
|
||||
s.AddKnownTypes(GroupVersion, &EgressService{}, &EgressServiceList{})
|
||||
metav1.AddToGroupVersion(s, GroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewEgressClient is a REST client for the EgressService API (no generated clientset: two plain types).
|
||||
func NewEgressClient(base *rest.Config) (*rest.RESTClient, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
if err := addKnownTypes(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := *base
|
||||
cfg.GroupVersion = &GroupVersion
|
||||
cfg.APIPath = "/apis"
|
||||
cfg.ContentType = runtime.ContentTypeJSON
|
||||
cfg.NegotiatedSerializer = serializer.NewCodecFactory(scheme).WithoutConversion()
|
||||
if cfg.UserAgent == "" {
|
||||
cfg.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
return rest.RESTClientFor(&cfg)
|
||||
}
|
||||
|
||||
// PatchEgressStatus merge-patches an EgressService's status (nil fields are removed).
|
||||
func PatchEgressStatus(ctx context.Context, c rest.Interface, namespace, name string, st EgressServiceStatus) error {
|
||||
body := map[string]interface{}{"host": nil, "address": nil, "blocks": nil}
|
||||
if st.Host != "" {
|
||||
body["host"] = st.Host
|
||||
}
|
||||
if st.Address != "" {
|
||||
body["address"] = st.Address
|
||||
}
|
||||
if len(st.Blocks) > 0 {
|
||||
body["blocks"] = st.Blocks
|
||||
}
|
||||
patch, err := json.Marshal(map[string]interface{}{"status": body})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Patch(types.MergePatchType).Namespace(namespace).Resource(EgressServices).Name(name).
|
||||
SubResource("status").Body(patch).Do(ctx).Error()
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// ClassChain (nat, appended to PREROUTING after kube-proxy's KUBE-SERVICES) marks members' new connections to
|
||||
// the outside. kube-proxy's DNAT ends the nat walk, so a connection to a Service never gets here.
|
||||
ClassChain = "REWEAVE-LB-CLASS"
|
||||
// FwdChain (filter, first in FORWARD) drops members' outside packets that would not leave as their address.
|
||||
FwdChain = "REWEAVE-LB-FWD"
|
||||
// InChain (filter, first in INPUT) accepts the egress tunnels only from the cluster's nodes.
|
||||
InChain = "REWEAVE-LB-IN"
|
||||
// MSSChain (mangle, first in FORWARD) clamps TCP MSS into the tunnels.
|
||||
MSSChain = "REWEAVE-LB-MSS"
|
||||
|
||||
// TunnelPrefix names the egress tunnels: one ipip device per peer node, between the two nodes' weave bridges,
|
||||
// so a holder knows which node a member's packet came from by the device it arrives on.
|
||||
TunnelPrefix = "rweg-"
|
||||
// tunnelGlob matches every egress tunnel in an iptables rule.
|
||||
tunnelGlob = "rweg+"
|
||||
// legacyTunnel is the single shared tunnel of earlier builds (removed when found).
|
||||
legacyTunnel = "reweave-eg"
|
||||
|
||||
// EgressMark marks a member's packets to the outside (connmark on its connections, fwmark on each packet).
|
||||
EgressMark = uint32(0x100000)
|
||||
// peerFlag, with a peer's index in the top byte, tags (on the holder) a connection that arrived from that peer's
|
||||
// tunnel: replies and related ICMP errors carry it back there.
|
||||
peerFlag = uint32(0x200000)
|
||||
peerMask = markMask | peerFlag
|
||||
|
||||
// HolderRulePriority: replies to a block go to its node; ahead of the local table, which moves to LocalRulePriority,
|
||||
// since the address is usually local on the holder.
|
||||
HolderRulePriority = 90
|
||||
LocalRulePriority = 100
|
||||
// MemberRulePriority: a member's marked packets go to its holder (or nowhere).
|
||||
MemberRulePriority = 9100
|
||||
// EgressTableBase + peer index routes into that peer's tunnel; EgressTableBase itself is unreachable.
|
||||
EgressTableBase = 53000
|
||||
|
||||
ipipOverhead = 20
|
||||
)
|
||||
|
||||
// portProtocols carry ports: their replies are routed by port block alone (stateless), and members' SNAT keeps
|
||||
// within the node's block. Everything else is routed back by the tag the holder put on the connection.
|
||||
var portProtocols = []struct {
|
||||
name string
|
||||
num int
|
||||
}{{"tcp", unix.IPPROTO_TCP}, {"udp", unix.IPPROTO_UDP}, {"sctp", unix.IPPROTO_SCTP}}
|
||||
|
||||
// TunnelName is the egress tunnel towards a peer node (stable, within the 15 characters a device name may have).
|
||||
func TunnelName(peer string) string {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(peer))
|
||||
return fmt.Sprintf("%s%08x", TunnelPrefix, h.Sum32())
|
||||
}
|
||||
|
||||
func peerTag(idx uint32) uint32 { return markValue(idx) | peerFlag }
|
||||
|
||||
// renderEgress appends the egress rules of each table to the bodies of S1's payloads.
|
||||
func renderEgress(e EgressDesired, self string, podNet *net.IPNet, nat, mangle, filter *bytes.Buffer) {
|
||||
members := e.Members
|
||||
if len(members) > 0 {
|
||||
// what is not "outside": the pod network, multicast, the cluster's nodes
|
||||
fmt.Fprintf(nat, "-A %s -d %s -j RETURN\n", ClassChain, podNet)
|
||||
fmt.Fprintf(nat, "-A %s -d 224.0.0.0/4 -j RETURN\n", ClassChain)
|
||||
seen := map[string]bool{}
|
||||
for _, a := range e.NodeAddrs {
|
||||
if !seen[a.String()] {
|
||||
seen[a.String()] = true
|
||||
fmt.Fprintf(nat, "-A %s -d %s/32 -j RETURN\n", ClassChain, a)
|
||||
}
|
||||
}
|
||||
for _, m := range members {
|
||||
fmt.Fprintf(nat, "-A %s -s %s/32 -m comment --comment %q -j CONNMARK --set-xmark 0x%x/0x%x\n", ClassChain, m.IP, m.Pod, EgressMark, EgressMark)
|
||||
fmt.Fprintf(nat, "-A %s -s %s/32 -j MARK --set-xmark 0x%x/0x%x\n", ClassChain, m.IP, EgressMark, EgressMark)
|
||||
}
|
||||
// later packets of a member's outside connection carry the mark too (the nat table saw only the first)
|
||||
fmt.Fprintf(mangle, "-A %s -m conntrack --ctdir ORIGINAL -m connmark --mark 0x%x/0x%x -j MARK --set-xmark 0x%x/0x%x\n",
|
||||
MarkChain, EgressMark, EgressMark, EgressMark, EgressMark)
|
||||
}
|
||||
for _, m := range members {
|
||||
if m.Unreachable {
|
||||
fmt.Fprintf(filter, "-A %s -s %s/32 -m mark --mark 0x%x/0x%x -m comment --comment %q -j DROP\n", FwdChain, m.IP, EgressMark, EgressMark, m.Pod)
|
||||
continue
|
||||
}
|
||||
out := "-o " + TunnelName(m.Holder.Name)
|
||||
if m.Holder.Name == self {
|
||||
// emitted here, from the address: never into the pod network unSNATed
|
||||
out = "! -o " + bridge
|
||||
fmt.Fprintf(filter, "-A %s -s %s/32 -o %s -m mark --mark 0x%x/0x%x -j DROP\n", FwdChain, m.IP, bridge, EgressMark, EgressMark)
|
||||
}
|
||||
// within this node's block for what has ports; the address alone for the rest (the holder tells those
|
||||
// apart by the tunnel they arrive on)
|
||||
for _, p := range portProtocols {
|
||||
fmt.Fprintf(nat, "-A %s -s %s/32 %s -m mark --mark 0x%x/0x%x -p %s -j SNAT --to-source %s:%d-%d\n",
|
||||
PostChain, m.IP, out, EgressMark, EgressMark, p.name, m.Address, m.From, m.To)
|
||||
}
|
||||
// ICMP queries (ping) too: NAT treats the echo identifier as a port, and without a block of their own two
|
||||
// nodes' pods (whose small, alike PIDs make alike identifiers) collide on the holder
|
||||
fmt.Fprintf(nat, "-A %s -s %s/32 %s -m mark --mark 0x%x/0x%x -p icmp -j SNAT --to-source %s:%d-%d\n",
|
||||
PostChain, m.IP, out, EgressMark, EgressMark, m.Address, m.From, m.To)
|
||||
fmt.Fprintf(nat, "-A %s -s %s/32 %s -m mark --mark 0x%x/0x%x -j SNAT --to-source %s\n",
|
||||
PostChain, m.IP, out, EgressMark, EgressMark, m.Address)
|
||||
fmt.Fprintf(filter, "-A %s -s %s/32 %s -m mark --mark 0x%x/0x%x -j RETURN\n", FwdChain, m.IP, out, EgressMark, EgressMark)
|
||||
// a member's outside traffic leaves no other way
|
||||
fmt.Fprintf(filter, "-A %s -s %s/32 -m mark --mark 0x%x/0x%x -m comment --comment %q -j DROP\n", FwdChain, m.IP, EgressMark, EgressMark, m.Pod)
|
||||
}
|
||||
if !e.Tunnelled(self) {
|
||||
return
|
||||
}
|
||||
tags := peerIndex(e, self)
|
||||
|
||||
// as holder: tag each connection arriving from a peer's tunnel with that peer, once; replies and related ICMP
|
||||
// errors restore the tag, and the policy rule for it sends them back into that tunnel
|
||||
byPeer := map[string][]BlockRoute{}
|
||||
var peerNames []string
|
||||
for _, r := range e.Routes {
|
||||
if _, ok := byPeer[r.Peer.Name]; !ok {
|
||||
peerNames = append(peerNames, r.Peer.Name)
|
||||
}
|
||||
byPeer[r.Peer.Name] = append(byPeer[r.Peer.Name], r)
|
||||
}
|
||||
sort.Strings(peerNames)
|
||||
if len(peerNames) > 0 {
|
||||
fmt.Fprintf(mangle, "-A %s -m conntrack --ctdir REPLY -m connmark --mark 0x%x/0x%x -j CONNMARK --restore-mark --nfmask 0x%x --ctmask 0x%x\n",
|
||||
MarkChain, peerFlag, peerFlag, peerMask, peerMask)
|
||||
}
|
||||
for _, name := range peerNames {
|
||||
dev, tag := TunnelName(name), peerTag(tags[name])
|
||||
fmt.Fprintf(mangle, "-A %s -i %s -m conntrack --ctstate NEW -m connmark --mark 0x0/0x%x -m comment --comment %q -j CONNMARK --set-xmark 0x%x/0x%x\n",
|
||||
MarkChain, dev, peerMask, "from "+name, tag, peerMask)
|
||||
// a connection another node already holds (a port-less protocol to the same remote): never cross-delivered
|
||||
fmt.Fprintf(filter, "-A %s -i %s -m conntrack --ctdir ORIGINAL -m connmark ! --mark 0x%x/0x%x -j DROP\n", FwdChain, dev, tag, peerMask)
|
||||
addrs := map[string]bool{}
|
||||
for _, r := range byPeer[name] {
|
||||
for _, p := range portProtocols {
|
||||
fmt.Fprintf(filter, "-A %s -i %s -m conntrack --ctdir ORIGINAL -s %s/32 -p %s -m %s --sport %d:%d -m comment --comment %q -j RETURN\n",
|
||||
FwdChain, dev, r.Address, p.name, p.name, r.From, r.To, "block of "+name)
|
||||
}
|
||||
addrs[r.Address.String()] = true
|
||||
}
|
||||
for _, a := range sortedSet(addrs) {
|
||||
// outside its own block a node may not use the ports of others
|
||||
for _, p := range portProtocols {
|
||||
fmt.Fprintf(filter, "-A %s -i %s -m conntrack --ctdir ORIGINAL -s %s/32 -p %s -j DROP\n", FwdChain, dev, a, p.name)
|
||||
}
|
||||
fmt.Fprintf(filter, "-A %s -i %s -m conntrack --ctdir ORIGINAL -s %s/32 -j RETURN\n", FwdChain, dev, a)
|
||||
}
|
||||
}
|
||||
// as a member's node: replies (and related errors) from the holder; nothing else comes back
|
||||
addrs := map[string]bool{}
|
||||
for _, m := range members {
|
||||
if !m.Unreachable && m.Holder.Name != self {
|
||||
addrs[m.Address.String()] = true
|
||||
}
|
||||
}
|
||||
for _, a := range sortedSet(addrs) {
|
||||
// still addressed to the address after nat: no member connection matched it
|
||||
fmt.Fprintf(filter, "-A %s -i %s -d %s/32 -j DROP\n", FwdChain, tunnelGlob, a)
|
||||
}
|
||||
fmt.Fprintf(filter, "-A %s -i %s -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN\n", FwdChain, tunnelGlob)
|
||||
fmt.Fprintf(filter, "-A %s -i %s -j DROP\n", FwdChain, tunnelGlob)
|
||||
// the tunnels themselves only from the cluster's nodes' bridges
|
||||
for _, p := range e.Peers(self) {
|
||||
fmt.Fprintf(filter, "-A %s -p 4 -s %s/32 -m mac --mac-source %s -j RETURN\n", InChain, p.WeaveIP, p.WeaveMAC)
|
||||
}
|
||||
fmt.Fprintf(filter, "-A %s -p 4 -j DROP\n", InChain)
|
||||
fmt.Fprintf(mangle, "-A %s -o %s -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu\n", MSSChain, tunnelGlob)
|
||||
}
|
||||
|
||||
func sortedSet(m map[string]bool) []string {
|
||||
out := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
out = append(out, k)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// peerIndex gives each tunnel peer a stable index (1..MaxMarks): its tag and its routing table.
|
||||
func peerIndex(e EgressDesired, self string) map[string]uint32 {
|
||||
out := map[string]uint32{}
|
||||
for _, h := range assignMarks(e.Peers(self)) {
|
||||
out[h.Node.Name] = h.Mark
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// peerTables gives each tunnel peer its routing table (EgressTableBase + its index).
|
||||
func peerTables(e EgressDesired, self string) map[string]int {
|
||||
out := map[string]int{}
|
||||
for name, idx := range peerIndex(e, self) {
|
||||
out[name] = EgressTableBase + int(idx)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// egressRule is a policy rule this package owns, in a comparable form.
|
||||
type egressRule struct {
|
||||
priority int
|
||||
src, dst string
|
||||
ipproto int
|
||||
from, to uint16
|
||||
mark, mask uint32
|
||||
table int
|
||||
}
|
||||
|
||||
func (r egressRule) key() string {
|
||||
return fmt.Sprintf("%d|%s|%s|%d|%d-%d|%x/%x|%d", r.priority, r.src, r.dst, r.ipproto, r.from, r.to, r.mark, r.mask, r.table)
|
||||
}
|
||||
|
||||
// wantRules is every egress policy rule this node needs.
|
||||
func wantRules(e EgressDesired, self string) []egressRule {
|
||||
tables := peerTables(e, self)
|
||||
tags := peerIndex(e, self)
|
||||
var out []egressRule
|
||||
tagged := map[string]bool{}
|
||||
for _, r := range e.Routes {
|
||||
for _, p := range portProtocols {
|
||||
out = append(out, egressRule{priority: HolderRulePriority, dst: r.Address.String() + "/32", ipproto: p.num,
|
||||
from: uint16(r.From), to: uint16(r.To), table: tables[r.Peer.Name]})
|
||||
}
|
||||
if !tagged[r.Peer.Name] {
|
||||
// whatever the tag says came from that peer (replies without ports, ICMP errors) goes back to it
|
||||
tagged[r.Peer.Name] = true
|
||||
out = append(out, egressRule{priority: HolderRulePriority, mark: peerTag(tags[r.Peer.Name]), mask: peerMask, table: tables[r.Peer.Name]})
|
||||
}
|
||||
}
|
||||
for _, m := range e.Members {
|
||||
if !m.Unreachable && m.Holder.Name == self {
|
||||
continue // emitted here: the main table already sends it out
|
||||
}
|
||||
table := EgressTableBase
|
||||
if !m.Unreachable {
|
||||
table = tables[m.Holder.Name]
|
||||
}
|
||||
out = append(out, egressRule{priority: MemberRulePriority, src: m.IP.String() + "/32", mark: EgressMark, mask: EgressMark, table: table})
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].key() < out[j].key() })
|
||||
return out
|
||||
}
|
||||
|
||||
func ruleOf(r netlink.Rule) egressRule {
|
||||
er := egressRule{priority: r.Priority, ipproto: r.IPProto, table: r.Table}
|
||||
if r.Src != nil {
|
||||
er.src = r.Src.String()
|
||||
}
|
||||
if r.Dst != nil {
|
||||
er.dst = r.Dst.String()
|
||||
}
|
||||
if r.Dport != nil {
|
||||
er.from, er.to = r.Dport.Start, r.Dport.End
|
||||
}
|
||||
if r.Mark > 0 {
|
||||
er.mark = uint32(r.Mark)
|
||||
er.mask = uint32(r.Mask)
|
||||
}
|
||||
if er.ipproto < 0 {
|
||||
er.ipproto = 0
|
||||
}
|
||||
return er
|
||||
}
|
||||
|
||||
// routesByTable lists, in one dump, the IPv4 routes of tables lo..hi.
|
||||
func routesByTable(lo, hi int) (map[int][]netlink.Route, error) {
|
||||
all, err := netlink.RouteListFiltered(unix.AF_INET, &netlink.Route{Table: unix.RT_TABLE_UNSPEC}, netlink.RT_FILTER_TABLE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := map[int][]netlink.Route{}
|
||||
for _, rt := range all {
|
||||
if rt.Table >= lo && rt.Table <= hi {
|
||||
out[rt.Table] = append(out[rt.Table], rt)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// egressNet makes the local rule's place, the policy rules and the tunnel tables exactly what e needs (the
|
||||
// tunnels themselves are made first, by Apply).
|
||||
func egressNet(e EgressDesired, self string) error {
|
||||
if len(e.Routes) > 0 {
|
||||
if err := moveLocalRule(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tables := peerTables(e, self)
|
||||
peerOf := map[int]Node{}
|
||||
for _, p := range e.Peers(self) {
|
||||
peerOf[tables[p.Name]] = p
|
||||
}
|
||||
unreachable := false
|
||||
for _, m := range e.Members {
|
||||
unreachable = unreachable || m.Unreachable
|
||||
}
|
||||
existing, err := routesByTable(EgressTableBase, EgressTableBase+MaxMarks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for table := EgressTableBase; table <= EgressTableBase+MaxMarks; table++ {
|
||||
var want *netlink.Route
|
||||
if p, ok := peerOf[table]; ok {
|
||||
link, err := netlink.LinkByName(TunnelName(p.Name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("egress tunnel to %s: %v", p.Name, err)
|
||||
}
|
||||
want = &netlink.Route{LinkIndex: link.Attrs().Index, Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)},
|
||||
Scope: netlink.SCOPE_LINK, Table: table}
|
||||
} else if table == EgressTableBase && unreachable {
|
||||
want = &netlink.Route{Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)}, Table: table, Type: unix.RTN_UNREACHABLE}
|
||||
}
|
||||
keep := false
|
||||
for _, rt := range existing[table] {
|
||||
if want != nil && rt.Type == want.Type &&
|
||||
(want.Type == unix.RTN_UNREACHABLE || rt.LinkIndex == want.LinkIndex && rt.Gw == nil) {
|
||||
keep = true
|
||||
continue
|
||||
}
|
||||
if err := delRoute(rt); err != nil {
|
||||
return fmt.Errorf("removing stale route in table %d: %v", table, err)
|
||||
}
|
||||
}
|
||||
if want != nil && !keep {
|
||||
if err := netlink.RouteReplace(want); err != nil {
|
||||
return fmt.Errorf("route in table %d: %v", table, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
want := map[string]egressRule{}
|
||||
for _, r := range wantRules(e, self) {
|
||||
want[r.key()] = r
|
||||
}
|
||||
rules, err := netlink.RuleList(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
have := map[string]bool{}
|
||||
for _, r := range rules {
|
||||
if r.Priority != HolderRulePriority && r.Priority != MemberRulePriority {
|
||||
continue
|
||||
}
|
||||
k := ruleOf(r).key()
|
||||
if _, ok := want[k]; ok && !have[k] {
|
||||
have[k] = true
|
||||
continue
|
||||
}
|
||||
r := r
|
||||
if err := netlink.RuleDel(&r); err != nil {
|
||||
return fmt.Errorf("removing stale egress rule: %v", err)
|
||||
}
|
||||
}
|
||||
for k, r := range want {
|
||||
if have[k] {
|
||||
continue
|
||||
}
|
||||
rule := netlink.NewRule()
|
||||
rule.Priority = r.priority
|
||||
rule.Table = r.table
|
||||
if r.src != "" {
|
||||
_, rule.Src, _ = net.ParseCIDR(r.src)
|
||||
}
|
||||
if r.dst != "" {
|
||||
_, rule.Dst, _ = net.ParseCIDR(r.dst)
|
||||
}
|
||||
if r.ipproto != 0 {
|
||||
rule.IPProto = r.ipproto
|
||||
rule.Dport = netlink.NewRulePortRange(r.from, r.to)
|
||||
}
|
||||
if r.mark != 0 {
|
||||
rule.Mark = int(r.mark)
|
||||
rule.Mask = int(r.mask)
|
||||
}
|
||||
if err := netlink.RuleAdd(rule); err != nil {
|
||||
return fmt.Errorf("adding egress rule %s: %v", k, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// moveLocalRule puts the local table's lookup after HolderRulePriority (it is at 0 by default), so the holder can
|
||||
// route replies to its own address onwards to their node instead of delivering them to itself.
|
||||
func moveLocalRule() error {
|
||||
rules, err := netlink.RuleList(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var at0, atLocal bool
|
||||
for _, r := range rules {
|
||||
if r.Table == unix.RT_TABLE_LOCAL && r.Src == nil && r.Dst == nil && r.Mark <= 0 {
|
||||
switch {
|
||||
case r.Priority <= 0:
|
||||
// the kernel leaves out the priority attribute when it is 0: listed as unset (-1)
|
||||
at0 = true
|
||||
case r.Priority == LocalRulePriority:
|
||||
atLocal = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !atLocal {
|
||||
rule := netlink.NewRule()
|
||||
rule.Priority = LocalRulePriority
|
||||
rule.Table = unix.RT_TABLE_LOCAL
|
||||
if err := netlink.RuleAdd(rule); err != nil {
|
||||
return fmt.Errorf("adding local table rule at %d: %v", LocalRulePriority, err)
|
||||
}
|
||||
}
|
||||
if at0 {
|
||||
rule := netlink.NewRule()
|
||||
rule.Priority = 0
|
||||
rule.Table = unix.RT_TABLE_LOCAL
|
||||
if err := netlink.RuleDel(rule); err != nil {
|
||||
return fmt.Errorf("removing local table rule at 0: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureTunnels makes one ipip tunnel per peer (this node's bridge address to the peer's), and removes every
|
||||
// other egress tunnel (including the shared one of earlier builds).
|
||||
func ensureTunnels(peers []Node, local Local) error {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
want := map[string]Node{}
|
||||
for _, p := range peers {
|
||||
want[TunnelName(p.Name)] = p
|
||||
}
|
||||
have := map[string]netlink.Link{}
|
||||
for _, l := range links {
|
||||
name := l.Attrs().Name
|
||||
if name == legacyTunnel || strings.HasPrefix(name, TunnelPrefix) {
|
||||
if _, ok := want[name]; ok {
|
||||
have[name] = l
|
||||
continue
|
||||
}
|
||||
if err := netlink.LinkDel(l); err != nil {
|
||||
return fmt.Errorf("removing egress tunnel %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(want) == 0 {
|
||||
return nil
|
||||
}
|
||||
if local.WeaveIP == nil {
|
||||
return fmt.Errorf("egress tunnels need the weave bridge's address")
|
||||
}
|
||||
br, err := netlink.LinkByName(bridge)
|
||||
if err != nil {
|
||||
return fmt.Errorf("weave bridge: %v", err)
|
||||
}
|
||||
mtu := br.Attrs().MTU - ipipOverhead
|
||||
names := make([]string, 0, len(want))
|
||||
for n := range want {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
peer := want[name]
|
||||
link := have[name]
|
||||
if link != nil {
|
||||
t, ok := link.(*netlink.Iptun)
|
||||
if !ok || !t.Local.Equal(local.WeaveIP) || !t.Remote.Equal(peer.WeaveIP) || t.Attrs().MTU != mtu {
|
||||
if err := netlink.LinkDel(link); err != nil {
|
||||
return err
|
||||
}
|
||||
link = nil
|
||||
}
|
||||
}
|
||||
if link == nil {
|
||||
t := &netlink.Iptun{LinkAttrs: netlink.LinkAttrs{Name: name, MTU: mtu}, Local: local.WeaveIP, Remote: peer.WeaveIP, PMtuDisc: 1}
|
||||
if err := netlink.LinkAdd(t); err != nil {
|
||||
return fmt.Errorf("creating egress tunnel %s to %s: %v", name, peer.Name, err)
|
||||
}
|
||||
if link, err = netlink.LinkByName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tunnelSetup(link, local); err != nil {
|
||||
return fmt.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tunnelSetup: the holder receives members' packets from its own address (accept_local); reverse-path filtering
|
||||
// rejects what arrives on a device with no IPv4 address unless the reverse route leaves by that very device, so
|
||||
// the tunnel carries this node's bridge address (never on the wire from here: the tunnel sets the outer source,
|
||||
// and what crosses it is forwarded).
|
||||
func tunnelSetup(link netlink.Link, local Local) error {
|
||||
name := link.Attrs().Name
|
||||
for k, v := range map[string]string{"accept_local": "1", "rp_filter": "2"} {
|
||||
if err := os.WriteFile("/proc/sys/net/ipv4/conf/"+name+"/"+k, []byte(v), 0o644); err != nil {
|
||||
return fmt.Errorf("sysctl %s: %v", k, err)
|
||||
}
|
||||
}
|
||||
addrs, err := netlink.AddrList(link, unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
has := false
|
||||
for _, a := range addrs {
|
||||
if a.IP.Equal(local.WeaveIP) {
|
||||
has = true
|
||||
continue
|
||||
}
|
||||
a := a
|
||||
if err := netlink.AddrDel(link, &a); err != nil {
|
||||
return fmt.Errorf("removing stale address %s: %v", a.IPNet, err)
|
||||
}
|
||||
}
|
||||
if !has {
|
||||
addr := &netlink.Addr{IPNet: &net.IPNet{IP: local.WeaveIP, Mask: net.CIDRMask(32, 32)}, Scope: unix.RT_SCOPE_LINK}
|
||||
if err := netlink.AddrAdd(link, addr); err != nil {
|
||||
return fmt.Errorf("address: %v", err)
|
||||
}
|
||||
}
|
||||
return netlink.LinkSetUp(link)
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// the S1 fixtures: cp, w1 (holds 86.107.168.15), w2
|
||||
|
||||
func egressFor(name string, status EgressServiceStatus) *EgressService {
|
||||
return &EgressService{ObjectMeta: metav1.ObjectMeta{Namespace: "gitea", Name: name},
|
||||
Spec: EgressServiceSpec{Emit: EmitHolder}, Status: status}
|
||||
}
|
||||
|
||||
func mailSvc() *coreapi.Service {
|
||||
return svc("mail", "86.107.168.15", func(s *coreapi.Service) {
|
||||
s.Spec.Selector = map[string]string{"app": "mail"}
|
||||
})
|
||||
}
|
||||
|
||||
func pod(name, node, ip string, labels map[string]string, mutate ...func(*coreapi.Pod)) *coreapi.Pod {
|
||||
p := &coreapi.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "gitea", Name: name, Labels: labels,
|
||||
CreationTimestamp: metav1.NewTime(time.Unix(1000, 0))},
|
||||
Spec: coreapi.PodSpec{NodeName: node}, Status: coreapi.PodStatus{PodIP: ip, Phase: coreapi.PodRunning}}
|
||||
for _, m := range mutate {
|
||||
m(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
var mailLabels = map[string]string{"app": "mail"}
|
||||
|
||||
// the status the holder (w1) writes for three nodes, blocks in name order
|
||||
func written() EgressServiceStatus {
|
||||
return EgressServiceStatus{Host: "w1", Address: "86.107.168.15", Blocks: []PortBlock{
|
||||
{Node: "cp", From: 61000, To: 61511}, {Node: "w1", From: 61512, To: 62023}, {Node: "w2", From: 62024, To: 62535}}}
|
||||
}
|
||||
|
||||
func TestBlocksAreStableAndCapped(t *testing.T) {
|
||||
got := AllocateBlocks(nil, []string{"w2", "cp", "w1"})
|
||||
if !sameStatus(EgressServiceStatus{Blocks: got}, EgressServiceStatus{Blocks: written().Blocks}) {
|
||||
t.Fatalf("fresh blocks: %+v", got)
|
||||
}
|
||||
// a node leaves, another joins: survivors keep their blocks, the newcomer takes the freed lowest one
|
||||
got = AllocateBlocks(got, []string{"w1", "w2", "w3"})
|
||||
want := []PortBlock{{"w1", 61512, 62023}, {"w2", 62024, 62535}, {"w3", 61000, 61511}}
|
||||
if !sameStatus(EgressServiceStatus{Blocks: got}, EgressServiceStatus{Blocks: want}) {
|
||||
t.Fatalf("after churn: %+v", got)
|
||||
}
|
||||
// misaligned or duplicate blocks are not kept
|
||||
got = AllocateBlocks([]PortBlock{{"w1", 61001, 61512}, {"w2", 61000, 61511}, {"w3", 61000, 61511}}, []string{"w1", "w2", "w3"})
|
||||
for _, b := range got {
|
||||
if (b.From-BlockBase)%BlockSize != 0 || b.To != b.From+BlockSize-1 {
|
||||
t.Fatalf("invalid block kept: %+v", got)
|
||||
}
|
||||
}
|
||||
if got[1].Node != "w2" || got[1].From != 61000 {
|
||||
t.Fatalf("first valid claim wins: %+v", got)
|
||||
}
|
||||
var many []string
|
||||
for i := 0; i < BlockCount+2; i++ {
|
||||
many = append(many, string(rune('a'+i)))
|
||||
}
|
||||
if n := len(AllocateBlocks(nil, many)); n != BlockCount {
|
||||
t.Fatalf("more blocks than exist: %d", n)
|
||||
}
|
||||
for _, b := range AllocateBlocks(nil, many) {
|
||||
if b.To > 65535 || b.From < 61000 {
|
||||
t.Fatalf("block outside the range: %+v", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHolderWritesStatusAndMembersWaitForIt(t *testing.T) {
|
||||
e := egressFor("mail", EgressServiceStatus{})
|
||||
pods := []*coreapi.Pod{pod("mail-0", "w2", "10.44.0.9", mailLabels)}
|
||||
d := ComputeEgress("w1", nodes, []*coreapi.Service{mailSvc()}, []*EgressService{e}, nil, nil)
|
||||
if len(d.Status) != 1 || !sameStatus(d.Status[0].Status, written()) {
|
||||
t.Fatalf("holder's status: %+v", d.Status)
|
||||
}
|
||||
// until the status is in the API, a member's outside traffic goes nowhere (fail closed)
|
||||
d = ComputeEgress("w2", nodes, []*coreapi.Service{mailSvc()}, []*EgressService{e}, pods, nil)
|
||||
if len(d.Status) != 0 || len(d.Members) != 1 || !d.Members[0].Unreachable {
|
||||
t.Fatalf("member before status: %+v", d)
|
||||
}
|
||||
if d.Tunnelled("w2") {
|
||||
t.Fatalf("no tunnel for an unreachable member")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberTunnelsToHolderWhichRoutesItsBlockBack(t *testing.T) {
|
||||
e := egressFor("mail", written())
|
||||
svcs := []*coreapi.Service{mailSvc()}
|
||||
pods := []*coreapi.Pod{
|
||||
pod("mail-0", "w2", "10.44.0.9", mailLabels),
|
||||
pod("web", "w2", "10.44.0.10", map[string]string{"app": "web"}), // not a member (A4)
|
||||
}
|
||||
d := ComputeEgress("w2", nodes, svcs, []*EgressService{e}, pods, nil)
|
||||
if len(d.Members) != 1 || d.Members[0].Pod != "gitea/mail-0" {
|
||||
t.Fatalf("members: %+v", d.Members)
|
||||
}
|
||||
m := d.Members[0]
|
||||
if m.Unreachable || !m.Address.Equal(net.ParseIP("86.107.168.15")) || m.From != 62024 || m.To != 62535 || m.Holder.Name != "w1" {
|
||||
t.Fatalf("member path: %+v", m)
|
||||
}
|
||||
if !d.Tunnelled("w2") || len(d.Peers("w2")) != 1 || d.Peers("w2")[0].Name != "w1" {
|
||||
t.Fatalf("w2 tunnels to w1 only: %+v", d.Peers("w2"))
|
||||
}
|
||||
if len(d.Status) != 0 || len(d.Routes) != 0 {
|
||||
t.Fatalf("only the holder writes and routes: %+v", d)
|
||||
}
|
||||
// the holder: blocks of the other nodes route back to them; its own block is plain conntrack
|
||||
h := ComputeEgress("w1", nodes, svcs, []*EgressService{e}, nil, nil)
|
||||
if len(h.Status) != 0 {
|
||||
t.Fatalf("status already right: %+v", h.Status)
|
||||
}
|
||||
if len(h.Routes) != 2 || h.Routes[0].Peer.Name != "cp" || h.Routes[1].Peer.Name != "w2" || h.Routes[1].From != 62024 {
|
||||
t.Fatalf("holder routes: %+v", h.Routes)
|
||||
}
|
||||
// a member on the holder leaves from there
|
||||
hm := ComputeEgress("w1", nodes, svcs, []*EgressService{e}, []*coreapi.Pod{pod("mail-1", "w1", "10.40.0.4", mailLabels)}, nil)
|
||||
if len(hm.Members) != 1 || hm.Members[0].Holder.Name != "w1" || hm.Members[0].From != 61512 {
|
||||
t.Fatalf("member on the holder: %+v", hm.Members)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEgressFailsClosed(t *testing.T) {
|
||||
pods := []*coreapi.Pod{pod("mail-0", "w2", "10.44.0.9", mailLabels)}
|
||||
// no node holds the address: status cleared (by the first node by name), member dropped
|
||||
unheld := svc("mail", "86.107.168.99", func(s *coreapi.Service) { s.Spec.Selector = mailLabels })
|
||||
for _, self := range []string{"cp", "w2"} {
|
||||
d := ComputeEgress(self, nodes, []*coreapi.Service{unheld}, []*EgressService{egressFor("mail", written())}, pods, nil)
|
||||
clears := len(d.Status) == 1 && d.Status[0].Status.Host == ""
|
||||
if clears != (self == "cp") {
|
||||
t.Fatalf("%s clears=%v: %+v", self, clears, d.Status)
|
||||
}
|
||||
if self == "w2" && (len(d.Members) != 1 || !d.Members[0].Unreachable) {
|
||||
t.Fatalf("member of an unheld address: %+v", d.Members)
|
||||
}
|
||||
}
|
||||
// two EgressServices select the pod: dropped, reported
|
||||
other := svc("mail2", "86.107.168.15", func(s *coreapi.Service) { s.Spec.Selector = mailLabels })
|
||||
d := ComputeEgress("w2", nodes, []*coreapi.Service{mailSvc(), other},
|
||||
[]*EgressService{egressFor("mail", written()), egressFor("mail2", written())}, pods, nil)
|
||||
if len(d.Members) != 1 || !d.Members[0].Unreachable || !strings.Contains(strings.Join(d.Problems, "\n"), "several EgressServices") {
|
||||
t.Fatalf("double member: %+v", d)
|
||||
}
|
||||
// a spec this agent does not implement is refused, not guessed at
|
||||
bad := egressFor("mail", written())
|
||||
bad.Spec.Emit = "Direct"
|
||||
d = ComputeEgress("w2", nodes, []*coreapi.Service{mailSvc()}, []*EgressService{bad}, pods, nil)
|
||||
if len(d.Members) != 1 || !d.Members[0].Unreachable {
|
||||
t.Fatalf("unknown emit mode: %+v", d)
|
||||
}
|
||||
// hostNetwork pods are never members (their traffic is the node's)
|
||||
hn := pod("mail-h", "w2", "86.107.168.88", mailLabels, func(p *coreapi.Pod) { p.Spec.HostNetwork = true })
|
||||
d = ComputeEgress("w2", nodes, []*coreapi.Service{mailSvc()}, []*EgressService{egressFor("mail", written())}, []*coreapi.Pod{hn}, nil)
|
||||
if len(d.Members) != 0 {
|
||||
t.Fatalf("hostNetwork pod became a member: %+v", d.Members)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmissionDecidesWhoOwnsAnIP(t *testing.T) {
|
||||
e := egressFor("mail", written())
|
||||
svcs := []*coreapi.Service{mailSvc()}
|
||||
old := pod("mail-0", "w2", "10.44.0.9", mailLabels, func(p *coreapi.Pod) {
|
||||
now := metav1.Now()
|
||||
p.DeletionTimestamp = &now
|
||||
})
|
||||
newer := pod("web", "w2", "", map[string]string{"app": "web"}) // API has no IP for it yet
|
||||
// the CNI plugin just gave 10.44.0.9 to web: the terminating member must not keep it (A4)
|
||||
d := ComputeEgress("w2", nodes, svcs, []*EgressService{e}, []*coreapi.Pod{old, newer},
|
||||
map[string]Admission{"10.44.0.9": {Namespace: "gitea", Name: "web"}})
|
||||
if len(d.Members) != 0 {
|
||||
t.Fatalf("reused IP kept the old member's rules: %+v", d.Members)
|
||||
}
|
||||
// and the other way round: a member admitted before the API shows its IP is protected at once (A1)
|
||||
fresh := pod("mail-1", "w2", "", mailLabels)
|
||||
d = ComputeEgress("w2", nodes, svcs, []*EgressService{e}, []*coreapi.Pod{fresh},
|
||||
map[string]Admission{"10.44.0.11": {Namespace: "gitea", Name: "mail-1"}})
|
||||
if len(d.Members) != 1 || d.Members[0].IP.String() != "10.44.0.11" || d.Members[0].Unreachable {
|
||||
t.Fatalf("admitted member: %+v", d.Members)
|
||||
}
|
||||
// without an admission, a live pod wins a shared IP over a terminating one
|
||||
live := pod("web", "w2", "10.44.0.9", map[string]string{"app": "web"})
|
||||
d = ComputeEgress("w2", nodes, svcs, []*EgressService{e}, []*coreapi.Pod{old, live}, nil)
|
||||
if len(d.Members) != 0 {
|
||||
t.Fatalf("terminating member won the IP: %+v", d.Members)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderedEgressRules(t *testing.T) {
|
||||
_, podNet, _ := net.ParseCIDR("10.32.0.0/12")
|
||||
e := egressFor("mail", written())
|
||||
svcs := []*coreapi.Service{mailSvc()}
|
||||
toW1, toW2, toCP := TunnelName("w1"), TunnelName("w2"), TunnelName("cp")
|
||||
member := ComputeEgress("w2", nodes, svcs, []*EgressService{e}, []*coreapi.Pod{pod("mail-0", "w2", "10.44.0.9", mailLabels)}, nil)
|
||||
r := Render(Desired{}, member, "w2", podNet, nil)
|
||||
for _, want := range []string{
|
||||
"-A REWEAVE-LB-CLASS -d 10.32.0.0/12 -j RETURN\n",
|
||||
"-A REWEAVE-LB-CLASS -d 86.107.168.24/32 -j RETURN\n", // a node is not outside
|
||||
"-A REWEAVE-LB-CLASS -s 10.44.0.9/32 -m comment --comment \"gitea/mail-0\" -j CONNMARK --set-xmark 0x100000/0x100000\n",
|
||||
"-A REWEAVE-LB-CLASS -s 10.44.0.9/32 -j MARK --set-xmark 0x100000/0x100000\n",
|
||||
// port protocols within this node's block, into the holder's own tunnel
|
||||
"-A REWEAVE-LB-POST -s 10.44.0.9/32 -o " + toW1 + " -m mark --mark 0x100000/0x100000 -p tcp -j SNAT --to-source 86.107.168.15:62024-62535\n",
|
||||
"-A REWEAVE-LB-POST -s 10.44.0.9/32 -o " + toW1 + " -m mark --mark 0x100000/0x100000 -p udp -j SNAT --to-source 86.107.168.15:62024-62535\n",
|
||||
"-A REWEAVE-LB-POST -s 10.44.0.9/32 -o " + toW1 + " -m mark --mark 0x100000/0x100000 -p sctp -j SNAT --to-source 86.107.168.15:62024-62535\n",
|
||||
// ping within the block too (the echo identifier is NAT's port for ICMP)
|
||||
"-A REWEAVE-LB-POST -s 10.44.0.9/32 -o " + toW1 + " -m mark --mark 0x100000/0x100000 -p icmp -j SNAT --to-source 86.107.168.15:62024-62535\n",
|
||||
// everything else (GRE, ...) from the address alone
|
||||
"-A REWEAVE-LB-POST -s 10.44.0.9/32 -o " + toW1 + " -m mark --mark 0x100000/0x100000 -j SNAT --to-source 86.107.168.15\n",
|
||||
} {
|
||||
if !strings.Contains(r.Nat, want) {
|
||||
t.Fatalf("member nat lacks %q:\n%s", want, r.Nat)
|
||||
}
|
||||
}
|
||||
// the catch-all SNAT comes after the port-ranged ones (or ports would not stay in the block)
|
||||
if strings.Index(r.Nat, "-j SNAT --to-source 86.107.168.15\n") < strings.Index(r.Nat, "-p icmp -j SNAT") {
|
||||
t.Fatalf("SNAT order:\n%s", r.Nat)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"-A REWEAVE-LB-FWD -s 10.44.0.9/32 -o " + toW1 + " -m mark --mark 0x100000/0x100000 -j RETURN\n",
|
||||
"-A REWEAVE-LB-FWD -s 10.44.0.9/32 -m mark --mark 0x100000/0x100000 -m comment --comment \"gitea/mail-0\" -j DROP\n",
|
||||
"-A REWEAVE-LB-FWD -i rweg+ -d 86.107.168.15/32 -j DROP\n",
|
||||
"-A REWEAVE-LB-FWD -i rweg+ -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN\n",
|
||||
"-A REWEAVE-LB-FWD -i rweg+ -j DROP\n",
|
||||
"-A REWEAVE-LB-IN -p 4 -s 10.40.0.0/32 -m mac --mac-source aa:aa:aa:aa:aa:02 -j RETURN\n",
|
||||
"-A REWEAVE-LB-IN -p 4 -j DROP\n",
|
||||
} {
|
||||
if !strings.Contains(r.Filter, want) {
|
||||
t.Fatalf("member filter lacks %q:\n%s", want, r.Filter)
|
||||
}
|
||||
}
|
||||
for _, want := range []string{
|
||||
"-A REWEAVE-LB-MARK -m conntrack --ctdir ORIGINAL -m connmark --mark 0x100000/0x100000 -j MARK --set-xmark 0x100000/0x100000\n",
|
||||
"-A REWEAVE-LB-MSS -o rweg+ -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu\n",
|
||||
} {
|
||||
if !strings.Contains(r.Mangle, want) {
|
||||
t.Fatalf("member mangle lacks %q:\n%s", want, r.Mangle)
|
||||
}
|
||||
}
|
||||
if strings.Contains(r.Mangle, "--set-xmark 0x") && strings.Contains(r.Mangle, "-i "+toW1) {
|
||||
t.Fatalf("a member's node does not tag what its holder sends back:\n%s", r.Mangle)
|
||||
}
|
||||
// the guard's DROP comes after the member's own RETURN: nothing else of its leaves
|
||||
if strings.Index(r.Filter, toW1+" -m mark --mark 0x100000/0x100000 -j RETURN") > strings.Index(r.Filter, "\"gitea/mail-0\" -j DROP") {
|
||||
t.Fatalf("guard order:\n%s", r.Filter)
|
||||
}
|
||||
|
||||
// the holder: tags each tunnel's connections with its node, accepts each node's block, nothing foreign
|
||||
h := ComputeEgress("w1", nodes, svcs, []*EgressService{e}, []*coreapi.Pod{pod("mail-1", "w1", "10.40.0.4", mailLabels)}, nil)
|
||||
r = Render(Desired{}, h, "w1", podNet, nil)
|
||||
idx := peerIndex(h, "w1")
|
||||
tagW2 := fmt.Sprintf("0x%x", peerTag(idx["w2"]))
|
||||
for _, want := range []string{
|
||||
"-A REWEAVE-LB-MARK -m conntrack --ctdir REPLY -m connmark --mark 0x200000/0x200000 -j CONNMARK --restore-mark --nfmask 0xff200000 --ctmask 0xff200000\n",
|
||||
"-A REWEAVE-LB-MARK -i " + toW2 + " -m conntrack --ctstate NEW -m connmark --mark 0x0/0xff200000 -m comment --comment \"from w2\" -j CONNMARK --set-xmark " + tagW2 + "/0xff200000\n",
|
||||
} {
|
||||
if !strings.Contains(r.Mangle, want) {
|
||||
t.Fatalf("holder mangle lacks %q:\n%s", want, r.Mangle)
|
||||
}
|
||||
}
|
||||
for _, want := range []string{
|
||||
"-A REWEAVE-LB-FWD -i " + toW2 + " -m conntrack --ctdir ORIGINAL -m connmark ! --mark " + tagW2 + "/0xff200000 -j DROP\n",
|
||||
"-A REWEAVE-LB-FWD -i " + toW2 + " -m conntrack --ctdir ORIGINAL -s 86.107.168.15/32 -p tcp -m tcp --sport 62024:62535 -m comment --comment \"block of w2\" -j RETURN\n",
|
||||
"-A REWEAVE-LB-FWD -i " + toCP + " -m conntrack --ctdir ORIGINAL -s 86.107.168.15/32 -p sctp -m sctp --sport 61000:61511 -m comment --comment \"block of cp\" -j RETURN\n",
|
||||
"-A REWEAVE-LB-FWD -i " + toW2 + " -m conntrack --ctdir ORIGINAL -s 86.107.168.15/32 -p udp -j DROP\n",
|
||||
"-A REWEAVE-LB-FWD -i " + toW2 + " -m conntrack --ctdir ORIGINAL -s 86.107.168.15/32 -j RETURN\n",
|
||||
"-A REWEAVE-LB-FWD -s 10.40.0.4/32 -o weave -m mark --mark 0x100000/0x100000 -j DROP\n",
|
||||
} {
|
||||
if !strings.Contains(r.Filter, want) {
|
||||
t.Fatalf("holder filter lacks %q:\n%s", want, r.Filter)
|
||||
}
|
||||
}
|
||||
// foreign tags are refused before anything of that tunnel is accepted; a port outside the block before the
|
||||
// catch-all accept
|
||||
if strings.Index(r.Filter, "! --mark "+tagW2) > strings.Index(r.Filter, "\"block of w2\"") ||
|
||||
strings.Index(r.Filter, "-i "+toW2+" -m conntrack --ctdir ORIGINAL -s 86.107.168.15/32 -p udp -j DROP") >
|
||||
strings.Index(r.Filter, "-i "+toW2+" -m conntrack --ctdir ORIGINAL -s 86.107.168.15/32 -j RETURN") {
|
||||
t.Fatalf("holder filter order:\n%s", r.Filter)
|
||||
}
|
||||
if !strings.Contains(r.Nat, "-A REWEAVE-LB-POST -s 10.40.0.4/32 ! -o weave -m mark --mark 0x100000/0x100000 -p tcp -j SNAT --to-source 86.107.168.15:61512-62023\n") {
|
||||
t.Fatalf("holder's own member:\n%s", r.Nat)
|
||||
}
|
||||
for _, line := range strings.Split(r.Nat+r.Filter, "\n") {
|
||||
if strings.Contains(line, "10.44.0.10") {
|
||||
t.Fatalf("a rule names a non-member: %s", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTunnelNames(t *testing.T) {
|
||||
a, b := TunnelName("reweave-worker-fe19b040"), TunnelName("reweave-cp-94a8db43")
|
||||
if a == b || len(a) > 15 || !strings.HasPrefix(a, TunnelPrefix) || a != TunnelName("reweave-worker-fe19b040") {
|
||||
t.Fatalf("tunnel names must be distinct, stable, short: %q %q", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyRules(t *testing.T) {
|
||||
e := egressFor("mail", written())
|
||||
svcs := []*coreapi.Service{mailSvc()}
|
||||
h := ComputeEgress("w1", nodes, svcs, []*EgressService{e}, nil, nil)
|
||||
rules := wantRules(h, "w1")
|
||||
tables := peerTables(h, "w1")
|
||||
idx := peerIndex(h, "w1")
|
||||
ports, tags := 0, 0
|
||||
for _, r := range rules {
|
||||
if r.priority != HolderRulePriority || r.src != "" {
|
||||
t.Fatalf("holder rule: %+v", r)
|
||||
}
|
||||
switch {
|
||||
case r.ipproto != 0:
|
||||
ports++
|
||||
if r.dst != "86.107.168.15/32" || r.mark != 0 {
|
||||
t.Fatalf("port rule: %+v", r)
|
||||
}
|
||||
if (r.from == 62024) != (r.table == tables["w2"]) || (r.from == 61000) != (r.table == tables["cp"]) {
|
||||
t.Fatalf("block routed to the wrong node: %+v %v", r, tables)
|
||||
}
|
||||
default:
|
||||
tags++
|
||||
if r.mask != peerMask || r.dst != "" {
|
||||
t.Fatalf("tag rule: %+v", r)
|
||||
}
|
||||
for name, i := range idx {
|
||||
if r.mark == peerTag(i) && r.table != tables[name] {
|
||||
t.Fatalf("tag of %s routed to table %d, want %d", name, r.table, tables[name])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ports != 2*len(portProtocols) || tags != 2 {
|
||||
t.Fatalf("2 peers: %d port rules (tcp, udp, sctp each), %d tag rules: %+v", ports, tags, rules)
|
||||
}
|
||||
if HolderRulePriority >= LocalRulePriority {
|
||||
t.Fatalf("holder rules must precede the local table")
|
||||
}
|
||||
m := ComputeEgress("w2", nodes, svcs, []*EgressService{e}, []*coreapi.Pod{pod("mail-0", "w2", "10.44.0.9", mailLabels)}, nil)
|
||||
rules = wantRules(m, "w2")
|
||||
if len(rules) != 1 || rules[0].src != "10.44.0.9/32" || rules[0].mark != EgressMark || rules[0].mask != EgressMark ||
|
||||
rules[0].table != peerTables(m, "w2")["w1"] {
|
||||
t.Fatalf("member rule: %+v", rules)
|
||||
}
|
||||
u := ComputeEgress("w2", nodes, svcs, []*EgressService{egressFor("mail", EgressServiceStatus{})}, []*coreapi.Pod{pod("mail-0", "w2", "10.44.0.9", mailLabels)}, nil)
|
||||
rules = wantRules(u, "w2")
|
||||
if len(rules) != 1 || rules[0].table != EgressTableBase {
|
||||
t.Fatalf("unreachable member must hit the unreachable table: %+v", rules)
|
||||
}
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
// Package lb gives LoadBalancer Services an address that a node holds, and
|
||||
// forwards that address's traffic to the Service's endpoints with the
|
||||
// client's address intact.
|
||||
//
|
||||
// A Service opts in with spec.loadBalancerClass = Class and names its
|
||||
// address in spec.loadBalancerIP. The node that holds the address (has it on
|
||||
// one of its interfaces, as published in its AnnAddresses annotation) is the
|
||||
// address's holder: it alone writes the Service's status (ipMode Proxy, so
|
||||
// kube-proxy installs no rules for the address) and DNATs the address's
|
||||
// ports to ready endpoints, without SNAT. Every other node marks the
|
||||
// connections that the holder forwards to its pods, and routes their replies
|
||||
// back to the holder over weave, so that the holder's conntrack can undo the
|
||||
// DNAT and the reply leaves from the address the client connected to.
|
||||
//
|
||||
// Compute is the whole policy as a pure function of what the API shows; the
|
||||
// datapath and the controller only carry it out.
|
||||
package lb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
discovery "k8s.io/api/discovery/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// Class is the spec.loadBalancerClass a Service sets to be served by this agent.
|
||||
Class = "reweave.levk.dev/node-address"
|
||||
// AnnAddresses lists the IPv4 addresses a node holds on its host interfaces (comma-separated).
|
||||
AnnAddresses = "reweave.levk.dev/addresses"
|
||||
// AnnWeave is "<ip>,<mac>" of the node's weave bridge: where replies to its forwards go.
|
||||
AnnWeave = "reweave.levk.dev/weave"
|
||||
|
||||
serviceNameLabel = "kubernetes.io/service-name"
|
||||
)
|
||||
|
||||
// Node is what one node publishes about itself.
|
||||
type Node struct {
|
||||
Name string
|
||||
Addresses []net.IP
|
||||
WeaveIP net.IP
|
||||
WeaveMAC net.HardwareAddr
|
||||
}
|
||||
|
||||
// Holds reports whether the node has ip on one of its host interfaces.
|
||||
func (n Node) Holds(ip net.IP) bool {
|
||||
for _, a := range n.Addresses {
|
||||
if a.Equal(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParseNode reads a Node's annotations. A node that has published nothing
|
||||
// (yet) holds nothing and cannot take replies.
|
||||
func ParseNode(n *coreapi.Node) (Node, error) {
|
||||
out := Node{Name: n.Name}
|
||||
if s := n.Annotations[AnnAddresses]; s != "" {
|
||||
for _, f := range strings.Split(s, ",") {
|
||||
ip := net.ParseIP(strings.TrimSpace(f)).To4()
|
||||
if ip == nil {
|
||||
return out, fmt.Errorf("node %s: %s: %q is not an IPv4 address", n.Name, AnnAddresses, f)
|
||||
}
|
||||
out.Addresses = append(out.Addresses, ip)
|
||||
}
|
||||
}
|
||||
if s := n.Annotations[AnnWeave]; s != "" {
|
||||
f := strings.Split(s, ",")
|
||||
if len(f) != 2 {
|
||||
return out, fmt.Errorf("node %s: %s: want \"<ip>,<mac>\", got %q", n.Name, AnnWeave, s)
|
||||
}
|
||||
ip := net.ParseIP(f[0]).To4()
|
||||
mac, err := net.ParseMAC(f[1])
|
||||
if ip == nil || err != nil {
|
||||
return out, fmt.Errorf("node %s: %s: want \"<ip>,<mac>\", got %q", n.Name, AnnWeave, s)
|
||||
}
|
||||
out.WeaveIP, out.WeaveMAC = ip, mac
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// FormatAddresses is the AnnAddresses value for a set of addresses (sorted, so it only changes when the set does).
|
||||
func FormatAddresses(ips []net.IP) string {
|
||||
s := make([]string, 0, len(ips))
|
||||
for _, ip := range ips {
|
||||
s = append(s, ip.String())
|
||||
}
|
||||
sort.Strings(s)
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
// Endpoint is one ready backend of one Service port.
|
||||
type Endpoint struct {
|
||||
IP net.IP
|
||||
Port int32
|
||||
}
|
||||
|
||||
func (e Endpoint) String() string { return fmt.Sprintf("%s:%d", e.IP, e.Port) }
|
||||
|
||||
// Port is one Service port at the held address.
|
||||
type Port struct {
|
||||
Protocol coreapi.Protocol
|
||||
Port int32
|
||||
Endpoints []Endpoint
|
||||
}
|
||||
|
||||
// Forward is a Service whose address this node holds.
|
||||
type Forward struct {
|
||||
Namespace, Name string
|
||||
Address net.IP
|
||||
Ports []Port
|
||||
}
|
||||
|
||||
// Status is a write to a Service's status.loadBalancer: Address set, or cleared when nil.
|
||||
type Status struct {
|
||||
Namespace, Name string
|
||||
Address net.IP
|
||||
}
|
||||
|
||||
// Holder is another node that forwards held addresses' connections to pods; Mark identifies its connections here.
|
||||
type Holder struct {
|
||||
Node Node
|
||||
Mark uint32 // 1..MaxMarks, in the connmark's top byte
|
||||
}
|
||||
|
||||
// MaxMarks is how many holders a node can tell apart (the top byte of the mark, 0 meaning none).
|
||||
const MaxMarks = 250
|
||||
|
||||
// Desired is what one node must do.
|
||||
type Desired struct {
|
||||
Forwards []Forward
|
||||
Holders []Holder
|
||||
Status []Status
|
||||
Problems []string // what an operator should hear about (logged and evented by the controller)
|
||||
}
|
||||
|
||||
// eligible reports whether the Service asks for this agent.
|
||||
func eligible(s *coreapi.Service) bool {
|
||||
return s.Spec.Type == coreapi.ServiceTypeLoadBalancer && s.Spec.LoadBalancerClass != nil &&
|
||||
*s.Spec.LoadBalancerClass == Class
|
||||
}
|
||||
|
||||
// shown is the address status.loadBalancer currently shows (nil if none or not a single IP).
|
||||
func shown(s *coreapi.Service) net.IP {
|
||||
if len(s.Status.LoadBalancer.Ingress) != 1 {
|
||||
return nil
|
||||
}
|
||||
return net.ParseIP(s.Status.LoadBalancer.Ingress[0].IP).To4()
|
||||
}
|
||||
|
||||
func hasStatus(s *coreapi.Service) bool { return len(s.Status.LoadBalancer.Ingress) != 0 }
|
||||
|
||||
// Compute decides what node `self` does, given every node, every Service and every EndpointSlice.
|
||||
func Compute(self string, nodes []Node, services []*coreapi.Service, slices []*discovery.EndpointSlice) Desired {
|
||||
var d Desired
|
||||
sortedNodes := append([]Node(nil), nodes...)
|
||||
sort.Slice(sortedNodes, func(i, j int) bool { return sortedNodes[i].Name < sortedNodes[j].Name })
|
||||
holdersOf := func(ip net.IP) []Node {
|
||||
var hs []Node
|
||||
for _, n := range sortedNodes {
|
||||
if ip != nil && n.Holds(ip) {
|
||||
hs = append(hs, n)
|
||||
}
|
||||
}
|
||||
return hs
|
||||
}
|
||||
// who may write a status that shows `ip`: its single holder, or (nobody holds it) the first node by name
|
||||
writerFor := func(ip net.IP) string {
|
||||
if hs := holdersOf(ip); len(hs) == 1 {
|
||||
return hs[0].Name
|
||||
}
|
||||
if len(sortedNodes) > 0 {
|
||||
return sortedNodes[0].Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
holding := map[string]bool{} // nodes that forward some address
|
||||
|
||||
svcs := append([]*coreapi.Service(nil), services...)
|
||||
sort.Slice(svcs, func(i, j int) bool { return key(svcs[i]) < key(svcs[j]) })
|
||||
for _, s := range svcs {
|
||||
if !eligible(s) {
|
||||
// loadBalancerClass is immutable and only allowed on type LoadBalancer, so a
|
||||
// Service never leaves the class while keeping a status we wrote
|
||||
continue
|
||||
}
|
||||
want := net.ParseIP(s.Spec.LoadBalancerIP).To4()
|
||||
var holder *Node
|
||||
switch hs := holdersOf(want); {
|
||||
case want == nil:
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("%s: spec.loadBalancerIP must name an IPv4 address a node holds (got %q)", key(s), s.Spec.LoadBalancerIP))
|
||||
case len(hs) == 0:
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("%s: no node holds %s", key(s), want))
|
||||
case len(hs) > 1:
|
||||
names := make([]string, len(hs))
|
||||
for i, h := range hs {
|
||||
names[i] = h.Name
|
||||
}
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("%s: %s is held by several nodes (%s); serving it from none", key(s), want, strings.Join(names, ", ")))
|
||||
default:
|
||||
holder = &hs[0]
|
||||
}
|
||||
if holder == nil {
|
||||
// fail closed: nobody forwards, and the status must not claim an address
|
||||
if hasStatus(s) && writerFor(shown(s)) == self {
|
||||
d.Status = append(d.Status, Status{Namespace: s.Namespace, Name: s.Name})
|
||||
}
|
||||
continue
|
||||
}
|
||||
holding[holder.Name] = true
|
||||
if holder.Name != self {
|
||||
continue
|
||||
}
|
||||
if cur := shown(s); cur == nil || !cur.Equal(want) {
|
||||
d.Status = append(d.Status, Status{Namespace: s.Namespace, Name: s.Name, Address: want})
|
||||
}
|
||||
d.Forwards = append(d.Forwards, Forward{Namespace: s.Namespace, Name: s.Name, Address: want, Ports: ports(s, slices)})
|
||||
}
|
||||
|
||||
var others []Node
|
||||
for _, n := range sortedNodes {
|
||||
if holding[n.Name] && n.Name != self {
|
||||
if n.WeaveIP == nil {
|
||||
d.Problems = append(d.Problems, fmt.Sprintf("node %s holds an address but has not published its weave bridge; replies to its forwards cannot reach it", n.Name))
|
||||
continue
|
||||
}
|
||||
others = append(others, n)
|
||||
}
|
||||
}
|
||||
d.Holders = assignMarks(others)
|
||||
return d
|
||||
}
|
||||
|
||||
// assignMarks gives each holder a mark from its name, probing past collisions in name order: stable while the set is.
|
||||
func assignMarks(nodes []Node) []Holder {
|
||||
taken := map[uint32]bool{}
|
||||
out := make([]Holder, 0, len(nodes))
|
||||
for _, n := range nodes {
|
||||
if len(out) == MaxMarks {
|
||||
break
|
||||
}
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(n.Name))
|
||||
m := h.Sum32()%MaxMarks + 1
|
||||
for taken[m] {
|
||||
m = m%MaxMarks + 1
|
||||
}
|
||||
taken[m] = true
|
||||
out = append(out, Holder{Node: n, Mark: m})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func key(s *coreapi.Service) string { return s.Namespace + "/" + s.Name }
|
||||
|
||||
// ports pairs each Service port with its ready endpoints.
|
||||
func ports(s *coreapi.Service, slices []*discovery.EndpointSlice) []Port {
|
||||
var out []Port
|
||||
for _, sp := range s.Spec.Ports {
|
||||
proto := sp.Protocol
|
||||
if proto == "" {
|
||||
proto = coreapi.ProtocolTCP
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
var eps []Endpoint
|
||||
for _, sl := range slices {
|
||||
if sl.Namespace != s.Namespace || sl.Labels[serviceNameLabel] != s.Name || sl.AddressType != discovery.AddressTypeIPv4 {
|
||||
continue
|
||||
}
|
||||
for _, p := range sl.Ports {
|
||||
pname, pproto := "", coreapi.ProtocolTCP
|
||||
if p.Name != nil {
|
||||
pname = *p.Name
|
||||
}
|
||||
if p.Protocol != nil {
|
||||
pproto = *p.Protocol
|
||||
}
|
||||
if pname != sp.Name || pproto != proto || p.Port == nil {
|
||||
continue
|
||||
}
|
||||
for _, ep := range sl.Endpoints {
|
||||
if ep.Conditions.Ready != nil && !*ep.Conditions.Ready {
|
||||
continue
|
||||
}
|
||||
for _, a := range ep.Addresses {
|
||||
ip := net.ParseIP(a).To4()
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
e := Endpoint{IP: ip, Port: *p.Port}
|
||||
if !seen[e.String()] {
|
||||
seen[e.String()] = true
|
||||
eps = append(eps, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(eps, func(i, j int) bool { return eps[i].String() < eps[j].String() })
|
||||
out = append(out, Port{Protocol: proto, Port: sp.Port, Endpoints: eps})
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
discovery "k8s.io/api/discovery/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func mac(s string) net.HardwareAddr {
|
||||
m, err := net.ParseMAC(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
var (
|
||||
cp = Node{Name: "cp", Addresses: []net.IP{net.ParseIP("86.107.168.24").To4()},
|
||||
WeaveIP: net.ParseIP("10.32.0.1").To4(), WeaveMAC: mac("aa:aa:aa:aa:aa:01")}
|
||||
w1 = Node{Name: "w1", Addresses: []net.IP{net.ParseIP("86.107.168.15").To4()},
|
||||
WeaveIP: net.ParseIP("10.40.0.0").To4(), WeaveMAC: mac("aa:aa:aa:aa:aa:02")}
|
||||
w2 = Node{Name: "w2", Addresses: []net.IP{net.ParseIP("86.107.168.88").To4()},
|
||||
WeaveIP: net.ParseIP("10.44.0.0").To4(), WeaveMAC: mac("aa:aa:aa:aa:aa:03")}
|
||||
nodes = []Node{w2, cp, w1}
|
||||
)
|
||||
|
||||
func svc(name, ip string, mutate ...func(*coreapi.Service)) *coreapi.Service {
|
||||
class := Class
|
||||
s := &coreapi.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "gitea", Name: name},
|
||||
Spec: coreapi.ServiceSpec{
|
||||
Type: coreapi.ServiceTypeLoadBalancer,
|
||||
LoadBalancerClass: &class,
|
||||
LoadBalancerIP: ip,
|
||||
Ports: []coreapi.ServicePort{
|
||||
{Name: "ssh", Protocol: coreapi.ProtocolTCP, Port: 22},
|
||||
{Name: "https", Port: 443}, // protocol defaults to TCP
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, m := range mutate {
|
||||
m(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func shows(ip string) func(*coreapi.Service) {
|
||||
return func(s *coreapi.Service) {
|
||||
s.Status.LoadBalancer.Ingress = []coreapi.LoadBalancerIngress{{IP: ip}}
|
||||
}
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func slice(svcName string, eps map[string]bool, ports map[string]int32) *discovery.EndpointSlice {
|
||||
sl := &discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "gitea", Name: svcName + "-x", Labels: map[string]string{serviceNameLabel: svcName}},
|
||||
AddressType: discovery.AddressTypeIPv4,
|
||||
}
|
||||
for ip, ready := range eps {
|
||||
sl.Endpoints = append(sl.Endpoints, discovery.Endpoint{Addresses: []string{ip}, Conditions: discovery.EndpointConditions{Ready: ptr(ready)}})
|
||||
}
|
||||
for name, port := range ports {
|
||||
sl.Ports = append(sl.Ports, discovery.EndpointPort{Name: ptr(name), Protocol: ptr(coreapi.ProtocolTCP), Port: ptr(port)})
|
||||
}
|
||||
return sl
|
||||
}
|
||||
|
||||
func TestHolderForwardsToReadyEndpointsAndWritesStatus(t *testing.T) {
|
||||
s := svc("gitea", "86.107.168.15")
|
||||
sl := slice("gitea", map[string]bool{"10.40.0.7": true, "10.44.0.3": true, "10.44.0.9": false},
|
||||
map[string]int32{"ssh": 2222, "https": 3000})
|
||||
d := Compute("w1", nodes, []*coreapi.Service{s}, []*discovery.EndpointSlice{sl})
|
||||
if len(d.Problems) != 0 {
|
||||
t.Fatalf("problems: %v", d.Problems)
|
||||
}
|
||||
if len(d.Forwards) != 1 || !d.Forwards[0].Address.Equal(net.ParseIP("86.107.168.15")) {
|
||||
t.Fatalf("forwards: %+v", d.Forwards)
|
||||
}
|
||||
f := d.Forwards[0]
|
||||
if len(f.Ports) != 2 || f.Ports[0].Port != 22 || f.Ports[1].Port != 443 || f.Ports[1].Protocol != coreapi.ProtocolTCP {
|
||||
t.Fatalf("ports: %+v", f.Ports)
|
||||
}
|
||||
if got := f.Ports[0].Endpoints; len(got) != 2 || got[0].String() != "10.40.0.7:2222" || got[1].String() != "10.44.0.3:2222" {
|
||||
t.Fatalf("ssh endpoints (not-ready one must be left out): %v", got)
|
||||
}
|
||||
if got := f.Ports[1].Endpoints; len(got) != 2 || got[0].Port != 3000 {
|
||||
t.Fatalf("https endpoints: %v", got)
|
||||
}
|
||||
if len(d.Status) != 1 || !d.Status[0].Address.Equal(net.ParseIP("86.107.168.15")) {
|
||||
t.Fatalf("status: %+v", d.Status)
|
||||
}
|
||||
if len(d.Holders) != 0 {
|
||||
t.Fatalf("a holder does not steer its own replies: %+v", d.Holders)
|
||||
}
|
||||
// already shown: nothing to write
|
||||
d = Compute("w1", nodes, []*coreapi.Service{svc("gitea", "86.107.168.15", shows("86.107.168.15"))}, nil)
|
||||
if len(d.Status) != 0 {
|
||||
t.Fatalf("status rewritten: %+v", d.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOtherNodesSteerRepliesToTheHolderAndNeverWrite(t *testing.T) {
|
||||
s := svc("gitea", "86.107.168.15")
|
||||
for _, self := range []string{"cp", "w2"} {
|
||||
d := Compute(self, nodes, []*coreapi.Service{s}, nil)
|
||||
if len(d.Forwards) != 0 || len(d.Status) != 0 {
|
||||
t.Fatalf("%s: only the holder forwards and writes: %+v", self, d)
|
||||
}
|
||||
if len(d.Holders) != 1 || d.Holders[0].Node.Name != "w1" || d.Holders[0].Mark < 1 || d.Holders[0].Mark > MaxMarks {
|
||||
t.Fatalf("%s: holders: %+v", self, d.Holders)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnheldOrContestedAddressFailsClosed(t *testing.T) {
|
||||
// nobody holds it: no forward anywhere; the stale status is cleared by exactly one node (first by name)
|
||||
s := svc("gitea", "86.107.168.99", shows("86.107.168.99"))
|
||||
for _, self := range []string{"cp", "w1", "w2"} {
|
||||
d := Compute(self, nodes, []*coreapi.Service{s}, nil)
|
||||
if len(d.Forwards) != 0 || len(d.Holders) != 0 || len(d.Problems) != 1 || !strings.Contains(d.Problems[0], "no node holds") {
|
||||
t.Fatalf("%s: %+v", self, d)
|
||||
}
|
||||
clears := len(d.Status) == 1 && d.Status[0].Address == nil
|
||||
if clears != (self == "cp") {
|
||||
t.Fatalf("%s: clears=%v", self, clears)
|
||||
}
|
||||
}
|
||||
// two nodes claim it: neither serves it, its holder's view clears the status
|
||||
both := w2
|
||||
both.Addresses = append([]net.IP{net.ParseIP("86.107.168.15").To4()}, w2.Addresses...)
|
||||
d := Compute("w1", []Node{cp, w1, both}, []*coreapi.Service{svc("gitea", "86.107.168.15", shows("86.107.168.15"))}, nil)
|
||||
if len(d.Forwards) != 0 || len(d.Problems) != 1 || !strings.Contains(d.Problems[0], "several nodes") {
|
||||
t.Fatalf("contested: %+v", d)
|
||||
}
|
||||
// a bad request is reported, not guessed at
|
||||
d = Compute("w1", nodes, []*coreapi.Service{svc("gitea", "")}, nil)
|
||||
if len(d.Forwards) != 0 || len(d.Problems) != 1 || !strings.Contains(d.Problems[0], "spec.loadBalancerIP") {
|
||||
t.Fatalf("no address: %+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnlyOptedInLoadBalancersAreServed(t *testing.T) {
|
||||
other := svc("a", "86.107.168.15", func(s *coreapi.Service) { s.Spec.LoadBalancerClass = ptr("example.com/other") })
|
||||
noClass := svc("b", "86.107.168.15", func(s *coreapi.Service) { s.Spec.LoadBalancerClass = nil })
|
||||
clusterIP := svc("c", "86.107.168.15", func(s *coreapi.Service) {
|
||||
s.Spec.Type = coreapi.ServiceTypeClusterIP
|
||||
})
|
||||
d := Compute("w1", nodes, []*coreapi.Service{other, noClass, clusterIP}, nil)
|
||||
if len(d.Forwards)+len(d.Status)+len(d.Holders)+len(d.Problems) != 0 {
|
||||
t.Fatalf("touched a Service that did not opt in: %+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNodeAndAnnotationsRoundTrip(t *testing.T) {
|
||||
l := Local{Addresses: []net.IP{net.ParseIP("86.107.168.88").To4(), net.ParseIP("86.107.168.15").To4()},
|
||||
WeaveIP: net.ParseIP("10.44.0.0").To4(), WeaveMAC: mac("aa:aa:aa:aa:aa:03")}
|
||||
a := l.Annotations()
|
||||
if a[AnnAddresses] != "86.107.168.15,86.107.168.88" || a[AnnWeave] != "10.44.0.0,aa:aa:aa:aa:aa:03" {
|
||||
t.Fatalf("annotations: %v", a)
|
||||
}
|
||||
n, err := ParseNode(&coreapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "w2", Annotations: a}})
|
||||
if err != nil || !n.Holds(net.ParseIP("86.107.168.15")) || !n.WeaveIP.Equal(l.WeaveIP) || n.WeaveMAC.String() != "aa:aa:aa:aa:aa:03" {
|
||||
t.Fatalf("parsed %+v, %v", n, err)
|
||||
}
|
||||
for _, bad := range []map[string]string{{AnnAddresses: "1.2.3.4,nope"}, {AnnWeave: "10.44.0.0"}, {AnnWeave: "x,aa:aa:aa:aa:aa:03"}} {
|
||||
if _, err := ParseNode(&coreapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "w2", Annotations: bad}}); err == nil {
|
||||
t.Fatalf("accepted %v", bad)
|
||||
}
|
||||
}
|
||||
if n, err := ParseNode(&coreapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "new"}}); err != nil || len(n.Addresses) != 0 || n.WeaveIP != nil {
|
||||
t.Fatalf("a node that published nothing holds nothing: %+v %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarksAreDistinctAndStable(t *testing.T) {
|
||||
var ns []Node
|
||||
for _, name := range []string{"a", "b", "c", "d", "e", "f", "g", "h"} {
|
||||
ns = append(ns, Node{Name: name})
|
||||
}
|
||||
first := assignMarks(ns)
|
||||
seen := map[uint32]bool{}
|
||||
for _, h := range first {
|
||||
if seen[h.Mark] || h.Mark < 1 || h.Mark > MaxMarks {
|
||||
t.Fatalf("bad or repeated mark: %+v", first)
|
||||
}
|
||||
seen[h.Mark] = true
|
||||
}
|
||||
again := assignMarks(ns)
|
||||
for i := range first {
|
||||
if first[i].Mark != again[i].Mark {
|
||||
t.Fatalf("marks moved between passes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderedRules(t *testing.T) {
|
||||
_, podNet, _ := net.ParseCIDR("10.32.0.0/12")
|
||||
s := svc("gitea", "86.107.168.15")
|
||||
sl := slice("gitea", map[string]bool{"10.40.0.7": true, "10.44.0.3": true, "10.44.0.4": true}, map[string]int32{"ssh": 2222, "https": 3000})
|
||||
d := Compute("w1", nodes, []*coreapi.Service{s}, []*discovery.EndpointSlice{sl})
|
||||
r := Render(d, EgressDesired{}, "w1", podNet, []string{"REWEAVE-LBS-DEADBEEF0000"})
|
||||
for _, want := range []string{
|
||||
"*nat\n:REWEAVE-LB - [0:0]\n:REWEAVE-LB-POST - [0:0]\n:REWEAVE-LB-CLASS - [0:0]\n",
|
||||
// the client's address survives weave's masquerade; a pod's own connection (hairpin) is masqueraded here
|
||||
"-A REWEAVE-LB-POST -m conntrack --ctstate DNAT --ctorigdst 86.107.168.15/32 -s 10.32.0.0/12 -j MASQUERADE\n",
|
||||
"-A REWEAVE-LB-POST -m conntrack --ctstate DNAT --ctorigdst 86.107.168.15/32 -j ACCEPT\n",
|
||||
":REWEAVE-LBS-DEADBEEF0000 - [0:0]\n", // stale chain emptied
|
||||
"-A REWEAVE-LB -d 86.107.168.15/32 -p tcp -m tcp --dport 22 -m comment --comment \"gitea/gitea\" -j REWEAVE-LBS-",
|
||||
"-p tcp -m statistic --mode random --probability 0.3333333333 -j DNAT --to-destination 10.40.0.7:2222\n",
|
||||
"-p tcp -m statistic --mode random --probability 0.5000000000 -j DNAT --to-destination 10.44.0.3:2222\n",
|
||||
"-p tcp -j DNAT --to-destination 10.44.0.4:2222\n",
|
||||
"COMMIT\n",
|
||||
} {
|
||||
if !strings.Contains(r.Nat, want) {
|
||||
t.Fatalf("nat lacks %q:\n%s", want, r.Nat)
|
||||
}
|
||||
}
|
||||
for _, line := range strings.Split(r.Nat, "\n") {
|
||||
// iptables refuses a DNAT to ip:port without the protocol in the same rule
|
||||
if strings.Contains(line, "-j DNAT") && !strings.Contains(line, " -p tcp ") {
|
||||
t.Fatalf("DNAT without its protocol: %q", line)
|
||||
}
|
||||
}
|
||||
if strings.Count(r.Nat, "MASQUERADE") != 1 || strings.Contains(r.Nat, "SNAT") {
|
||||
t.Fatalf("only a pod's hairpin connection may be masqueraded:\n%s", r.Nat)
|
||||
}
|
||||
if r.Mangle != "*mangle\n:REWEAVE-LB-MARK - [0:0]\n:REWEAVE-LB-MSS - [0:0]\nCOMMIT\n" {
|
||||
t.Fatalf("a holder marks nothing:\n%s", r.Mangle)
|
||||
}
|
||||
d = Compute("w2", nodes, []*coreapi.Service{s}, []*discovery.EndpointSlice{sl})
|
||||
r = Render(d, EgressDesired{}, "w2", podNet, nil)
|
||||
m := d.Holders[0].Mark << 24
|
||||
for _, want := range []string{
|
||||
"-A REWEAVE-LB-MARK -m conntrack --ctdir REPLY -m connmark ! --mark 0x0/0xff000000 -j CONNMARK --restore-mark --nfmask 0xff000000 --ctmask 0xff000000\n",
|
||||
"-A REWEAVE-LB-MARK -i weave -m mac --mac-source aa:aa:aa:aa:aa:02 ! -s 10.32.0.0/12 -d 10.32.0.0/12 -m conntrack --ctstate NEW -m comment --comment \"holder w1\" -j CONNMARK --set-xmark " + fmt.Sprintf("0x%x", m) + "/0xff000000\n",
|
||||
} {
|
||||
if !strings.Contains(r.Mangle, want) {
|
||||
t.Fatalf("mangle lacks %q:\n%s", want, r.Mangle)
|
||||
}
|
||||
}
|
||||
if r.Nat != "*nat\n:REWEAVE-LB - [0:0]\n:REWEAVE-LB-POST - [0:0]\n:REWEAVE-LB-CLASS - [0:0]\nCOMMIT\n" {
|
||||
t.Fatalf("a non-holder forwards nothing:\n%s", r.Nat)
|
||||
}
|
||||
if r.Filter != "*filter\n:REWEAVE-LB-FWD - [0:0]\n:REWEAVE-LB-IN - [0:0]\nCOMMIT\n" {
|
||||
t.Fatalf("no EgressService, no filter rules:\n%s", r.Filter)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -273,7 +273,7 @@ func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
|
||||
reqCopy := req.Copy()
|
||||
reqCopy.Id = dns.Id()
|
||||
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, upstreamConfig.Port))
|
||||
if (err != nil && err != dns.ErrTruncated) || response == nil {
|
||||
if (err != nil) || response == nil {
|
||||
h.ns.debugf("error trying %s: %v", server, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/rajch/weave/net/address"
|
||||
"github.com/weaveworks/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type mockUpstream struct {
|
||||
@@ -45,17 +45,16 @@ func TestTruncation(t *testing.T) {
|
||||
nameserver.AddEntry("foo.weave.local.", "", mesh.UnknownPeerName, i)
|
||||
}
|
||||
|
||||
doRequest := func(client *dns.Client, request *dns.Msg, port int, expectedErr error) *dns.Msg {
|
||||
doRequest := func(client *dns.Client, request *dns.Msg, port int) *dns.Msg {
|
||||
request.SetQuestion("foo.weave.local.", dns.TypeA)
|
||||
response, _, err := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
|
||||
require.Equal(t, expectedErr, err)
|
||||
response, _, _ := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
|
||||
return response
|
||||
}
|
||||
|
||||
// do a udp query, ensure we get a truncated response
|
||||
{
|
||||
udpClient := dns.Client{Net: "udp", UDPSize: minUDPSize}
|
||||
response := doRequest(&udpClient, &dns.Msg{}, udpPort, dns.ErrTruncated)
|
||||
response := doRequest(&udpClient, &dns.Msg{}, udpPort)
|
||||
require.True(t, response.MsgHdr.Truncated)
|
||||
require.True(t, len(response.Answer) < 100)
|
||||
}
|
||||
@@ -65,7 +64,7 @@ func TestTruncation(t *testing.T) {
|
||||
udpClient := dns.Client{Net: "udp", UDPSize: 65535}
|
||||
request := &dns.Msg{}
|
||||
request.SetEdns0(65535, false)
|
||||
response := doRequest(&udpClient, request, udpPort, nil)
|
||||
response := doRequest(&udpClient, request, udpPort)
|
||||
require.False(t, response.MsgHdr.Truncated)
|
||||
require.Equal(t, len(response.Answer), 100)
|
||||
}
|
||||
@@ -73,7 +72,7 @@ func TestTruncation(t *testing.T) {
|
||||
// do a tcp query, ensure we don't get a truncated response
|
||||
{
|
||||
tcpClient := dns.Client{Net: "tcp"}
|
||||
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort, nil)
|
||||
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort)
|
||||
require.False(t, response.MsgHdr.Truncated)
|
||||
require.Equal(t, len(response.Answer), 100)
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
var now = func() int64 { return time.Now().Unix() }
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
func l(es Entries) Entries {
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common/docker"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
func (n *Nameserver) badRequest(w http.ResponseWriter, err error) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/address"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
"github.com/rajch/weave/net/address"
|
||||
"github.com/rajch/weave/testing/gossip"
|
||||
)
|
||||
|
||||
func makeNameserver(name mesh.PeerName) *Nameserver {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/rajch/weave/common"
|
||||
)
|
||||
|
||||
// Using 32-bit integer to represent IPv4 address
|
||||
|
||||
+6
-6
@@ -12,11 +12,11 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/chains"
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/common/chains"
|
||||
"github.com/rajch/weave/common/odp"
|
||||
"github.com/rajch/weave/net/address"
|
||||
"github.com/rajch/weave/net/ipset"
|
||||
)
|
||||
|
||||
/* This code implements three possible configurations to connect
|
||||
@@ -48,7 +48,7 @@ the "datapath" device, via "router/fastdp.go:fastDatapathBridge"
|
||||
|
||||
"weave" is an Open vSwitch datapath, and capture/injection are as in
|
||||
BridgedFastdp. Not used by default due to missing conntrack support in
|
||||
datapath of old kernel versions (https://github.com/weaveworks/weave/issues/1577).
|
||||
datapath of old kernel versions (https://github.com/rajch/weave/issues/1577).
|
||||
*/
|
||||
|
||||
const (
|
||||
|
||||
@@ -21,7 +21,7 @@ func ensureInterface(ifaceName string) (*net.Interface, error) {
|
||||
ch := make(chan netlink.LinkUpdate)
|
||||
// NB: We do not supply (and eventually close) a 'done' channel
|
||||
// here since that can cause incorrect file descriptor
|
||||
// re-usage. See https://github.com/weaveworks/weave/issues/2120
|
||||
// re-usage. See https://github.com/rajch/weave/issues/2120
|
||||
if err := netlink.LinkSubscribe(ch, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ type ipset struct {
|
||||
// List of users per ipset entry. User is either a namespace or a pod.
|
||||
// There might be multiple users for the same ipset & entry pair because
|
||||
// events from k8s API server might be out of order causing duplicate IPs:
|
||||
// https://github.com/weaveworks/weave/issues/2792.
|
||||
// https://github.com/rajch/weave/issues/2792.
|
||||
users map[entryKey]map[UID]struct{}
|
||||
}
|
||||
|
||||
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"github.com/vishvananda/netns"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// A pod's port is locked to the pod's own addresses: what the pod sends in through the host side of its veth has to
|
||||
// carry the pod's MAC as its Ethernet source and be either IPv4 from the pod's address or ARP speaking for exactly
|
||||
// that MAC and address (or probing, from 0.0.0.0). Everything else is dropped before the bridge sees it, so a pod can
|
||||
// claim neither another pod's identity nor a node's, on its own node or, through weave, on any other.
|
||||
//
|
||||
// The lock is a set of tc filters on the veth's ingress qdisc. They match the way iproute2 lays out "ether src"
|
||||
// (the Ethernet source at -8 and -4 from the network header) and "ip src" (at 12), and they go away with the veth.
|
||||
|
||||
const (
|
||||
portLockPrioIPv4 = 1
|
||||
portLockPrioARP = 2
|
||||
portLockPrioProbe = 3
|
||||
portLockPrioDrop = 9
|
||||
)
|
||||
|
||||
var ingressParent = netlink.MakeHandle(0xffff, 0)
|
||||
|
||||
// PortLockFilters are the filters that lock the port with the given index to a pod's MAC and IPv4 address.
|
||||
func PortLockFilters(index int, mac net.HardwareAddr, ip net.IP) ([]netlink.Filter, error) {
|
||||
if len(mac) != 6 {
|
||||
return nil, fmt.Errorf("port lock needs an Ethernet MAC, got %q", mac)
|
||||
}
|
||||
ip4 := ip.To4()
|
||||
if ip4 == nil {
|
||||
return nil, fmt.Errorf("port lock needs an IPv4 address, got %q", ip)
|
||||
}
|
||||
src := etherSource(mac)
|
||||
arp := func(spa net.IP) []netlink.TcU32Key {
|
||||
keys := append([]netlink.TcU32Key{}, src...)
|
||||
return append(keys,
|
||||
// Ethernet/IPv4 ARP: hardware type 1, protocol 0x0800, address lengths 6 and 4 (any operation)
|
||||
netlink.TcU32Key{Mask: 0xffffffff, Val: 0x00010800, Off: 0},
|
||||
netlink.TcU32Key{Mask: 0xffff0000, Val: 0x06040000, Off: 4},
|
||||
// the sender hardware address (8..13) and the sender protocol address (14..17)
|
||||
netlink.TcU32Key{Mask: 0xffffffff, Val: binary.BigEndian.Uint32(mac[0:4]), Off: 8},
|
||||
netlink.TcU32Key{Mask: 0xffffffff, Val: uint32(binary.BigEndian.Uint16(mac[4:6]))<<16 | uint32(binary.BigEndian.Uint16(spa[0:2])), Off: 12},
|
||||
netlink.TcU32Key{Mask: 0xffff0000, Val: uint32(binary.BigEndian.Uint16(spa[2:4])) << 16, Off: 16},
|
||||
)
|
||||
}
|
||||
ipv4 := append(append([]netlink.TcU32Key{}, src...), netlink.TcU32Key{Mask: 0xffffffff, Val: binary.BigEndian.Uint32(ip4), Off: 12})
|
||||
return []netlink.Filter{
|
||||
portLockU32(index, portLockPrioIPv4, unix.ETH_P_IP, ipv4),
|
||||
portLockU32(index, portLockPrioARP, unix.ETH_P_ARP, arp(ip4)),
|
||||
portLockU32(index, portLockPrioProbe, unix.ETH_P_ARP, arp(net.IPv4zero.To4())),
|
||||
&netlink.MatchAll{
|
||||
FilterAttrs: netlink.FilterAttrs{LinkIndex: index, Parent: ingressParent, Priority: portLockPrioDrop, Protocol: unix.ETH_P_ALL},
|
||||
Actions: []netlink.Action{gact(netlink.TC_ACT_SHOT)},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// etherSource matches the Ethernet source address, which sits 8 bytes before the network header.
|
||||
func etherSource(mac net.HardwareAddr) []netlink.TcU32Key {
|
||||
return []netlink.TcU32Key{
|
||||
{Mask: 0xffffffff, Val: binary.BigEndian.Uint32(mac[0:4]), Off: -8},
|
||||
{Mask: 0xffff0000, Val: uint32(binary.BigEndian.Uint16(mac[4:6])) << 16, Off: -4},
|
||||
}
|
||||
}
|
||||
|
||||
func portLockU32(index int, prio uint16, proto uint16, keys []netlink.TcU32Key) *netlink.U32 {
|
||||
// clipped: the vendored netlink serialises cap(Keys) keys, and spare capacity would go out as extra all-zero keys
|
||||
keys = keys[:len(keys):len(keys)]
|
||||
return &netlink.U32{
|
||||
FilterAttrs: netlink.FilterAttrs{LinkIndex: index, Parent: ingressParent, Priority: prio, Protocol: proto},
|
||||
Sel: &netlink.TcU32Sel{Flags: nl.TC_U32_TERMINAL, Keys: keys},
|
||||
Actions: []netlink.Action{gact(netlink.TC_ACT_OK)},
|
||||
}
|
||||
}
|
||||
|
||||
func gact(action netlink.TcAct) netlink.Action {
|
||||
return &netlink.GenericAction{ActionAttrs: netlink.ActionAttrs{Action: action}}
|
||||
}
|
||||
|
||||
func ingressQdisc(index int) *netlink.Ingress {
|
||||
return &netlink.Ingress{QdiscAttrs: netlink.QdiscAttrs{LinkIndex: index, Handle: ingressParent, Parent: netlink.HANDLE_INGRESS}}
|
||||
}
|
||||
|
||||
// LockPort locks a pod's port (the host side of its veth) to the pod's MAC and IPv4 address. A port already locked
|
||||
// to them is left alone; anything else on its ingress is replaced, and the pod's own traffic is let through before
|
||||
// the rest is dropped, so a running pod never loses its own frames.
|
||||
func LockPort(link netlink.Link, mac net.HardwareAddr, ip net.IP) error {
|
||||
index := link.Attrs().Index
|
||||
want, err := PortLockFilters(index, mac, ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if locked, _ := portHasFilters(link, want); locked {
|
||||
return nil
|
||||
}
|
||||
// removing the ingress qdisc takes its filters with it
|
||||
if err := netlink.QdiscDel(ingressQdisc(index)); err != nil && !errors.Is(err, unix.ENOENT) && !errors.Is(err, unix.EINVAL) {
|
||||
return fmt.Errorf("clearing ingress of %s: %w", link.Attrs().Name, err)
|
||||
}
|
||||
if err := netlink.QdiscAdd(ingressQdisc(index)); err != nil {
|
||||
return fmt.Errorf("adding ingress qdisc to %s: %w", link.Attrs().Name, err)
|
||||
}
|
||||
for _, f := range want {
|
||||
if err := netlink.FilterAdd(f); err != nil {
|
||||
return fmt.Errorf("adding port lock filter (prio %d) to %s: %w", f.Attrs().Priority, link.Attrs().Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LockContainerPort locks the port of a pod attached by the CNI plugin: the host side of the veth whose container
|
||||
// end is ifName in ns, to that end's MAC and the pod's address.
|
||||
func LockContainerPort(ns netns.NsHandle, ifName string, ip net.IP) error {
|
||||
var mac net.HardwareAddr
|
||||
peer := 0
|
||||
if err := WithNetNSLink(ns, ifName, func(link netlink.Link) error {
|
||||
// a veth's link attribute is its peer's index, in the peer's namespace: the host's
|
||||
mac, peer = link.Attrs().HardwareAddr, link.Attrs().ParentIndex
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if peer == 0 {
|
||||
return fmt.Errorf("%s has no peer: not a veth", ifName)
|
||||
}
|
||||
host, err := netlink.LinkByIndex(peer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("host side of %s: %w", ifName, err)
|
||||
}
|
||||
if host.Type() != "veth" || !strings.HasPrefix(host.Attrs().Name, vethPrefix+"pl") {
|
||||
return fmt.Errorf("host side of %s is %s (%s), not a weave pod veth", ifName, host.Attrs().Name, host.Type())
|
||||
}
|
||||
return LockPort(host, mac, ip)
|
||||
}
|
||||
|
||||
// PortLocked reports whether a port is locked to exactly this MAC and address.
|
||||
func PortLocked(link netlink.Link, mac net.HardwareAddr, ip net.IP) (bool, error) {
|
||||
want, err := PortLockFilters(link.Attrs().Index, mac, ip)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return portHasFilters(link, want)
|
||||
}
|
||||
|
||||
func portHasFilters(link netlink.Link, want []netlink.Filter) (bool, error) {
|
||||
have, err := netlink.FilterList(link, ingressParent)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var got []netlink.Filter
|
||||
for _, f := range have {
|
||||
// u32 lists the hash table that holds a filter as a filter of its own, without a selector
|
||||
if u, ok := f.(*netlink.U32); ok && u.Sel == nil {
|
||||
continue
|
||||
}
|
||||
got = append(got, f)
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
return false, nil
|
||||
}
|
||||
for _, w := range want {
|
||||
found := false
|
||||
for _, g := range got {
|
||||
if sameFilter(g, w) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func sameFilter(a, b netlink.Filter) bool {
|
||||
if a.Attrs().Priority != b.Attrs().Priority || a.Attrs().Protocol != b.Attrs().Protocol || a.Type() != b.Type() {
|
||||
return false
|
||||
}
|
||||
switch fa := a.(type) {
|
||||
case *netlink.U32:
|
||||
fb := b.(*netlink.U32)
|
||||
if fa.Sel == nil || fb.Sel == nil || len(fa.Sel.Keys) != len(fb.Sel.Keys) || fa.Sel.Flags&nl.TC_U32_TERMINAL == 0 {
|
||||
return false
|
||||
}
|
||||
for i := range fa.Sel.Keys {
|
||||
ka, kb := fa.Sel.Keys[i], fb.Sel.Keys[i]
|
||||
if ka.Mask != kb.Mask || ka.Val != kb.Val || ka.Off != kb.Off {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return sameActions(fa.Actions, fb.Actions)
|
||||
case *netlink.MatchAll:
|
||||
return sameActions(fa.Actions, b.(*netlink.MatchAll).Actions)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func sameActions(a, b []netlink.Action) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
ga, ok1 := a[i].(*netlink.GenericAction)
|
||||
gb, ok2 := b[i].(*netlink.GenericAction)
|
||||
if !ok1 || !ok2 || ga.Attrs().Action != gb.Attrs().Action {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// TestPortLockInKernel builds a bridge with two pods in throwaway network namespaces, locks pod A's port and sends
|
||||
// frames from A: what A may send reaches B, what claims anything but A's own addresses does not. It needs root.
|
||||
func TestPortLockInKernel(t *testing.T) {
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("needs root (creates network namespaces)")
|
||||
}
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
orig, err := netns.Get()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer orig.Close()
|
||||
defer netns.Set(orig)
|
||||
|
||||
hub, err := netns.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer hub.Close()
|
||||
podA, _ := netns.New()
|
||||
defer podA.Close()
|
||||
podB, _ := netns.New()
|
||||
defer podB.Close()
|
||||
must(t, netns.Set(hub))
|
||||
|
||||
br := &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: "br0"}}
|
||||
must(t, netlink.LinkAdd(br))
|
||||
must(t, netlink.LinkSetUp(br))
|
||||
ipA, ipB := net.IPv4(10, 99, 0, 1).To4(), net.IPv4(10, 99, 0, 2).To4()
|
||||
hostA, macA := attach(t, hub, podA, br, "vethwepltsta", ipA)
|
||||
_, macB := attach(t, hub, podB, br, "vethwepltstb", ipB)
|
||||
|
||||
// as the CNI plugin does it: from the pod's end of the veth, in the pod's namespace
|
||||
must(t, LockContainerPort(podA, "eth0", ipA))
|
||||
if ok, err := PortLocked(hostA, macA, ipA); err != nil || !ok {
|
||||
dumpIngress(t, hostA)
|
||||
t.Fatalf("port not locked after LockContainerPort: %v %v", ok, err)
|
||||
}
|
||||
// locking again changes nothing (the filters are compared, not rebuilt)
|
||||
must(t, LockPort(hostA, macA, ipA))
|
||||
if ok, _ := PortLocked(hostA, macA, ipA); !ok {
|
||||
t.Fatal("port lost its lock on a repeated LockPort")
|
||||
}
|
||||
|
||||
send := socketIn(t, podA, "eth0")
|
||||
recv := socketIn(t, podB, "eth0")
|
||||
defer unix.Close(send)
|
||||
defer unix.Close(recv)
|
||||
other, _ := net.ParseMAC("02:00:00:00:00:99")
|
||||
spoofIP := net.IPv4(10, 99, 0, 200).To4()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
frame []byte
|
||||
passes bool
|
||||
}{
|
||||
{"ipv4 from its own addresses", ipv4Frame(macB, macA, ipA, ipB, "own-ip"), true},
|
||||
{"ipv4 from another MAC", ipv4Frame(macB, other, ipA, ipB, "mac-spoof"), false},
|
||||
{"ipv4 from another address", ipv4Frame(macB, macA, spoofIP, ipB, "ip-spoof"), false},
|
||||
{"arp for itself", arpFrame(macA, macA, ipA, ipB, "own-arp"), true},
|
||||
{"arp probe", arpFrame(macA, macA, net.IPv4zero.To4(), ipB, "probe"), true},
|
||||
{"arp claiming another address", arpFrame(macA, macA, spoofIP, ipB, "arp-ip-spoof"), false},
|
||||
{"arp claiming another MAC", arpFrame(macA, other, ipA, ipB, "arp-sha-spoof"), false},
|
||||
{"arp from another MAC", arpFrame(other, other, ipA, ipB, "arp-eth-spoof"), false},
|
||||
{"ipv6", etherFrame(macB, macA, 0x86dd, []byte("ipv6-frame")), false},
|
||||
{"other ethertype", etherFrame(macB, macA, 0x88b5, []byte("experimental")), false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
drain(recv)
|
||||
if _, err := unix.Write(send, c.frame); err != nil {
|
||||
t.Fatalf("%s: send: %v", c.name, err)
|
||||
}
|
||||
got := sawPayload(recv, c.frame[len(c.frame)-12:], 300*time.Millisecond)
|
||||
if got != c.passes {
|
||||
t.Errorf("%s: reached B = %v, want %v", c.name, got, c.passes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dumpIngress(t *testing.T, link netlink.Link) {
|
||||
have, _ := netlink.FilterList(link, netlink.MakeHandle(0xffff, 0))
|
||||
for _, f := range have {
|
||||
t.Logf("have %s %+v", f.Type(), f.Attrs())
|
||||
if u, ok := f.(*netlink.U32); ok && u.Sel != nil {
|
||||
t.Logf(" sel flags %x keys %+v actions %+v", u.Sel.Flags, u.Sel.Keys, u.Actions)
|
||||
}
|
||||
if m, ok := f.(*netlink.MatchAll); ok {
|
||||
t.Logf(" actions %+v", m.Actions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func must(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// attach plugs a pod namespace into the bridge through a veth named host on the hub side, eth0 on the pod side.
|
||||
func attach(t *testing.T, hub, pod netns.NsHandle, br netlink.Link, host string, ip net.IP) (netlink.Link, net.HardwareAddr) {
|
||||
t.Helper()
|
||||
must(t, netlink.LinkAdd(&netlink.Veth{LinkAttrs: netlink.LinkAttrs{Name: host}, PeerName: "tmp" + host[len(host)-2:]}))
|
||||
h, err := netlink.LinkByName(host)
|
||||
must(t, err)
|
||||
must(t, netlink.LinkSetMaster(h, br))
|
||||
must(t, netlink.LinkSetUp(h))
|
||||
p, err := netlink.LinkByName("tmp" + host[len(host)-2:])
|
||||
must(t, err)
|
||||
must(t, netlink.LinkSetNsFd(p, int(pod)))
|
||||
must(t, netns.Set(pod))
|
||||
p, err = netlink.LinkByName("tmp" + host[len(host)-2:])
|
||||
must(t, err)
|
||||
must(t, netlink.LinkSetName(p, "eth0"))
|
||||
must(t, netlink.AddrAdd(p, &netlink.Addr{IPNet: &net.IPNet{IP: ip, Mask: net.CIDRMask(24, 32)}}))
|
||||
must(t, netlink.LinkSetUp(p))
|
||||
mac := p.Attrs().HardwareAddr
|
||||
must(t, netns.Set(hub))
|
||||
return h, mac
|
||||
}
|
||||
|
||||
// socketIn opens a raw packet socket on an interface of a namespace (the socket stays in that namespace).
|
||||
func socketIn(t *testing.T, ns netns.NsHandle, ifName string) int {
|
||||
t.Helper()
|
||||
cur, _ := netns.Get()
|
||||
defer cur.Close()
|
||||
must(t, netns.Set(ns))
|
||||
defer netns.Set(cur)
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
must(t, err)
|
||||
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL)))
|
||||
must(t, err)
|
||||
must(t, unix.Bind(fd, &unix.SockaddrLinklayer{Protocol: htons(unix.ETH_P_ALL), Ifindex: link.Attrs().Index}))
|
||||
return fd
|
||||
}
|
||||
|
||||
func htons(v uint16) uint16 { return v<<8 | v>>8 }
|
||||
|
||||
func drain(fd int) {
|
||||
buf := make([]byte, 2048)
|
||||
for {
|
||||
if _, _, err := unix.Recvfrom(fd, buf, unix.MSG_DONTWAIT); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sawPayload(fd int, marker []byte, within time.Duration) bool {
|
||||
buf := make([]byte, 2048)
|
||||
deadline := time.Now().Add(within)
|
||||
for time.Now().Before(deadline) {
|
||||
n, _, err := unix.Recvfrom(fd, buf, unix.MSG_DONTWAIT)
|
||||
if err != nil {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
if bytes.Contains(buf[:n], marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func etherFrame(dst, src net.HardwareAddr, ethertype uint16, payload []byte) []byte {
|
||||
f := make([]byte, 14, 14+len(payload)+12)
|
||||
copy(f[0:6], dst)
|
||||
copy(f[6:12], src)
|
||||
binary.BigEndian.PutUint16(f[12:14], ethertype)
|
||||
f = append(f, payload...)
|
||||
// a fixed-size tail the receiver looks for, so each case is told apart
|
||||
tail := make([]byte, 12)
|
||||
copy(tail, payload)
|
||||
return append(f, tail...)
|
||||
}
|
||||
|
||||
func ipv4Frame(dst, src net.HardwareAddr, from, to net.IP, tag string) []byte {
|
||||
body := []byte(tag)
|
||||
h := make([]byte, 20)
|
||||
h[0] = 0x45
|
||||
binary.BigEndian.PutUint16(h[2:4], uint16(20+len(body)+12))
|
||||
h[8] = 64
|
||||
h[9] = 253 // experimental protocol: nothing answers it
|
||||
copy(h[12:16], from)
|
||||
copy(h[16:20], to)
|
||||
binary.BigEndian.PutUint16(h[10:12], checksum(h))
|
||||
return etherFrame(dst, src, unix.ETH_P_IP, append(h, body...))
|
||||
}
|
||||
|
||||
func arpFrame(ethSrc, sha net.HardwareAddr, spa, tpa net.IP, tag string) []byte {
|
||||
a := make([]byte, 28)
|
||||
binary.BigEndian.PutUint16(a[0:2], 1)
|
||||
binary.BigEndian.PutUint16(a[2:4], unix.ETH_P_IP)
|
||||
a[4], a[5] = 6, 4
|
||||
binary.BigEndian.PutUint16(a[6:8], 1) // request
|
||||
copy(a[8:14], sha)
|
||||
copy(a[14:18], spa)
|
||||
copy(a[24:28], tpa)
|
||||
bcast, _ := net.ParseMAC("ff:ff:ff:ff:ff:ff")
|
||||
return etherFrame(bcast, ethSrc, unix.ETH_P_ARP, append(a, []byte(tag)...))
|
||||
}
|
||||
|
||||
func checksum(b []byte) uint16 {
|
||||
var s uint32
|
||||
for i := 0; i+1 < len(b); i += 2 {
|
||||
s += uint32(binary.BigEndian.Uint16(b[i:]))
|
||||
}
|
||||
for s>>16 != 0 {
|
||||
s = s&0xffff + s>>16
|
||||
}
|
||||
return ^uint16(s)
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// The keys iproute2 writes for "u32 match ether src 02:11:22:33:44:55 match ip src 10.32.0.7/32" (tc filter show,
|
||||
// iproute2 6.x): the port lock has to match the same bytes.
|
||||
func TestPortLockMatchesIproute2Layout(t *testing.T) {
|
||||
mac, _ := net.ParseMAC("02:11:22:33:44:55")
|
||||
fs, err := PortLockFilters(7, mac, net.ParseIP("10.32.0.7"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(fs) != 4 {
|
||||
t.Fatalf("want 4 filters, got %d", len(fs))
|
||||
}
|
||||
ipv4 := fs[0].(*netlink.U32)
|
||||
want := []netlink.TcU32Key{
|
||||
{Val: 0x02112233, Mask: 0xffffffff, Off: -8},
|
||||
{Val: 0x44550000, Mask: 0xffff0000, Off: -4},
|
||||
{Val: 0x0a200007, Mask: 0xffffffff, Off: 12},
|
||||
}
|
||||
if ipv4.Protocol != unix.ETH_P_IP || ipv4.Priority != portLockPrioIPv4 || ipv4.Sel.Flags&nl.TC_U32_TERMINAL == 0 {
|
||||
t.Fatalf("ipv4 filter attrs: %+v flags %x", ipv4.FilterAttrs, ipv4.Sel.Flags)
|
||||
}
|
||||
assertKeys(t, "ipv4", ipv4.Sel.Keys, want)
|
||||
for _, f := range fs {
|
||||
if f.Attrs().LinkIndex != 7 || f.Attrs().Parent != netlink.MakeHandle(0xffff, 0) {
|
||||
t.Fatalf("filter not on ingress of link 7: %+v", f.Attrs())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortLockARP(t *testing.T) {
|
||||
mac, _ := net.ParseMAC("02:11:22:33:44:55")
|
||||
fs, _ := PortLockFilters(7, mac, net.ParseIP("10.32.0.7"))
|
||||
common := []netlink.TcU32Key{
|
||||
{Val: 0x02112233, Mask: 0xffffffff, Off: -8},
|
||||
{Val: 0x44550000, Mask: 0xffff0000, Off: -4},
|
||||
{Val: 0x00010800, Mask: 0xffffffff, Off: 0},
|
||||
{Val: 0x06040000, Mask: 0xffff0000, Off: 4},
|
||||
{Val: 0x02112233, Mask: 0xffffffff, Off: 8},
|
||||
}
|
||||
own := append(append([]netlink.TcU32Key{}, common...),
|
||||
netlink.TcU32Key{Val: 0x44550a20, Mask: 0xffffffff, Off: 12}, // sha 44:55, spa 10.32
|
||||
netlink.TcU32Key{Val: 0x00070000, Mask: 0xffff0000, Off: 16}, // spa .0.7
|
||||
)
|
||||
probe := append(append([]netlink.TcU32Key{}, common...),
|
||||
netlink.TcU32Key{Val: 0x44550000, Mask: 0xffffffff, Off: 12},
|
||||
netlink.TcU32Key{Val: 0x00000000, Mask: 0xffff0000, Off: 16},
|
||||
)
|
||||
arp, pr := fs[1].(*netlink.U32), fs[2].(*netlink.U32)
|
||||
if arp.Protocol != unix.ETH_P_ARP || pr.Protocol != unix.ETH_P_ARP {
|
||||
t.Fatal("ARP filters must be protocol arp")
|
||||
}
|
||||
assertKeys(t, "arp", arp.Sel.Keys, own)
|
||||
assertKeys(t, "probe", pr.Sel.Keys, probe)
|
||||
drop := fs[3].(*netlink.MatchAll)
|
||||
if drop.Protocol != unix.ETH_P_ALL || drop.Priority != portLockPrioDrop {
|
||||
t.Fatalf("drop filter attrs: %+v", drop.FilterAttrs)
|
||||
}
|
||||
if a := drop.Actions[0].(*netlink.GenericAction); a.Attrs().Action != netlink.TC_ACT_SHOT {
|
||||
t.Fatalf("last filter must drop, has action %v", a.Attrs().Action)
|
||||
}
|
||||
for _, f := range fs[:3] {
|
||||
if a := f.(*netlink.U32).Actions[0].(*netlink.GenericAction); a.Attrs().Action != netlink.TC_ACT_OK {
|
||||
t.Fatalf("prio %d must pass, has action %v", f.Attrs().Priority, a.Attrs().Action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortLockRefusesWhatItCannotPin(t *testing.T) {
|
||||
mac, _ := net.ParseMAC("02:11:22:33:44:55")
|
||||
if _, err := PortLockFilters(7, mac, net.ParseIP("fd00::1")); err == nil {
|
||||
t.Fatal("an IPv6 address must be refused: the lock pins IPv4 and ARP")
|
||||
}
|
||||
long, _ := net.ParseMAC("02:11:22:33:44:55:66:77")
|
||||
if _, err := PortLockFilters(7, long, net.ParseIP("10.32.0.7")); err == nil {
|
||||
t.Fatal("a non-Ethernet hardware address must be refused")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameFilterComparesKeysAndActions(t *testing.T) {
|
||||
mac, _ := net.ParseMAC("02:11:22:33:44:55")
|
||||
a, _ := PortLockFilters(7, mac, net.ParseIP("10.32.0.7"))
|
||||
b, _ := PortLockFilters(7, mac, net.ParseIP("10.32.0.8"))
|
||||
if !sameFilter(a[0], a[0]) || sameFilter(a[0], b[0]) {
|
||||
t.Fatal("ipv4 filters for different addresses must differ")
|
||||
}
|
||||
if !sameFilter(a[3], b[3]) {
|
||||
t.Fatal("the drop filters are the same for every pod")
|
||||
}
|
||||
if sameFilter(a[1], a[2]) {
|
||||
t.Fatal("the ARP and probe filters differ in priority and keys")
|
||||
}
|
||||
}
|
||||
|
||||
func assertKeys(t *testing.T, what string, got, want []netlink.TcU32Key) {
|
||||
t.Helper()
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("%s: want %d keys, got %d: %+v", what, len(want), len(got), got)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i].Val != want[i].Val || got[i].Mask != want[i].Mask || got[i].Off != want[i].Off {
|
||||
t.Fatalf("%s key %d: want %08x/%08x at %d, got %08x/%08x at %d", what, i,
|
||||
want[i].Val, want[i].Mask, want[i].Off, got[i].Val, got[i].Mask, got[i].Off)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,8 +6,8 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/rajch/weave/db"
|
||||
"github.com/weaveworks/mesh"
|
||||
"github.com/weaveworks/weave/db"
|
||||
)
|
||||
|
||||
func getOldStyleSystemUUID() ([]byte, error) {
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import (
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/rajch/weave/net/ipset"
|
||||
)
|
||||
|
||||
func (ns *ns) analysePolicy(policy *networkingv1.NetworkPolicy) (
|
||||
|
||||
+4
-4
@@ -12,10 +12,10 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/chains"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/common/chains"
|
||||
"github.com/rajch/weave/net/ipset"
|
||||
"github.com/rajch/weave/npc/iptables"
|
||||
)
|
||||
|
||||
type NetworkPolicyController interface {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rajch/weave/common/chains"
|
||||
"github.com/rajch/weave/net/ipset"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/common/chains"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -156,7 +156,7 @@ func (ipt *mockIPTables) Insert(table, chain string, pos int, rulespec ...string
|
||||
|
||||
func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
|
||||
// Test for race condition between namespace and networkpolicy events
|
||||
// https://github.com/weaveworks/weave/issues/3059
|
||||
// https://github.com/rajch/weave/issues/3059
|
||||
|
||||
sourceNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -456,7 +456,7 @@ func TestOutOfOrderPodEvents(t *testing.T) {
|
||||
require.False(t, m.entriesExist(runBarIPSetName, podIP))
|
||||
}
|
||||
|
||||
// Test case for https://github.com/weaveworks/weave/issues/3222
|
||||
// Test case for https://github.com/rajch/weave/issues/3222
|
||||
func TestNewTargetSelector(t *testing.T) {
|
||||
const (
|
||||
ingressDefaultAllowIPSetName = "weave-;rGqyMIl1HN^cfDki~Z$3]6!N"
|
||||
@@ -633,7 +633,7 @@ func TestEgressPolicyWithIPBlock(t *testing.T) {
|
||||
require.Equal(t, 2, len(m.sets[exceptIPSetNameInNonDefault].subSets))
|
||||
}
|
||||
|
||||
// Test case for https://github.com/weaveworks/weave/issues/3653
|
||||
// Test case for https://github.com/rajch/weave/issues/3653
|
||||
func TestIngressPolicyWithIPBlockAndPortSpecified(t *testing.T) {
|
||||
const (
|
||||
barPodIP = "10.32.0.11"
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/ipset"
|
||||
)
|
||||
|
||||
type ipBlockSpec struct {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/rajch/weave/common"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
+4
-4
@@ -10,10 +10,10 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/chains"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/common/chains"
|
||||
"github.com/rajch/weave/net/ipset"
|
||||
"github.com/rajch/weave/npc/iptables"
|
||||
)
|
||||
|
||||
var errInvalidNetworkPolicyObjType = errors.New("invalid NetworkPolicy object type")
|
||||
|
||||
+4
-4
@@ -5,10 +5,10 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/chains"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/common/chains"
|
||||
"github.com/rajch/weave/net/ipset"
|
||||
"github.com/rajch/weave/npc/iptables"
|
||||
)
|
||||
|
||||
type ruleHost interface {
|
||||
|
||||
+3
-2
@@ -1,13 +1,14 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRegressionHandleNil3095(t *testing.T) {
|
||||
// Test for handling nil values
|
||||
// https://github.com/weaveworks/weave/issues/3095
|
||||
// https://github.com/rajch/weave/issues/3095
|
||||
|
||||
rs := newRuleSpec(policyTypeIngress, nil, nil, nil, nil)
|
||||
|
||||
|
||||
+5
-5
@@ -6,8 +6,8 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/rajch/weave/common"
|
||||
"github.com/rajch/weave/net/ipset"
|
||||
)
|
||||
|
||||
type selectorSpec struct {
|
||||
@@ -121,9 +121,9 @@ func newSelectorSet(ips ipset.Interface, onNewSelector selectorFn, onNewTargetSe
|
||||
onNewSelector: onNewSelector,
|
||||
onNewTargetSelector: onNewTargetSelector,
|
||||
onDestroyTargetSelector: onDestroyTargetSelector,
|
||||
users: make(map[string]map[ipset.UID]struct{}),
|
||||
entries: make(map[string]*selector),
|
||||
targetSelectorsCount: make(map[string]map[policyType]int)}
|
||||
users: make(map[string]map[ipset.UID]struct{}),
|
||||
entries: make(map[string]*selector),
|
||||
targetSelectorsCount: make(map[string]map[policyType]int)}
|
||||
}
|
||||
|
||||
func (ss *selectorSet) addToMatchingPodSelector(user ipset.UID, podLabelsMap map[string]string, entry string, comment string) (bool, bool, error) {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/rajch/weave/common"
|
||||
)
|
||||
|
||||
func waitForExit(cmd *exec.Cmd) {
|
||||
|
||||
File renamed without changes.
File renamed without changes.
Generated
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,430 @@
|
||||
.DEFAULT: all
|
||||
.PHONY: all exes testrunner update tests lint publish-one-arch $(PUBLISH) clean clean-bin clean-work-dir prerequisites build run-smoketests
|
||||
|
||||
# If you can use docker without being root, you can do "make SUDO="
|
||||
SUDO=$(shell docker info >/dev/null 2>&1 || echo "sudo -E")
|
||||
BUILD_IN_CONTAINER=true
|
||||
RM=--rm
|
||||
RUN_FLAGS=-ti
|
||||
COVERAGE=
|
||||
|
||||
# This specifies the architecture we're building for
|
||||
ARCH?=amd64
|
||||
|
||||
# We're using QEMU to be able to build Docker images for other architectures
|
||||
# from amd64 machines.
|
||||
QEMU_VERSION=v2.7.0
|
||||
|
||||
# A list of all supported architectures here. Should be named as Go is naming platforms
|
||||
# All supported architectures must have an "ifeq" block below that customizes the parameters
|
||||
ALL_ARCHITECTURES=amd64 arm arm64 ppc64le s390x
|
||||
ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64,linux/ppc64le,linux/s390x
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=x86_64
|
||||
|
||||
# The name of the alpine baseimage to use as the base for weave images
|
||||
ALPINE_BASEIMAGE?=alpine:3.10
|
||||
|
||||
# The extension for the made images
|
||||
# Specifying none means for example weaveworks/weave:latest
|
||||
ARCH_EXT?=
|
||||
|
||||
# The name of the gcc we're using for compiling C code
|
||||
CC=gcc
|
||||
|
||||
# Optional parameters that can be passed when linking C code into the Go binary
|
||||
CGO_LDFLAGS=
|
||||
endif
|
||||
ifeq ($(ARCH),arm)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=arm32v6/alpine:3.8
|
||||
|
||||
# arm images have the -arm suffix, for instance weaveworks/weave-arm:latest
|
||||
ARCH_EXT?=-arm
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=arm-linux-gnueabihf-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=arm
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),arm64)
|
||||
# Use the arm 32-bit docker client variant
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=arm64v8/alpine:3.8
|
||||
|
||||
# arm64 images have the -arm64 suffix, for instance weaveworks/weave-arm64:latest
|
||||
ARCH_EXT?=-arm64
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=aarch64-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=aarch64
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm64 are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),ppc64le)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=ppc64le
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=ppc64le/alpine:3.8
|
||||
|
||||
# ppc64le images have the -ppc64le suffix, for instance weaveworks/weave-ppc64le:latest
|
||||
ARCH_EXT?=-ppc64le
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=powerpc64le-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=ppc64le
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for ppc64le are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),s390x)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=s390x
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=s390x/alpine:3.6
|
||||
|
||||
# s390x images have the -s390x suffix, for instance weaveworks/weave-s390x:latest
|
||||
ARCH_EXT?=-s390x
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=s390x-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=s390x
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for s390x are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
|
||||
# The name of the user that this Makefile should produce image artifacts for. Can/should be overridden
|
||||
DOCKERHUB_USER?=weaveworks
|
||||
# The default version that's chosen when pushing the images. Can/should be overridden
|
||||
GIT_REVISION=$(shell git rev-parse HEAD)
|
||||
WEAVE_VERSION?=git-$(shell git rev-parse --short=12 HEAD)
|
||||
# Docker Store does not allow the "latest" tag.
|
||||
NET_PLUGIN_LATEST=latest_release
|
||||
|
||||
# Paths to all relevant binaries that should be compiled
|
||||
WEAVER_EXE=prog/weaver/weaver
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
KUBEUTILS_EXE=prog/kube-utils/kube-utils
|
||||
WEAVENPC_EXE=prog/weave-npc/weave-npc
|
||||
WEAVEWAIT_EXE=prog/weavewait/weavewait
|
||||
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
|
||||
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
|
||||
WEAVEUTIL_EXE=prog/weaveutil/weaveutil
|
||||
RUNNER_EXE=tools/runner/runner
|
||||
# Get this from https://github.com/estesp/manifest-tool/releases
|
||||
MANIFEST_TOOL_CMD=manifest-tool
|
||||
TEST_TLS_EXE=test/tls/tls
|
||||
NETWORKTESTER_EXE=test/images/network-tester/webserver
|
||||
|
||||
# All binaries together in a list
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(NETWORKTESTER_EXE)
|
||||
|
||||
# These stamp files are used to mark the current state of the build; whether an image has been built or not
|
||||
BUILD_UPTODATE=.build.uptodate
|
||||
WEAVER_UPTODATE=.weaver$(ARCH_EXT).uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec$(ARCH_EXT).uptodate
|
||||
PLUGIN_UPTODATE=.net-plugin$(ARCH_EXT).uptodate
|
||||
WEAVEKUBE_UPTODATE=.weavekube$(ARCH_EXT).uptodate
|
||||
WEAVENPC_UPTODATE=.weavenpc$(ARCH_EXT).uptodate
|
||||
WEAVEDB_UPTODATE=.weavedb.uptodate
|
||||
NETWORKTESTER_UPTODATE=.networktester$(ARCH_EXT).uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE) $(NETWORKTESTER_UPTODATE)
|
||||
|
||||
# The names of the images. Note that the images for other architectures than amd64 have a suffix in the image name.
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave$(ARCH_EXT)
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec$(ARCH_EXT)
|
||||
WEAVEKUBE_IMAGE=$(DOCKERHUB_USER)/weave-kube$(ARCH_EXT)
|
||||
WEAVENPC_IMAGE=$(DOCKERHUB_USER)/weave-npc$(ARCH_EXT)
|
||||
BUILD_IMAGE=weaveworks/weavebuild
|
||||
WEAVEDB_IMAGE=$(DOCKERHUB_USER)/weavedb
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/net-plugin
|
||||
NETWORKTESTER_IMAGE=$(DOCKERHUB_USER)/network-tester
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(WEAVEKUBE_IMAGE) $(WEAVENPC_IMAGE) $(WEAVEDB_IMAGE) $(NETWORKTESTER_IMAGE)
|
||||
|
||||
PLUGIN_WORK_DIR="prog/net-plugin/rootfs"
|
||||
PLUGIN_BUILD_IMG="plugin-builder"
|
||||
|
||||
PUBLISH=publish_weave publish_weaveexec publish_weave-kube publish_weave-npc
|
||||
PUSH_ML=push_ml_weave push_ml_weaveexec push_ml_weave-kube push_ml_weave-npc
|
||||
|
||||
WEAVE_EXPORT=weave$(ARCH_EXT).tar.gz
|
||||
|
||||
# The flags we are passing to go build. -extldflags -static for making a static binary,
|
||||
# -X main.version for telling the Go binary which version it is,
|
||||
# -s -w drop symbol tables used by debuggers (not Go's internal symbol info)
|
||||
# -tags "osusergo netgo" to use native Go UID and DNS implementations
|
||||
BUILD_FLAGS=-ldflags "-extldflags -static -X main.version=$(WEAVE_VERSION) -s -w" -tags "osusergo netgo"
|
||||
|
||||
PACKAGE_BASE=$(shell go list -e ./)
|
||||
|
||||
all: $(WEAVE_EXPORT)
|
||||
testrunner: $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go db/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVER_EXE): api/*.go plugin/*.go plugin/*/*
|
||||
$(WEAVER_EXE): proxy/*.go
|
||||
$(WEAVEUTIL_EXE): prog/weaveutil/*.go net/*.go plugin/net/*.go plugin/ipam/*.go db/*.go
|
||||
$(SIGPROXY_EXE): prog/sigproxy/*.go
|
||||
$(KUBEUTILS_EXE): prog/kube-utils/*.go
|
||||
$(WEAVENPC_EXE): prog/weave-npc/*.go npc/*.go npc/*/*.go
|
||||
$(TEST_TLS_EXE): test/tls/*.go
|
||||
$(RUNNER_EXE): tools/runner/*.go
|
||||
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
|
||||
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
|
||||
$(NETWORKTESTER_EXE): test/images/network-tester/*.go
|
||||
tests: tools/.git
|
||||
lint: tools/.git
|
||||
|
||||
ifeq ($(BUILD_IN_CONTAINER),true)
|
||||
|
||||
# This make target compiles all binaries inside of the weaveworks/build container
|
||||
# It bind-mounts the source into the container and passes all important variables
|
||||
exes $(EXES) tests lint: $(BUILD_UPTODATE)
|
||||
$(SUDO) docker run $(RM) $(RUN_FLAGS) \
|
||||
-v $(shell pwd):/go/src/github.com/weaveworks/weave \
|
||||
-e GOARCH=$(ARCH) -e CGO_ENABLED=1 -e GOOS=linux -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW -e DEBUG \
|
||||
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) CC=$(CC) QEMUARCH=$(QEMUARCH) CGO_LDFLAGS=$(CGO_LDFLAGS) $@
|
||||
touch $@
|
||||
|
||||
else
|
||||
|
||||
exes: $(EXES)
|
||||
|
||||
$(WEAVER_EXE):
|
||||
ifeq ($(COVERAGE),true)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | grep -v "^$(PACKAGE_BASE)/vendor/" | paste -s -d,))
|
||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||
else
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
endif
|
||||
|
||||
$(WEAVEUTIL_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(NETWORKTESTER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "osusergo netgo iface mcast" -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_NOMCAST_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "osusergo netgo iface" -o $@ ./$(@D)
|
||||
|
||||
# These programs need a separate rule as they fail the netgo check in
|
||||
# the main build stanza due to not importing net package
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
tests:
|
||||
./tools/test -no-go-get -netgo -timeout 8m
|
||||
|
||||
lint:
|
||||
./tools/lint -nocomment -notestpackage
|
||||
|
||||
endif
|
||||
|
||||
# This rule makes sure the build image is up-to-date.
|
||||
# It also makes sure the multiarch hooks are reqistered in the kernel so the QEMU emulation works
|
||||
$(BUILD_UPTODATE): build/* tools/.git
|
||||
$(SUDO) docker build -t $(BUILD_IMAGE) build/
|
||||
$(SUDO) docker tag $(BUILD_IMAGE) $(BUILD_IMAGE):$(shell tools/image-tag)
|
||||
$(SUDO) docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
touch $@
|
||||
|
||||
# Creates the Dockerfile.your-user-here file from the template
|
||||
# Also replaces all placeholders with real values
|
||||
# If the architecture is amd64, it deletes all CROSS_BUILD lines
|
||||
# but otherwise, it only removes the "CROSS_BUILD_" placeholder and downloads QEMU
|
||||
%/Dockerfile.$(DOCKERHUB_USER): %/Dockerfile.template
|
||||
echo "DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)"
|
||||
sed -e "s|DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)|g" $^ > $@
|
||||
ifeq ($(ARCH),amd64)
|
||||
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
|
||||
sed -i.bak "/CROSS_BUILD_/d" $@
|
||||
else
|
||||
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for ARM and ppc64le binaries in the kernel
|
||||
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMU_VERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(shell dirname $@)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/qemu-$(QEMUARCH)-static.sha256sum
|
||||
sed -i.bak "s/CROSS_BUILD_//g" $@
|
||||
endif
|
||||
|
||||
# The targets below builds the weave images
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) weave $(WEAVEUTIL_EXE)
|
||||
cp $(WEAVEUTIL_EXE) prog/weaver/weaveutil
|
||||
cp weave prog/weaver/weave
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
|
||||
touch $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexec/symlink $(SIGPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
touch $@
|
||||
|
||||
# Builds Docker plugin.
|
||||
$(PLUGIN_UPTODATE): prog/net-plugin/launch.sh prog/net-plugin/config.json $(WEAVER_UPTODATE)
|
||||
-$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG) 2>/dev/null
|
||||
$(SUDO) docker create --name=$(PLUGIN_BUILD_IMG) $(WEAVER_IMAGE) true
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
mkdir $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker export $(PLUGIN_BUILD_IMG) | tar -x -C $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG)
|
||||
cp prog/net-plugin/launch.sh $(PLUGIN_WORK_DIR)/home/weave/launch.sh
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(WEAVE_VERSION) prog/net-plugin
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) prog/net-plugin
|
||||
touch $@
|
||||
|
||||
$(WEAVEKUBE_UPTODATE): prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) prog/weave-kube/launch.sh $(KUBEUTILS_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(KUBEUTILS_EXE) prog/weave-kube/
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEKUBE_IMAGE) prog/weave-kube
|
||||
touch $@
|
||||
|
||||
$(WEAVENPC_UPTODATE): prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) $(WEAVENPC_EXE) prog/weave-npc/ulogd.conf
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVENPC_IMAGE) prog/weave-npc
|
||||
touch $@
|
||||
|
||||
$(WEAVEDB_UPTODATE): prog/weavedb/Dockerfile
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -t $(WEAVEDB_IMAGE) prog/weavedb
|
||||
touch $@
|
||||
|
||||
$(NETWORKTESTER_UPTODATE): test/images/network-tester/Dockerfile $(NETWORKTESTER_EXE)
|
||||
$(SUDO) docker build -t $(NETWORKTESTER_IMAGE) test/images/network-tester
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||
|
||||
tools/.git:
|
||||
git submodule update --init
|
||||
|
||||
# CODE FOR PUBLISHING THE IMAGES
|
||||
|
||||
# Push plugin
|
||||
plugin_publish: $(PLUGIN_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(WEAVE_VERSION)
|
||||
# "$(NET_PLUGIN_LATEST)" means "stable release" here, so only push that if explicitly told to
|
||||
ifeq ($(UPDATE_LATEST),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST)
|
||||
endif
|
||||
|
||||
# This target first runs "make publish" for each architecture
|
||||
# Then it pushes the manifest lists
|
||||
publish: $(addprefix sub-publish-,$(ALL_ARCHITECTURES)) $(WEAVEDB_UPTODATE)
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(PUSH_ML)
|
||||
ifeq ($(PUBLISH_WEAVEDB),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/weavedb:latest
|
||||
endif
|
||||
|
||||
sub-publish-%:
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) clean-bin
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(IMAGES_UPTODATE)
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) publish-one-arch
|
||||
|
||||
publish-one-arch: $(PUBLISH)
|
||||
|
||||
# This rule handles the pushing of the built images
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
# Tag :latest with the real version
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*$(ARCH_EXT) $(DOCKERHUB_USER)/$*$(ARCH_EXT):$(WEAVE_VERSION)
|
||||
# Push the image with the arch suffix for arm, arm64 and ppc64le, and without suffix for amd64
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*$(ARCH_EXT)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# If the architecture is amd64, add the -amd64 suffix.
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION)
|
||||
# If the version is latest, tag the -amd64 with latest as well
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:latest
|
||||
endif
|
||||
# Push the image with the -amd64-suffix so BINARY-ARCH-named images exist for all arches, so manifest lists may be made and ARCH is replaceable in scripts
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*-amd64
|
||||
endif
|
||||
|
||||
# This target pushes an image, and if UPDATE_LATEST is anything but false, also updates the latest tag
|
||||
# It takes one parameter, the image name
|
||||
push_%:
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
# This target pushes a manifest list; it takes one parameter, the image name.
|
||||
# The variable UPDATE_LATEST controls whether it updates the versioned tag and/or the latest tag
|
||||
$(PUSH_ML): push_ml_%:
|
||||
ifneq ($(UPDATE_LATEST),latest-only)
|
||||
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:$(WEAVE_VERSION) --target $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
endif
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
# Push the manifest list to :latest as well
|
||||
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:latest --target $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
clean-work-dir:
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
|
||||
clean-bin:
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
|
||||
find prog -type f -name "Dockerfile.*" -not -name "Dockerfile.template" -print | xargs rm -f
|
||||
find prog -type f -name "*qemu-*" -print | xargs rm -f
|
||||
go clean ./...
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE) weave*.tar.gz .cache
|
||||
|
||||
clean: clean-bin clean-work-dir
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(BUILD_IMAGE)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE)
|
||||
|
||||
build:
|
||||
$(MAKE)
|
||||
|
||||
run-smoketests: all testrunner
|
||||
cd test && ./setup.sh && ./run_all.sh
|
||||
|
||||
integration-tests: all testrunner
|
||||
# Usage:
|
||||
# $ make \
|
||||
# NAME="<prefix used to name VMs and other resources>" \
|
||||
# PROVIDER="<provider among {vagrant|gcp|aws|do}>" \
|
||||
# NUM_HOSTS="<# test machines>" \
|
||||
# PLAYBOOK="<filename>" \
|
||||
# RUNNER_ARGS="<...>" \
|
||||
# TESTS="<...>" \ # Can be set to only run one or a few tests instead of the full test suite.
|
||||
# DOCKER_VERSION=<...> \
|
||||
# KUBERNETES_VERSION=<...> \
|
||||
# KUBERNETES_CNI_VERSION=<...> \
|
||||
# <...> # See also run-integration-test.sh for all variables and individual functions.
|
||||
# integration-tests
|
||||
#
|
||||
RUNNER_ARGS="-parallel" ./test/run-integration-tests.sh
|
||||
@@ -0,0 +1,37 @@
|
||||
# Weave Net - Weaving Containers into Applications
|
||||
|
||||
[](https://circleci.com/gh/weaveworks/weave)
|
||||
[](https://coveralls.io/r/weaveworks/weave)
|
||||
[](https://goreportcard.com/report/github.com/weaveworks/weave)
|
||||
[](https://hub.docker.com/r/weaveworks/weave/)
|
||||
|
||||
# About Weaveworks
|
||||
|
||||
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control Docker containers.
|
||||
|
||||
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, with over 8 million downloads to date. Weave Net enables you to get started with Docker clusters and portable apps in a fraction of the time required by other solutions.
|
||||
|
||||
# Weave Net
|
||||
|
||||
Weave Net creates a virtual network that connects Docker containers across multiple hosts and enables their automatic discovery. With Weave Net, portable microservices-based applications consisting of multiple containers can run anywhere: on one host, multiple hosts or even across cloud providers and data centers. Applications use the network just as if the containers were all plugged into the same network switch, without having to configure port mappings, ambassadors or links.
|
||||
|
||||
Services provided by application containers on the Weave network can be exposed to the outside world, regardless of where they are running. Similarly, existing internal systems can be opened to accept connections from application containers irrespective of their location.
|
||||
|
||||
## <a name="help"></a>Getting help
|
||||
|
||||
If you have any questions about, feedback for or a problem with Weave Net:
|
||||
|
||||
- Read [the Weave Net docs](https://www.weave.works/docs/net/latest/overview).
|
||||
- Invite yourself to the <a href="https://slack.weave.works/" target="_blank">Weave Users Slack</a>.
|
||||
- The channel for contributors and developers of Weave Net is [#net-dev](https://weave-community.slack.com/messages/net-dev/).
|
||||
- Join the <a href="https://www.meetup.com/pro/Weave/"> Weave User Group </a> and get invited to online talks, hands-on training and meetups in your area.
|
||||
- [File an issue](https://github.com/weaveworks/weave/issues/new).
|
||||
|
||||
We follow the [CNCF Code of Conduct](code-of-conduct.md).
|
||||
|
||||
Your feedback is always welcome!
|
||||
|
||||
## Further Information
|
||||
|
||||
* [Installing Weave Net](https://www.weave.works/docs/net/latest/install/installing-weave)
|
||||
* [Weave Net Product Overview](https://www.weave.works/products/weave-net)
|
||||
@@ -0,0 +1,12 @@
|
||||
# Security Policy
|
||||
|
||||
At Weaveworks we take security very seriously, and value our close relationship with members of the security community.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
As an Open Source project, only the latest code has all fixes and security patches.
|
||||
If you require a higher level of commitment, please [contact Weaveworks](https://www.weave.works/contact/) to discuss.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To submit a security bug report please e-mail us at security@weave.works.
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 5034 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user