init
This commit is contained in:
commit
e989f0a25f
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.cache/
|
||||
.container*
|
||||
.push*
|
||||
bin/
|
13
.header
Normal file
13
.header
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright YEAR the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
22
.travis.yml
Normal file
22
.travis.yml
Normal file
@ -0,0 +1,22 @@
|
||||
sudo: required
|
||||
|
||||
language: go
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
go:
|
||||
- 1.11.1
|
||||
|
||||
before_install:
|
||||
- go get -u golang.org/x/lint/golint
|
||||
|
||||
script:
|
||||
- make
|
||||
- make unit
|
||||
- make lint
|
||||
- make container
|
||||
|
||||
after_success:
|
||||
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
|
||||
- make push && make push-latest
|
6
Dockerfile
Normal file
6
Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM alpine
|
||||
MAINTAINER squat <lserven@gmail.com>
|
||||
RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
||||
apk add --no-cache ipset iptables wireguard-tools@testing
|
||||
COPY bin/kg /opt/bin/
|
||||
ENTRYPOINT ["/opt/bin/kg"]
|
202
LICENSE
Normal file
202
LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
130
Makefile
Normal file
130
Makefile
Normal file
@ -0,0 +1,130 @@
|
||||
.PHONY: all push container clean container-name container-latest push-latest fmt lint test unit vendor header
|
||||
|
||||
BINS := $(addprefix bin/,kg kgctl)
|
||||
PROJECT := kilo
|
||||
PKG := github.com/squat/$(PROJECT)
|
||||
REGISTRY ?= index.docker.io
|
||||
IMAGE ?= squat/$(PROJECT)
|
||||
|
||||
TAG := $(shell git describe --abbrev=0 --tags HEAD 2>/dev/null)
|
||||
COMMIT := $(shell git rev-parse HEAD)
|
||||
VERSION := $(COMMIT)
|
||||
ifneq ($(TAG),)
|
||||
ifeq ($(COMMIT), $(shell git rev-list -n1 $(TAG)))
|
||||
VERSION := $(TAG)
|
||||
endif
|
||||
endif
|
||||
DIRTY := $(shell test -z "$$(git diff --shortstat 2>/dev/null)" || echo -dirty)
|
||||
VERSION := $(VERSION)$(DIRTY)
|
||||
LD_FLAGS := -ldflags '-X $(PKG)/pkg/version.Version=$(VERSION)'
|
||||
SRC := $(shell find . -type f -name '*.go' -not -path "./vendor/*")
|
||||
GO_FILES ?= $$(find . -name '*.go' -not -path './vendor/*')
|
||||
GO_PKGS ?= $$(go list ./... | grep -v "$(PKG)/vendor")
|
||||
|
||||
BUILD_IMAGE ?= golang:1.11.1-alpine
|
||||
|
||||
all: build
|
||||
|
||||
build: $(BINS)
|
||||
|
||||
$(BINS): $(SRC) go.mod
|
||||
@mkdir -p bin
|
||||
@echo "building: $@"
|
||||
@docker run --rm \
|
||||
-u $$(id -u):$$(id -g) \
|
||||
-v $$(pwd):/$(PROJECT) \
|
||||
-w /$(PROJECT) \
|
||||
$(BUILD_IMAGE) \
|
||||
/bin/sh -c " \
|
||||
rm -rf ./.cache && \
|
||||
GOOS=linux \
|
||||
GOCACHE=./.cache \
|
||||
CGO_ENABLED=0 \
|
||||
go build -mod=vendor -o $@ \
|
||||
$(LD_FLAGS) \
|
||||
./cmd/$(@F)/... \
|
||||
"
|
||||
|
||||
fmt:
|
||||
@echo $(GO_PKGS)
|
||||
gofmt -w -s $(GO_FILES)
|
||||
|
||||
lint: header
|
||||
@echo 'go vet $(GO_PKGS)'
|
||||
@vet_res=$$(go vet $(GO_PKGS) 2>&1); if [ -n "$$vet_res" ]; then \
|
||||
echo ""; \
|
||||
echo "Go vet found issues. Please check the reported issues"; \
|
||||
echo "and fix them if necessary before submitting the code for review:"; \
|
||||
echo "$$vet_res"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo 'golint $(GO_PKGS)'
|
||||
@lint_res=$$(golint $(GO_PKGS)); if [ -n "$$lint_res" ]; then \
|
||||
echo ""; \
|
||||
echo "Golint found style issues. Please check the reported issues"; \
|
||||
echo "and fix them if necessary before submitting the code for review:"; \
|
||||
echo "$$lint_res"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo 'gofmt -d -s $(GO_FILES)'
|
||||
@fmt_res=$$(gofmt -d -s $(GO_FILES)); if [ -n "$$fmt_res" ]; then \
|
||||
echo ""; \
|
||||
echo "Gofmt found style issues. Please check the reported issues"; \
|
||||
echo "and fix them if necessary before submitting the code for review:"; \
|
||||
echo "$$fmt_res"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
unit:
|
||||
go test --race ./...
|
||||
|
||||
test: lint unit
|
||||
|
||||
header: .header
|
||||
@HEADER=$$(sed "s/YEAR/$$(date '+%Y')/" .header); \
|
||||
FILES=; \
|
||||
for f in $(GO_FILES); do \
|
||||
FILE=$$(head -n $$(wc -l .header | awk '{print $$1}') $$f); \
|
||||
[ "$$FILE" != "$$HEADER" ] && FILES="$$FILES$$f "; \
|
||||
done; \
|
||||
if [ -n "$$FILES" ]; then \
|
||||
printf 'the following files are missing the license header: %s\n' "$$FILES"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
container: .container-$(VERSION) container-name
|
||||
.container-$(VERSION): $(BINS) Dockerfile
|
||||
@docker build -t $(IMAGE):$(VERSION) .
|
||||
@docker images -q $(IMAGE):$(VERSION) > $@
|
||||
|
||||
container-latest: .container-$(VERSION)
|
||||
@docker tag $(IMAGE):$(VERSION) $(IMAGE):latest
|
||||
@echo "container: $(IMAGE):latest"
|
||||
|
||||
container-name:
|
||||
@echo "container: $(IMAGE):$(VERSION)"
|
||||
|
||||
push: .push-$(VERSION) push-name
|
||||
.push-$(VERSION): .container-$(VERSION)
|
||||
@docker push $(REGISTRY)/$(IMAGE):$(VERSION)
|
||||
@docker images -q $(IMAGE):$(VERSION) > $@
|
||||
|
||||
push-latest: container-latest
|
||||
@docker push $(REGISTRY)/$(IMAGE):latest
|
||||
@echo "pushed: $(IMAGE):latest"
|
||||
|
||||
push-name:
|
||||
@echo "pushed: $(IMAGE):$(VERSION)"
|
||||
|
||||
clean: container-clean bin-clean
|
||||
rm -r .cache
|
||||
|
||||
container-clean:
|
||||
rm -rf .container-* .push-*
|
||||
|
||||
bin-clean:
|
||||
rm -rf bin
|
||||
|
||||
vendor:
|
||||
go mod tidy
|
||||
go mod vendor
|
88
README.md
Normal file
88
README.md
Normal file
@ -0,0 +1,88 @@
|
||||
<p align="center"><img src="./kilo.svg" width="150"></p>
|
||||
|
||||
# Kilo
|
||||
|
||||
Kilo is a multi-cloud network overlay built on WireGuard and designed for Kubernetes.
|
||||
|
||||
[![Build Status](https://travis-ci.org/squat/kilo.svg?branch=master)](https://travis-ci.org/squat/kilo)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/squat/kilo)](https://goreportcard.com/report/github.com/squat/kilo)
|
||||
|
||||
## Overview
|
||||
|
||||
Kilo connects nodes in a cluster by providing an encrypted layer 3 network that can span across data centers and public clouds.
|
||||
By allowing pools of nodes in different locations to communicate securely, Kilo enables the operation of multi-cloud clusters.
|
||||
|
||||
## How it works
|
||||
|
||||
Kilo uses [WireGuard](https://www.wireguard.com/), a performant and secure VPN, to create a mesh between the different logical locations in a cluster.
|
||||
The Kilo agent, `kg`, runs on every node in the cluster, setting up the public and private keys for the VPN as well as the necessary rules to route packets between locations.
|
||||
|
||||
Kilo can operate as an add-on complimenting the cluster-networking solution currently installed on a cluster.
|
||||
This means that if a cluster uses, for example, Calico for networking, Kilo can be installed on top to enable pools of nodes in different locations to join; Kilo will take care of the network between locations, while Calico will take care of the network within locations.
|
||||
|
||||
## Installing on Kubernetes
|
||||
|
||||
Kilo can be installed on any Kubernetes cluster either pre- or post-bring-up.
|
||||
|
||||
### Step 1: install WireGuard
|
||||
|
||||
Kilo requires the WireGuard kernel module on all nodes in the cluster.
|
||||
For most Linux distributions, this can be installed using the system package manager.
|
||||
For Container Linux, WireGuard can be easily installed using a DaemonSet:
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/squat/modulus/master/wireguard/daemonset.yaml
|
||||
```
|
||||
|
||||
### Step 2: open WireGuard port
|
||||
|
||||
The nodes in the mesh will require an open UDP port in order to communicate.
|
||||
By default, Kilo uses UDP port 51820.
|
||||
|
||||
### Step 3: specify locations
|
||||
|
||||
Kilo needs to know which nodes are in each location.
|
||||
If the cluster does not automatically set the [failure-domain.beta.kubernetes.io/region](https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#failure-domain-beta-kubernetes-io-region) node label, then the [kilo.squat.ai/location](./docs/annotations.md#location) annotation can be used.
|
||||
For example, the following snippet could be used to annotate all nodes with `GCP` in the name:
|
||||
|
||||
```shell
|
||||
for node in $(kubectl get nodes | grep -i gcp | awk '{print $1}'); do kubectl annotate node $node kilo.squat.ai/location="gcp"; done
|
||||
```
|
||||
|
||||
### Step 4: ensure nodes have public IP
|
||||
|
||||
At least one node in each location must have a public IP address.
|
||||
If the public IP address is not automatically configured on the node's Ethernet device, it can be manually specified using the [kilo.squat.ai/force-external-ip](./docs/annotations.md#force-external-ip) annotation.
|
||||
|
||||
### Step 5: install Kilo!
|
||||
|
||||
Kilo can be installed by deploying a DaemonSet to the cluster.
|
||||
|
||||
To run Kilo on kubeadm:
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/master/manifests/kilo-kubeadm.yaml
|
||||
```
|
||||
|
||||
To run Kilo on bootkube:
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/master/manifests/kilo-bootkube.yaml
|
||||
```
|
||||
|
||||
To run Kilo on Typhoon:
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/master/manifests/kilo-typhoon.yaml
|
||||
```
|
||||
|
||||
## Analysis
|
||||
|
||||
The topology of a Kilo network can be analyzed using the `kgctl` binary.
|
||||
For example, the `graph` command can be used to generate a graph of the network in Graphviz format:
|
||||
|
||||
```shell
|
||||
kgctl graph --kubeconfig=$KUBECONFIG | twopi -Tsvg > cluster.svg
|
||||
```
|
||||
|
||||
<img src="./cluster.svg">
|
217
cluster.svg
Normal file
217
cluster.svg
Normal file
@ -0,0 +1,217 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||
-->
|
||||
<!-- Title: kilo Pages: 1 -->
|
||||
<svg width="1004pt" height="966pt"
|
||||
viewBox="0.00 0.00 1003.56 965.53" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 961.5326)">
|
||||
<title>kilo</title>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-961.5326 999.5561,-961.5326 999.5561,4 -4,4"/>
|
||||
<text text-anchor="middle" x="497.7781" y="-942.3326" font-family="Times,serif" font-size="14.00" fill="#000000">10.4.0.0/16</text>
|
||||
<!-- ip-10-0-1-81 -->
|
||||
<g id="node1" class="node">
|
||||
<title>ip-10-0-1-81</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="344.169" cy="-606.994" rx="60.623" ry="58.8803"/>
|
||||
<text text-anchor="middle" x="344.169" y="-633.294" font-family="Times,serif" font-size="14.00" fill="#000000">aws</text>
|
||||
<text text-anchor="middle" x="344.169" y="-618.294" font-family="Times,serif" font-size="14.00" fill="#000000">ip-10-0-1-81</text>
|
||||
<text text-anchor="middle" x="344.169" y="-603.294" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.1.0/24</text>
|
||||
<text text-anchor="middle" x="344.169" y="-588.294" font-family="Times,serif" font-size="14.00" fill="#000000">10.0.1.81</text>
|
||||
<text text-anchor="middle" x="344.169" y="-573.294" font-family="Times,serif" font-size="14.00" fill="#000000">10.4.0.1</text>
|
||||
</g>
|
||||
<!-- ip-10-0-18-139 -->
|
||||
<g id="node2" class="node">
|
||||
<title>ip-10-0-18-139</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="615.0618" cy="-686.5353" rx="70.0071" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="615.0618" y="-705.3353" font-family="Times,serif" font-size="14.00" fill="#000000">aws</text>
|
||||
<text text-anchor="middle" x="615.0618" y="-690.3353" font-family="Times,serif" font-size="14.00" fill="#000000">ip-10-0-18-139</text>
|
||||
<text text-anchor="middle" x="615.0618" y="-675.3353" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.2.0/24</text>
|
||||
<text text-anchor="middle" x="615.0618" y="-660.3353" font-family="Times,serif" font-size="14.00" fill="#000000">10.0.18.139</text>
|
||||
</g>
|
||||
<!-- ip-10-0-1-81->ip-10-0-18-139 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>ip-10-0-1-81->ip-10-0-18-139</title>
|
||||
<path fill="none" stroke="#000000" d="M412.2945,-626.9975C451.454,-638.4957 500.6809,-652.9501 540.8243,-664.7372"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="413.1962,-623.6145 402.6152,-624.1554 411.2241,-630.331 413.1962,-623.6145"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="539.977,-668.1361 550.558,-667.5953 541.9492,-661.4197 539.977,-668.1361"/>
|
||||
</g>
|
||||
<!-- ip-10-0-25-19 -->
|
||||
<g id="node3" class="node">
|
||||
<title>ip-10-0-25-19</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="529.0553" cy="-820.3641" rx="65.1077" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="529.0553" y="-839.1641" font-family="Times,serif" font-size="14.00" fill="#000000">aws</text>
|
||||
<text text-anchor="middle" x="529.0553" y="-824.1641" font-family="Times,serif" font-size="14.00" fill="#000000">ip-10-0-25-19</text>
|
||||
<text text-anchor="middle" x="529.0553" y="-809.1641" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.0.0/24</text>
|
||||
<text text-anchor="middle" x="529.0553" y="-794.1641" font-family="Times,serif" font-size="14.00" fill="#000000">10.0.25.19</text>
|
||||
</g>
|
||||
<!-- ip-10-0-1-81->ip-10-0-25-19 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>ip-10-0-1-81->ip-10-0-25-19</title>
|
||||
<path fill="none" stroke="#000000" d="M390.0745,-659.9717C419.8286,-694.3098 458.2845,-738.6903 487.1993,-772.0598"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="392.5724,-657.5098 383.3786,-652.2443 387.2821,-662.0939 392.5724,-657.5098"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="484.7664,-774.5967 493.9602,-779.8622 490.0567,-770.0127 484.7664,-774.5967"/>
|
||||
</g>
|
||||
<!-- ip-10-0-30-70 -->
|
||||
<g id="node4" class="node">
|
||||
<title>ip-10-0-30-70</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="384.3486" cy="-886.4494" rx="65.1077" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="384.3486" y="-905.2494" font-family="Times,serif" font-size="14.00" fill="#000000">aws</text>
|
||||
<text text-anchor="middle" x="384.3486" y="-890.2494" font-family="Times,serif" font-size="14.00" fill="#000000">ip-10-0-30-70</text>
|
||||
<text text-anchor="middle" x="384.3486" y="-875.2494" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.4.0/24</text>
|
||||
<text text-anchor="middle" x="384.3486" y="-860.2494" font-family="Times,serif" font-size="14.00" fill="#000000">10.0.30.70</text>
|
||||
</g>
|
||||
<!-- ip-10-0-1-81->ip-10-0-30-70 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>ip-10-0-1-81->ip-10-0-30-70</title>
|
||||
<path fill="none" stroke="#000000" d="M354.0092,-675.4337C360.7548,-722.3504 369.6062,-783.9132 376.0059,-828.4244"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="357.4307,-674.6369 352.5431,-665.2369 350.5019,-675.6332 357.4307,-674.6369"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="372.567,-829.1005 377.4546,-838.5005 379.4958,-828.1042 372.567,-829.1005"/>
|
||||
</g>
|
||||
<!-- ip-10-0-37-193 -->
|
||||
<g id="node5" class="node">
|
||||
<title>ip-10-0-37-193</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="226.8853" cy="-863.8096" rx="70.0071" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="226.8853" y="-882.6096" font-family="Times,serif" font-size="14.00" fill="#000000">aws</text>
|
||||
<text text-anchor="middle" x="226.8853" y="-867.6096" font-family="Times,serif" font-size="14.00" fill="#000000">ip-10-0-37-193</text>
|
||||
<text text-anchor="middle" x="226.8853" y="-852.6096" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.3.0/24</text>
|
||||
<text text-anchor="middle" x="226.8853" y="-837.6096" font-family="Times,serif" font-size="14.00" fill="#000000">10.0.37.193</text>
|
||||
</g>
|
||||
<!-- ip-10-0-1-81->ip-10-0-37-193 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>ip-10-0-1-81->ip-10-0-37-193</title>
|
||||
<path fill="none" stroke="#000000" d="M315.3699,-670.0552C295.9976,-712.4747 270.7015,-767.8655 252.1201,-808.553"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="318.612,-671.3813 319.5825,-660.831 312.2446,-668.4733 318.612,-671.3813"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="248.8766,-807.23 247.9061,-817.7803 255.2441,-810.138 248.8766,-807.23"/>
|
||||
</g>
|
||||
<!-- ip-10-0-4-141 -->
|
||||
<g id="node6" class="node">
|
||||
<title>ip-10-0-4-141</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="106.6587" cy="-759.6326" rx="65.1077" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="106.6587" y="-778.4326" font-family="Times,serif" font-size="14.00" fill="#000000">aws</text>
|
||||
<text text-anchor="middle" x="106.6587" y="-763.4326" font-family="Times,serif" font-size="14.00" fill="#000000">ip-10-0-4-141</text>
|
||||
<text text-anchor="middle" x="106.6587" y="-748.4326" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.7.0/24</text>
|
||||
<text text-anchor="middle" x="106.6587" y="-733.4326" font-family="Times,serif" font-size="14.00" fill="#000000">10.0.4.141</text>
|
||||
</g>
|
||||
<!-- ip-10-0-1-81->ip-10-0-4-141 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>ip-10-0-1-81->ip-10-0-4-141</title>
|
||||
<path fill="none" stroke="#000000" d="M284.718,-645.2009C247.9717,-668.8163 201.034,-698.9813 164.5764,-722.4112"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="286.9881,-647.9024 293.5084,-639.5516 283.2036,-642.0137 286.9881,-647.9024"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="162.5411,-719.5587 156.0208,-727.9095 166.3256,-725.4475 162.5411,-719.5587"/>
|
||||
</g>
|
||||
<!-- ip-10-0-4-62 -->
|
||||
<g id="node7" class="node">
|
||||
<title>ip-10-0-4-62</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="61.8399" cy="-606.994" rx="60.623" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="61.8399" y="-625.794" font-family="Times,serif" font-size="14.00" fill="#000000">aws</text>
|
||||
<text text-anchor="middle" x="61.8399" y="-610.794" font-family="Times,serif" font-size="14.00" fill="#000000">ip-10-0-4-62</text>
|
||||
<text text-anchor="middle" x="61.8399" y="-595.794" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.5.0/24</text>
|
||||
<text text-anchor="middle" x="61.8399" y="-580.794" font-family="Times,serif" font-size="14.00" fill="#000000">10.0.4.62</text>
|
||||
</g>
|
||||
<!-- ip-10-0-1-81->ip-10-0-4-62 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>ip-10-0-1-81->ip-10-0-4-62</title>
|
||||
<path fill="none" stroke="#000000" d="M273.016,-606.994C230.2472,-606.994 175.9979,-606.994 133.1933,-606.994"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="273.2554,-610.4941 283.2554,-606.994 273.2554,-603.4941 273.2554,-610.4941"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="133.1311,-603.4941 123.1311,-606.994 133.131,-610.4941 133.1311,-603.4941"/>
|
||||
</g>
|
||||
<!-- ip-10-0-47-198 -->
|
||||
<g id="node8" class="node">
|
||||
<title>ip-10-0-47-198</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="106.6587" cy="-454.3554" rx="70.0071" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="106.6587" y="-473.1554" font-family="Times,serif" font-size="14.00" fill="#000000">aws</text>
|
||||
<text text-anchor="middle" x="106.6587" y="-458.1554" font-family="Times,serif" font-size="14.00" fill="#000000">ip-10-0-47-198</text>
|
||||
<text text-anchor="middle" x="106.6587" y="-443.1554" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.6.0/24</text>
|
||||
<text text-anchor="middle" x="106.6587" y="-428.1554" font-family="Times,serif" font-size="14.00" fill="#000000">10.0.47.198</text>
|
||||
</g>
|
||||
<!-- ip-10-0-1-81->ip-10-0-47-198 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>ip-10-0-1-81->ip-10-0-47-198</title>
|
||||
<path fill="none" stroke="#000000" d="M284.869,-568.8842C248.7668,-545.6827 202.7849,-516.1319 166.5717,-492.8591"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="283.2036,-571.9744 293.5084,-574.4364 286.9881,-566.0856 283.2036,-571.9744"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="168.3654,-489.8515 158.0606,-487.3894 164.5809,-495.7402 168.3654,-489.8515"/>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker0.squat.ai -->
|
||||
<g id="node9" class="node">
|
||||
<title>kilo-gcp-worker0.squat.ai</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="461.4528" cy="-350.1784" rx="109.7032" ry="58.8803"/>
|
||||
<text text-anchor="middle" x="461.4528" y="-376.4784" font-family="Times,serif" font-size="14.00" fill="#000000">gcp</text>
|
||||
<text text-anchor="middle" x="461.4528" y="-361.4784" font-family="Times,serif" font-size="14.00" fill="#000000">kilo-gcp-worker0.squat.ai</text>
|
||||
<text text-anchor="middle" x="461.4528" y="-346.4784" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.12.0/24</text>
|
||||
<text text-anchor="middle" x="461.4528" y="-331.4784" font-family="Times,serif" font-size="14.00" fill="#000000">10.1.96.8</text>
|
||||
<text text-anchor="middle" x="461.4528" y="-316.4784" font-family="Times,serif" font-size="14.00" fill="#000000">10.4.0.2</text>
|
||||
</g>
|
||||
<!-- ip-10-0-1-81->kilo-gcp-worker0.squat.ai -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>ip-10-0-1-81->kilo-gcp-worker0.squat.ai</title>
|
||||
<path fill="none" stroke="#000000" d="M372.9731,-543.9219C390.6038,-505.3159 413.1398,-455.969 431.0552,-416.7398"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="369.726,-542.6068 368.7555,-553.1571 376.0935,-545.5147 369.726,-542.6068"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="434.2995,-418.061 435.27,-407.5107 427.9321,-415.153 434.2995,-418.061"/>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker1.squat.ai -->
|
||||
<g id="node10" class="node">
|
||||
<title>kilo-gcp-worker1.squat.ai</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="109.6016" cy="-93.3629" rx="109.7032" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="109.6016" y="-112.1629" font-family="Times,serif" font-size="14.00" fill="#000000">gcp</text>
|
||||
<text text-anchor="middle" x="109.6016" y="-97.1629" font-family="Times,serif" font-size="14.00" fill="#000000">kilo-gcp-worker1.squat.ai</text>
|
||||
<text text-anchor="middle" x="109.6016" y="-82.1629" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.8.0/24</text>
|
||||
<text text-anchor="middle" x="109.6016" y="-67.1629" font-family="Times,serif" font-size="14.00" fill="#000000">10.1.96.7</text>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker0.squat.ai->kilo-gcp-worker1.squat.ai -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>kilo-gcp-worker0.squat.ai->kilo-gcp-worker1.squat.ai</title>
|
||||
<path fill="none" stroke="#000000" d="M388.2111,-296.7195C325.2056,-250.7319 235.1652,-185.0116 174.6581,-140.8476"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="386.367,-299.7067 396.5078,-302.7752 390.494,-294.0526 386.367,-299.7067"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="176.463,-137.8318 166.3222,-134.7632 172.336,-143.4859 176.463,-137.8318"/>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker2.squat.ai -->
|
||||
<g id="node11" class="node">
|
||||
<title>kilo-gcp-worker2.squat.ai</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="424.5283" cy="-48.0833" rx="109.7032" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="424.5283" y="-66.8833" font-family="Times,serif" font-size="14.00" fill="#000000">gcp</text>
|
||||
<text text-anchor="middle" x="424.5283" y="-51.8833" font-family="Times,serif" font-size="14.00" fill="#000000">kilo-gcp-worker2.squat.ai</text>
|
||||
<text text-anchor="middle" x="424.5283" y="-36.8833" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.10.0/24</text>
|
||||
<text text-anchor="middle" x="424.5283" y="-21.8833" font-family="Times,serif" font-size="14.00" fill="#000000">10.1.96.4</text>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker0.squat.ai->kilo-gcp-worker2.squat.ai -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>kilo-gcp-worker0.squat.ai->kilo-gcp-worker2.squat.ai</title>
|
||||
<path fill="none" stroke="#000000" d="M453.0351,-281.3102C446.5861,-228.5483 437.7864,-156.5538 431.6768,-106.5685"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="449.6021,-282.0718 454.2896,-291.5733 456.5504,-281.2225 449.6021,-282.0718"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="435.1415,-106.0659 430.454,-96.5644 428.1932,-106.9152 435.1415,-106.0659"/>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker3.squat.ai -->
|
||||
<g id="node12" class="node">
|
||||
<title>kilo-gcp-worker3.squat.ai</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="713.9415" cy="-180.2539" rx="109.7032" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="713.9415" y="-199.0539" font-family="Times,serif" font-size="14.00" fill="#000000">gcp</text>
|
||||
<text text-anchor="middle" x="713.9415" y="-184.0539" font-family="Times,serif" font-size="14.00" fill="#000000">kilo-gcp-worker3.squat.ai</text>
|
||||
<text text-anchor="middle" x="713.9415" y="-169.0539" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.9.0/24</text>
|
||||
<text text-anchor="middle" x="713.9415" y="-154.0539" font-family="Times,serif" font-size="14.00" fill="#000000">10.1.96.5</text>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker0.squat.ai->kilo-gcp-worker3.squat.ai -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>kilo-gcp-worker0.squat.ai->kilo-gcp-worker3.squat.ai</title>
|
||||
<path fill="none" stroke="#000000" d="M538.5422,-298.2974C572.509,-275.4378 612.1018,-248.7919 645.0663,-226.6068"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="536.1219,-295.7074 529.7798,-304.1944 540.0302,-301.5147 536.1219,-295.7074"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="647.3941,-229.259 653.7361,-220.772 643.4857,-223.4517 647.3941,-229.259"/>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker4.squat.ai -->
|
||||
<g id="node13" class="node">
|
||||
<title>kilo-gcp-worker4.squat.ai</title>
|
||||
<ellipse fill="none" stroke="#000000" cx="885.9546" cy="-447.9114" rx="109.7032" ry="48.1667"/>
|
||||
<text text-anchor="middle" x="885.9546" y="-466.7114" font-family="Times,serif" font-size="14.00" fill="#000000">gcp</text>
|
||||
<text text-anchor="middle" x="885.9546" y="-451.7114" font-family="Times,serif" font-size="14.00" fill="#000000">kilo-gcp-worker4.squat.ai</text>
|
||||
<text text-anchor="middle" x="885.9546" y="-436.7114" font-family="Times,serif" font-size="14.00" fill="#000000">10.2.11.0/24</text>
|
||||
<text text-anchor="middle" x="885.9546" y="-421.7114" font-family="Times,serif" font-size="14.00" fill="#000000">10.1.96.6</text>
|
||||
</g>
|
||||
<!-- kilo-gcp-worker0.squat.ai->kilo-gcp-worker4.squat.ai -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>kilo-gcp-worker0.squat.ai->kilo-gcp-worker4.squat.ai</title>
|
||||
<path fill="none" stroke="#000000" d="M572.0986,-375.6524C635.8498,-390.3298 715.6064,-408.6922 778.7656,-423.2333"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="572.8532,-372.2347 562.3229,-373.4018 571.2826,-379.0562 572.8532,-372.2347"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="778.2244,-426.7002 788.7548,-425.5331 779.795,-419.8787 778.2244,-426.7002"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 17 KiB |
232
cmd/kg/main.go
Normal file
232
cmd/kg/main.go
Normal file
@ -0,0 +1,232 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"github.com/oklog/run"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/squat/kilo/pkg/k8s"
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
"github.com/squat/kilo/pkg/version"
|
||||
)
|
||||
|
||||
const (
|
||||
logLevelAll = "all"
|
||||
logLevelDebug = "debug"
|
||||
logLevelInfo = "info"
|
||||
logLevelWarn = "warn"
|
||||
logLevelError = "error"
|
||||
logLevelNone = "none"
|
||||
)
|
||||
|
||||
var (
|
||||
availableBackends = strings.Join([]string{
|
||||
k8s.Backend,
|
||||
}, ", ")
|
||||
availableEncapsulations = strings.Join([]string{
|
||||
string(mesh.NeverEncapsulate),
|
||||
string(mesh.CrossSubnetEncapsulate),
|
||||
string(mesh.AlwaysEncapsulate),
|
||||
}, ", ")
|
||||
availableGranularities = strings.Join([]string{
|
||||
string(mesh.DataCenterGranularity),
|
||||
string(mesh.NodeGranularity),
|
||||
}, ", ")
|
||||
availableLogLevels = strings.Join([]string{
|
||||
logLevelAll,
|
||||
logLevelDebug,
|
||||
logLevelInfo,
|
||||
logLevelWarn,
|
||||
logLevelError,
|
||||
logLevelNone,
|
||||
}, ", ")
|
||||
)
|
||||
|
||||
// Main is the principal function for the binary, wrapped only by `main` for convenience.
|
||||
func Main() error {
|
||||
backend := flag.String("backend", k8s.Backend, fmt.Sprintf("The backend for the mesh. Possible values: %s", availableBackends))
|
||||
encapsulate := flag.String("encapsulate", string(mesh.AlwaysEncapsulate), fmt.Sprintf("When should Kilo encapsulate packets within a location. Possible values: %s", availableEncapsulations))
|
||||
granularity := flag.String("mesh-granularity", string(mesh.DataCenterGranularity), fmt.Sprintf("The granularity of the network mesh to create. Possible values: %s", availableGranularities))
|
||||
kubeconfig := flag.String("kubeconfig", "", "Path to kubeconfig.")
|
||||
hostname := flag.String("hostname", "", "Hostname of the node on which this process is running.")
|
||||
listen := flag.String("listen", "localhost:1107", "The address at which to listen for health and metrics.")
|
||||
local := flag.Bool("local", true, "Should Kilo manage routes within a location.")
|
||||
logLevel := flag.String("log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", availableLogLevels))
|
||||
master := flag.String("master", "", "The address of the Kubernetes API server (overrides any value in kubeconfig).")
|
||||
port := flag.Int("port", 51820, "The port over which WireGuard peers should communicate.")
|
||||
subnet := flag.String("subnet", "10.4.0.0/16", "CIDR from which to allocate addressees to WireGuard interfaces.")
|
||||
printVersion := flag.Bool("version", false, "Print version and exit")
|
||||
flag.Parse()
|
||||
|
||||
if *printVersion {
|
||||
fmt.Println(version.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, s, err := net.ParseCIDR(*subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse %q as CIDR: %v", *subnet, err)
|
||||
}
|
||||
|
||||
if *hostname == "" {
|
||||
var err error
|
||||
*hostname, err = os.Hostname()
|
||||
if *hostname == "" || err != nil {
|
||||
return errors.New("failed to determine hostname")
|
||||
}
|
||||
}
|
||||
|
||||
logger := log.NewJSONLogger(log.NewSyncWriter(os.Stdout))
|
||||
switch *logLevel {
|
||||
case logLevelAll:
|
||||
logger = level.NewFilter(logger, level.AllowAll())
|
||||
case logLevelDebug:
|
||||
logger = level.NewFilter(logger, level.AllowDebug())
|
||||
case logLevelInfo:
|
||||
logger = level.NewFilter(logger, level.AllowInfo())
|
||||
case logLevelWarn:
|
||||
logger = level.NewFilter(logger, level.AllowWarn())
|
||||
case logLevelError:
|
||||
logger = level.NewFilter(logger, level.AllowError())
|
||||
case logLevelNone:
|
||||
logger = level.NewFilter(logger, level.AllowNone())
|
||||
default:
|
||||
return fmt.Errorf("log level %v unknown; posible values are: %s", *logLevel, availableLogLevels)
|
||||
}
|
||||
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
|
||||
logger = log.With(logger, "caller", log.DefaultCaller)
|
||||
|
||||
e := mesh.Encapsulate(*encapsulate)
|
||||
switch e {
|
||||
case mesh.NeverEncapsulate:
|
||||
case mesh.CrossSubnetEncapsulate:
|
||||
case mesh.AlwaysEncapsulate:
|
||||
default:
|
||||
return fmt.Errorf("encapsulation %v unknown; posible values are: %s", *encapsulate, availableEncapsulations)
|
||||
}
|
||||
|
||||
gr := mesh.Granularity(*granularity)
|
||||
switch gr {
|
||||
case mesh.DataCenterGranularity:
|
||||
case mesh.NodeGranularity:
|
||||
default:
|
||||
return fmt.Errorf("mesh granularity %v unknown; posible values are: %s", *granularity, availableGranularities)
|
||||
}
|
||||
|
||||
var b mesh.Backend
|
||||
switch *backend {
|
||||
case k8s.Backend:
|
||||
config, err := clientcmd.BuildConfigFromFlags(*master, *kubeconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Kubernetes config: %v", err)
|
||||
}
|
||||
client := kubernetes.NewForConfigOrDie(config)
|
||||
b = k8s.New(client)
|
||||
default:
|
||||
return fmt.Errorf("backend %v unknown; posible values are: %s", *backend, availableBackends)
|
||||
}
|
||||
|
||||
m, err := mesh.New(b, e, gr, *hostname, *port, s, *local, log.With(logger, "component", "kilo"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Kilo mesh: %v", err)
|
||||
}
|
||||
|
||||
r := prometheus.NewRegistry()
|
||||
r.MustRegister(
|
||||
prometheus.NewGoCollector(),
|
||||
prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}),
|
||||
)
|
||||
m.RegisterMetrics(r)
|
||||
|
||||
var g run.Group
|
||||
{
|
||||
// Run the HTTP server.
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
mux.Handle("/metrics", promhttp.HandlerFor(r, promhttp.HandlerOpts{}))
|
||||
l, err := net.Listen("tcp", *listen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on %s: %v", *listen, err)
|
||||
}
|
||||
|
||||
g.Add(func() error {
|
||||
if err := http.Serve(l, mux); err != nil && err != http.ErrServerClosed {
|
||||
return fmt.Errorf("error: server exited unexpectedly: %v", err)
|
||||
}
|
||||
return nil
|
||||
}, func(error) {
|
||||
l.Close()
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
// Start the mesh.
|
||||
g.Add(func() error {
|
||||
logger.Log("msg", fmt.Sprintf("Starting Kilo network mesh '%v'.", version.Version))
|
||||
if err := m.Run(); err != nil {
|
||||
return fmt.Errorf("error: Kilo exited unexpectedly: %v", err)
|
||||
}
|
||||
return nil
|
||||
}, func(error) {
|
||||
m.Stop()
|
||||
})
|
||||
}
|
||||
{
|
||||
// Exit gracefully on SIGINT and SIGTERM.
|
||||
term := make(chan os.Signal, 1)
|
||||
signal.Notify(term, syscall.SIGINT, syscall.SIGTERM)
|
||||
cancel := make(chan struct{})
|
||||
g.Add(func() error {
|
||||
for {
|
||||
select {
|
||||
case <-term:
|
||||
logger.Log("msg", "caught interrupt; gracefully cleaning up; see you next time!")
|
||||
return nil
|
||||
case <-cancel:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}, func(error) {
|
||||
close(cancel)
|
||||
})
|
||||
}
|
||||
|
||||
return g.Run()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := Main(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
58
cmd/kgctl/graph.go
Normal file
58
cmd/kgctl/graph.go
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
)
|
||||
|
||||
func newGraph() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "graph",
|
||||
Short: "Generates a graph of the Kilo network",
|
||||
Long: "",
|
||||
RunE: runGraph,
|
||||
}
|
||||
}
|
||||
|
||||
func runGraph(_ *cobra.Command, _ []string) error {
|
||||
ns, err := opts.backend.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list nodes: %v", err)
|
||||
}
|
||||
var hostname string
|
||||
if len(ns) != 0 {
|
||||
hostname = ns[0].Name
|
||||
}
|
||||
nodes := make(map[string]*mesh.Node)
|
||||
for _, n := range ns {
|
||||
if n.Ready() {
|
||||
nodes[n.Name] = n
|
||||
}
|
||||
}
|
||||
t, err := mesh.NewTopology(nodes, opts.granularity, hostname, 0, []byte{}, opts.subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create topology: %v", err)
|
||||
}
|
||||
g, err := t.Dot()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate graph: %v", err)
|
||||
}
|
||||
fmt.Println(g)
|
||||
return nil
|
||||
}
|
124
cmd/kgctl/main.go
Normal file
124
cmd/kgctl/main.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/squat/kilo/pkg/k8s"
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
"github.com/squat/kilo/pkg/version"
|
||||
)
|
||||
|
||||
const (
|
||||
logLevelAll = "all"
|
||||
logLevelDebug = "debug"
|
||||
logLevelInfo = "info"
|
||||
logLevelWarn = "warn"
|
||||
logLevelError = "error"
|
||||
logLevelNone = "none"
|
||||
)
|
||||
|
||||
var (
|
||||
availableBackends = strings.Join([]string{
|
||||
k8s.Backend,
|
||||
}, ", ")
|
||||
availableGranularities = strings.Join([]string{
|
||||
string(mesh.DataCenterGranularity),
|
||||
string(mesh.NodeGranularity),
|
||||
}, ", ")
|
||||
availableLogLevels = strings.Join([]string{
|
||||
logLevelAll,
|
||||
logLevelDebug,
|
||||
logLevelInfo,
|
||||
logLevelWarn,
|
||||
logLevelError,
|
||||
logLevelNone,
|
||||
}, ", ")
|
||||
opts struct {
|
||||
backend mesh.Backend
|
||||
granularity mesh.Granularity
|
||||
subnet *net.IPNet
|
||||
}
|
||||
backend string
|
||||
granularity string
|
||||
kubeconfig string
|
||||
subnet string
|
||||
)
|
||||
|
||||
func runRoot(_ *cobra.Command, _ []string) error {
|
||||
_, s, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse %q as CIDR: %v", subnet, err)
|
||||
}
|
||||
opts.subnet = s
|
||||
|
||||
opts.granularity = mesh.Granularity(granularity)
|
||||
switch opts.granularity {
|
||||
case mesh.DataCenterGranularity:
|
||||
case mesh.NodeGranularity:
|
||||
default:
|
||||
return fmt.Errorf("mesh granularity %v unknown; posible values are: %s", granularity, availableGranularities)
|
||||
}
|
||||
|
||||
switch backend {
|
||||
case k8s.Backend:
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Kubernetes config: %v", err)
|
||||
}
|
||||
client := kubernetes.NewForConfigOrDie(config)
|
||||
opts.backend = k8s.New(client)
|
||||
default:
|
||||
return fmt.Errorf("backend %v unknown; posible values are: %s", backend, availableBackends)
|
||||
}
|
||||
|
||||
if err := opts.backend.Init(make(chan struct{})); err != nil {
|
||||
return fmt.Errorf("failed to initialize backend: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "kgctl",
|
||||
Short: "Manage a Kilo network",
|
||||
Long: "",
|
||||
PersistentPreRunE: runRoot,
|
||||
Version: version.Version,
|
||||
}
|
||||
cmd.PersistentFlags().StringVar(&backend, "backend", k8s.Backend, fmt.Sprintf("The backend for the mesh. Possible values: %s", availableBackends))
|
||||
cmd.PersistentFlags().StringVar(&granularity, "mesh-granularity", string(mesh.DataCenterGranularity), fmt.Sprintf("The granularity of the network mesh to create. Possible values: %s", availableGranularities))
|
||||
cmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", "", "Path to kubeconfig.")
|
||||
cmd.PersistentFlags().StringVar(&subnet, "subnet", "10.4.0.0/16", "CIDR from which to allocate addressees to WireGuard interfaces.")
|
||||
|
||||
for _, subCmd := range []*cobra.Command{
|
||||
newGraph(),
|
||||
} {
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
33
docs/annotations.md
Normal file
33
docs/annotations.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Annotations
|
||||
The following annotations can be added to any Kubernetes Node object to configure the Kilo network.
|
||||
|
||||
|Name|type|example|
|
||||
|----|----|-------|
|
||||
|[kilo.squat.ai/force-external-ip](#force-external-ip)|CIDR|`"55.55.55.55/32"`|
|
||||
|[kilo.squat.ai/leader](#leader)|string|`""`|
|
||||
|[kilo.squat.ai/location](#location)|string|`"gcp-east"`|
|
||||
|
||||
### force-external-ip
|
||||
Kilo requires at least one node in each location to have a publicly accessible IP address in order to create links to other locations.
|
||||
The Kilo agent running on each node will use heuristics to automatically detect an external IP address for the node; however, in some circumstances it may be necessary to explicitly configure the IP address, for example:
|
||||
* _no automatic public IP on ethernet device_: on some cloud providers it is common for nodes to be allocated a public IP address but for the Ethernet devices to only be automatically configured with the private network address; in this case the allocated public IP address should be specified;
|
||||
* _multiple public IP addresses_: if a node has multiple public IPs but one is preferred, then the preferred IP address should be specified;
|
||||
* _IPv6_: if a node has both public IPv4 and IPv6 addresses and the Kilo network should operate over IPv6, then the IPv6 address should be specified;
|
||||
|
||||
### leader
|
||||
By default, Kilo creates a network mesh at the data-center granularity.
|
||||
This means that one leader node is selected from each location to be an edge server and act as the gateway to other locations; the network topology will be a full mesh between leaders.
|
||||
Kilo automatically selects the leader for each location in a stable and deterministic manner to avoid churn in the network configuration, while giving preference to nodes that are known to have public IP addresses.
|
||||
In some situations it may be desirable to manually select the leader for a location, for example:
|
||||
* _firewall_: Kilo requires an open UDP port, which defaults to 51820, to communicate between locations; if only one node is configured to have that port open, then that node should be given the leader annotation;
|
||||
* _bandwidth_: if certain nodes in the cluster have a higher bandwidth or lower latency Internet connection, then those nodes should be given the leader annotation;
|
||||
|
||||
_Note_: multiple nodes within a single location can be given the leader annotation; in this case, Kilo will select one leader from the set of annotated nodes.
|
||||
|
||||
### location
|
||||
Kilo allows nodes in different logical or physical locations to route packets to one-another.
|
||||
In order to know what connections to create, Kilo needs to know which nodes are in each location.
|
||||
Kilo will try to infer each node's location from the [failure-domain.beta.kubernetes.io/region](https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#failure-domain-beta-kubernetes-io-region) node label.
|
||||
If the label is not present for a node, for example if running a bare-metal cluster or on an unsupported cloud provider, then the location annotation should be specified.
|
||||
|
||||
_Note_: all nodes without a defined location will be considered to be in the default location `""`.
|
61
go.mod
Normal file
61
go.mod
Normal file
@ -0,0 +1,61 @@
|
||||
module github.com/squat/kilo
|
||||
|
||||
require (
|
||||
github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
|
||||
github.com/coreos/go-iptables v0.4.0
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-kit/kit v0.8.0
|
||||
github.com/go-logfmt/logfmt v0.4.0 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/gogo/protobuf v1.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff // indirect
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
|
||||
github.com/googleapis/gnostic v0.2.0 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.5 // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/oklog/run v1.0.0
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v0.9.1
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 // indirect
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a // indirect
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.3 // indirect
|
||||
github.com/stretchr/testify v1.2.2 // indirect
|
||||
github.com/vishvananda/netlink v1.0.0
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 // indirect
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
|
||||
google.golang.org/appengine v1.3.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.41.0
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
k8s.io/api v0.0.0-20181130031204-d04500c8c3dd
|
||||
k8s.io/apimachinery v0.0.0-20181215012845-4d029f033399
|
||||
k8s.io/client-go v10.0.0+incompatible
|
||||
k8s.io/klog v0.1.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be // indirect
|
||||
sigs.k8s.io/yaml v1.1.0 // indirect
|
||||
)
|
139
go.sum
Normal file
139
go.sum
Normal file
@ -0,0 +1,139 @@
|
||||
github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310 h1:t+qxRrRtwNiUYA+Xh2jSXhoG2grnMCMKX4Fg6lx9X1U=
|
||||
github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/coreos/go-iptables v0.4.0 h1:wh4UbVs8DhLUbpyq97GLJDKrQMjEDD63T1xE4CrsKzQ=
|
||||
github.com/coreos/go-iptables v0.4.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu09SJ6W3NCsHG7crFaJILQ22Gozp3lg=
|
||||
github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
|
||||
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM=
|
||||
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/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/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
||||
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/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/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4=
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
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.41.0 h1:Ka3ViY6gNYSKiVy71zXBEqKplnV35ImDLVG+8uoIklE=
|
||||
gopkg.in/ini.v1 v1.41.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=
|
||||
k8s.io/api v0.0.0-20181130031204-d04500c8c3dd h1:5aHsneN62ehs/tdtS9tWZlhVk68V7yms/Qw7nsGmvCA=
|
||||
k8s.io/api v0.0.0-20181130031204-d04500c8c3dd/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
|
||||
k8s.io/apimachinery v0.0.0-20181215012845-4d029f033399 h1:xdXaRQ7uNX4x6NpvxXASvlVXtKa8+WbCXK7Hjr6XZ6c=
|
||||
k8s.io/apimachinery v0.0.0-20181215012845-4d029f033399/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
||||
k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34=
|
||||
k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
||||
k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk=
|
||||
k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be h1:aWEq4nbj7HRJ0mtKYjNSk/7X28Tl6TI6FeG8gKF+r7Q=
|
||||
k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
2
kilo.svg
Normal file
2
kilo.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<!-- Source: https://github.com/FortAwesome/Font-Awesome/blob/master/svgs/solid/weight-hanging.svg -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M510.28 445.86l-73.03-292.13c-3.8-15.19-16.44-25.72-30.87-25.72h-60.25c3.57-10.05 5.88-20.72 5.88-32 0-53.02-42.98-96-96-96s-96 42.98-96 96c0 11.28 2.3 21.95 5.88 32h-60.25c-14.43 0-27.08 10.54-30.87 25.72L1.72 445.86C-6.61 479.17 16.38 512 48.03 512h415.95c31.64 0 54.63-32.83 46.3-66.14zM256 128c-17.64 0-32-14.36-32-32s14.36-32 32-32 32 14.36 32 32-14.36 32-32 32z"/></svg>
|
After Width: | Height: | Size: 551 B |
34
manifests/kilo-bootkube.yaml
Normal file
34
manifests/kilo-bootkube.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: kilo
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: kilo
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kilo
|
||||
spec:
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- name: kilo
|
||||
image: squat/kilo
|
||||
args:
|
||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: kubeconfig
|
||||
mountPath: /etc/kubernetes/kubeconfig
|
||||
readOnly: true
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
operator: Exists
|
||||
- effect: NoExecute
|
||||
operator: Exists
|
||||
volumes:
|
||||
- name: kubeconfig
|
||||
hostPath:
|
||||
path: /etc/kubernetes/kubeconfig
|
37
manifests/kilo-kubeadm.yaml
Normal file
37
manifests/kilo-kubeadm.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: kilo
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: kilo
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kilo
|
||||
spec:
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- name: kilo
|
||||
image: squat/kilo
|
||||
args:
|
||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: kubeconfig
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
operator: Exists
|
||||
- effect: NoExecute
|
||||
operator: Exists
|
||||
volumes:
|
||||
- name: kubeconfig
|
||||
configMap:
|
||||
name: kube-proxy
|
||||
items:
|
||||
- key: kubeconfig.conf
|
||||
path: kubeconfig
|
34
manifests/kilo-typhoon.yaml
Normal file
34
manifests/kilo-typhoon.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: kilo
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: kilo
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kilo
|
||||
spec:
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- name: kilo
|
||||
image: squat/kilo
|
||||
args:
|
||||
- --kubeconfig=/etc/kubernetes/kubeconfig
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: kubeconfig
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
operator: Exists
|
||||
- effect: NoExecute
|
||||
operator: Exists
|
||||
volumes:
|
||||
- name: kubeconfig
|
||||
configMap:
|
||||
name: kubeconfig-in-cluster
|
59
pkg/iproute/ipip.go
Normal file
59
pkg/iproute/ipip.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package iproute
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const (
|
||||
ipipHeaderSize = 20
|
||||
tunnelName = "tunl0"
|
||||
)
|
||||
|
||||
// NewIPIP creates an IPIP interface using the base interface
|
||||
// to derive the tunnel's MTU.
|
||||
func NewIPIP(baseIndex int) (int, error) {
|
||||
link, err := netlink.LinkByName(tunnelName)
|
||||
if err != nil {
|
||||
// If we failed to find the tunnel, then it probably simply does not exist.
|
||||
cmd := exec.Command("ip", "tunnel", "add", tunnelName, "mode", "ipip")
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return 0, fmt.Errorf("failed to create IPIP tunnel: %s", stderr.String())
|
||||
}
|
||||
link, err = netlink.LinkByName(tunnelName)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get tunnel device: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
base, err := netlink.LinkByIndex(baseIndex)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get base device: %v", err)
|
||||
}
|
||||
|
||||
mtu := base.Attrs().MTU - ipipHeaderSize
|
||||
if err = netlink.LinkSetMTU(link, mtu); err != nil {
|
||||
return 0, fmt.Errorf("failed to set tunnel MTU: %v", err)
|
||||
}
|
||||
|
||||
return link.Attrs().Index, nil
|
||||
}
|
70
pkg/iproute/iproute.go
Normal file
70
pkg/iproute/iproute.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package iproute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// RemoveInterface removes an interface.
|
||||
func RemoveInterface(index int) error {
|
||||
link, err := netlink.LinkByIndex(index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get link: %s", err)
|
||||
}
|
||||
return netlink.LinkDel(link)
|
||||
}
|
||||
|
||||
// Set sets the interface up or down.
|
||||
func Set(index int, up bool) error {
|
||||
link, err := netlink.LinkByIndex(index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get link: %s", err)
|
||||
}
|
||||
if up {
|
||||
return netlink.LinkSetUp(link)
|
||||
}
|
||||
return netlink.LinkSetDown(link)
|
||||
}
|
||||
|
||||
// SetAddress sets the IP address of an interface.
|
||||
func SetAddress(index int, cidr *net.IPNet) error {
|
||||
link, err := netlink.LinkByIndex(index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get link: %s", err)
|
||||
}
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l := len(addrs)
|
||||
for _, addr := range addrs {
|
||||
if addr.IP.Equal(cidr.IP) && addr.Mask.String() == cidr.Mask.String() {
|
||||
continue
|
||||
}
|
||||
if err := netlink.AddrDel(link, &addr); err != nil {
|
||||
return fmt.Errorf("failed to delete address: %s", err)
|
||||
}
|
||||
l--
|
||||
}
|
||||
// The only address left is the desired address, so quit.
|
||||
if l == 1 {
|
||||
return nil
|
||||
}
|
||||
return netlink.AddrReplace(link, &netlink.Addr{IPNet: cidr})
|
||||
}
|
199
pkg/ipset/ipset.go
Normal file
199
pkg/ipset/ipset.go
Normal file
@ -0,0 +1,199 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Set represents an ipset.
|
||||
// Set can safely be used concurrently.
|
||||
type Set struct {
|
||||
errors chan error
|
||||
hosts map[string]struct{}
|
||||
mu sync.Mutex
|
||||
name string
|
||||
subscribed bool
|
||||
|
||||
// Make these functions fields to allow
|
||||
// for testing.
|
||||
add func(string) error
|
||||
del func(string) error
|
||||
}
|
||||
|
||||
func setExists(name string) (bool, error) {
|
||||
cmd := exec.Command("ipset", "list", "-n")
|
||||
var stderr, stdout bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false, fmt.Errorf("failed to check for set %s: %s", name, stderr.String())
|
||||
}
|
||||
return bytes.Contains(stdout.Bytes(), []byte(name)), nil
|
||||
}
|
||||
|
||||
func hostInSet(set, name string) (bool, error) {
|
||||
cmd := exec.Command("ipset", "list", set)
|
||||
var stderr, stdout bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false, fmt.Errorf("failed to check for host %s: %s", name, stderr.String())
|
||||
}
|
||||
return bytes.Contains(stdout.Bytes(), []byte(name)), nil
|
||||
}
|
||||
|
||||
// New generates a new ipset.
|
||||
func New(name string) *Set {
|
||||
return &Set{
|
||||
errors: make(chan error),
|
||||
hosts: make(map[string]struct{}),
|
||||
name: name,
|
||||
|
||||
add: func(ip string) error {
|
||||
ok, err := hostInSet(name, ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
cmd := exec.Command("ipset", "add", name, ip)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to add host %s to set %s: %s", ip, name, stderr.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
del: func(ip string) error {
|
||||
ok, err := hostInSet(name, ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
cmd := exec.Command("ipset", "del", name, ip)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to remove host %s from set %s: %s", ip, name, stderr.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run watches for changes to the ipset and reconciles
|
||||
// the ipset against the desired state.
|
||||
func (s *Set) Run(stop <-chan struct{}) (<-chan error, error) {
|
||||
s.mu.Lock()
|
||||
if s.subscribed {
|
||||
s.mu.Unlock()
|
||||
return s.errors, nil
|
||||
}
|
||||
// Ensure a given instance only subscribes once.
|
||||
s.subscribed = true
|
||||
s.mu.Unlock()
|
||||
go func() {
|
||||
defer close(s.errors)
|
||||
for {
|
||||
select {
|
||||
case <-time.After(2 * time.Second):
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
ok, err := setExists(s.name)
|
||||
if err != nil {
|
||||
nonBlockingSend(s.errors, err)
|
||||
}
|
||||
// The set does not exist so wait and try again later.
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s.mu.Lock()
|
||||
for h := range s.hosts {
|
||||
if err := s.add(h); err != nil {
|
||||
nonBlockingSend(s.errors, err)
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
return s.errors, nil
|
||||
}
|
||||
|
||||
// CleanUp will clean up any hosts added to the set.
|
||||
func (s *Set) CleanUp() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for h := range s.hosts {
|
||||
if err := s.del(h); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(s.hosts, h)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set idempotently overwrites any hosts previously defined
|
||||
// for the ipset with the given hosts.
|
||||
func (s *Set) Set(hosts []net.IP) error {
|
||||
h := make(map[string]struct{})
|
||||
for _, host := range hosts {
|
||||
if host == nil {
|
||||
continue
|
||||
}
|
||||
h[host.String()] = struct{}{}
|
||||
}
|
||||
exists, err := setExists(s.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for k := range s.hosts {
|
||||
if _, ok := h[k]; !ok {
|
||||
if exists {
|
||||
if err := s.del(k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
delete(s.hosts, k)
|
||||
}
|
||||
}
|
||||
for k := range h {
|
||||
if _, ok := s.hosts[k]; !ok {
|
||||
if exists {
|
||||
if err := s.add(k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.hosts[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nonBlockingSend(errors chan<- error, err error) {
|
||||
select {
|
||||
case errors <- err:
|
||||
default:
|
||||
}
|
||||
}
|
92
pkg/iptables/fake.go
Normal file
92
pkg/iptables/fake.go
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
type statusExiter interface {
|
||||
ExitStatus() int
|
||||
}
|
||||
|
||||
var _ statusExiter = (*iptables.Error)(nil)
|
||||
var _ statusExiter = statusError(0)
|
||||
|
||||
type statusError int
|
||||
|
||||
func (s statusError) Error() string {
|
||||
return fmt.Sprintf("%d", s)
|
||||
}
|
||||
|
||||
func (s statusError) ExitStatus() int {
|
||||
return int(s)
|
||||
}
|
||||
|
||||
type fakeClient map[string]Rule
|
||||
|
||||
var _ iptablesClient = fakeClient(nil)
|
||||
|
||||
func (f fakeClient) AppendUnique(table, chain string, spec ...string) error {
|
||||
r := &rule{table, chain, spec, nil}
|
||||
f[r.String()] = r
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeClient) Delete(table, chain string, spec ...string) error {
|
||||
r := &rule{table, chain, spec, nil}
|
||||
delete(f, r.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeClient) Exists(table, chain string, spec ...string) (bool, error) {
|
||||
r := &rule{table, chain, spec, nil}
|
||||
_, ok := f[r.String()]
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func (f fakeClient) ClearChain(table, name string) error {
|
||||
c := &chain{table, name, nil}
|
||||
for k := range f {
|
||||
if strings.HasPrefix(k, c.String()) {
|
||||
delete(f, k)
|
||||
}
|
||||
}
|
||||
f[c.String()] = c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeClient) DeleteChain(table, name string) error {
|
||||
c := &chain{table, name, nil}
|
||||
for k := range f {
|
||||
if strings.HasPrefix(k, c.String()) {
|
||||
return fmt.Errorf("cannot delete chain %s; rules exist", name)
|
||||
}
|
||||
}
|
||||
delete(f, c.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeClient) NewChain(table, name string) error {
|
||||
c := &chain{table, name, nil}
|
||||
if _, ok := f[c.String()]; ok {
|
||||
return statusError(1)
|
||||
}
|
||||
f[c.String()] = c
|
||||
return nil
|
||||
}
|
289
pkg/iptables/iptables.go
Normal file
289
pkg/iptables/iptables.go
Normal file
@ -0,0 +1,289 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
type iptablesClient interface {
|
||||
AppendUnique(string, string, ...string) error
|
||||
Delete(string, string, ...string) error
|
||||
Exists(string, string, ...string) (bool, error)
|
||||
ClearChain(string, string) error
|
||||
DeleteChain(string, string) error
|
||||
NewChain(string, string) error
|
||||
}
|
||||
|
||||
// rule represents an iptables rule.
|
||||
type rule struct {
|
||||
table string
|
||||
chain string
|
||||
spec []string
|
||||
client iptablesClient
|
||||
}
|
||||
|
||||
func (r *rule) Add() error {
|
||||
if err := r.client.AppendUnique(r.table, r.chain, r.spec...); err != nil {
|
||||
return fmt.Errorf("failed to add iptables rule: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rule) Delete() error {
|
||||
// Ignore the returned error as an error likely means
|
||||
// that the rule doesn't exist, which is fine.
|
||||
r.client.Delete(r.table, r.chain, r.spec...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rule) Exists() (bool, error) {
|
||||
return r.client.Exists(r.table, r.chain, r.spec...)
|
||||
}
|
||||
|
||||
func (r *rule) String() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s_%s_%s", r.table, r.chain, strings.Join(r.spec, "_"))
|
||||
}
|
||||
|
||||
// chain represents an iptables chain.
|
||||
type chain struct {
|
||||
table string
|
||||
chain string
|
||||
client iptablesClient
|
||||
}
|
||||
|
||||
func (c *chain) Add() error {
|
||||
if err := c.client.ClearChain(c.table, c.chain); err != nil {
|
||||
return fmt.Errorf("failed to add iptables chain: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chain) Delete() error {
|
||||
// The chain must be empty before it can be deleted.
|
||||
if err := c.client.ClearChain(c.table, c.chain); err != nil {
|
||||
return fmt.Errorf("failed to clear iptables chain: %v", err)
|
||||
}
|
||||
// Ignore the returned error as an error likely means
|
||||
// that the chain doesn't exist, which is fine.
|
||||
c.client.DeleteChain(c.table, c.chain)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chain) Exists() (bool, error) {
|
||||
// The code for "chain already exists".
|
||||
existsErr := 1
|
||||
err := c.client.NewChain(c.table, c.chain)
|
||||
se, ok := err.(statusExiter)
|
||||
switch {
|
||||
case err == nil:
|
||||
// If there was no error adding a new chain, then it did not exist.
|
||||
// Delete it and return false.
|
||||
c.client.DeleteChain(c.table, c.chain)
|
||||
return false, nil
|
||||
case ok && se.ExitStatus() == existsErr:
|
||||
return true, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *chain) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", c.table, c.chain)
|
||||
}
|
||||
|
||||
// Rule is an interface for interacting with iptables objects.
|
||||
type Rule interface {
|
||||
Add() error
|
||||
Delete() error
|
||||
Exists() (bool, error)
|
||||
String() string
|
||||
}
|
||||
|
||||
// Controller is able to reconcile a given set of iptables rules.
|
||||
type Controller struct {
|
||||
client iptablesClient
|
||||
errors chan error
|
||||
rules map[string]Rule
|
||||
mu sync.Mutex
|
||||
subscribed bool
|
||||
}
|
||||
|
||||
// New generates a new iptables rules controller.
|
||||
// It expects an IP address length to determine
|
||||
// whether to operate in IPv4 or IPv6 mode.
|
||||
func New(ipLength int) (*Controller, error) {
|
||||
p := iptables.ProtocolIPv4
|
||||
if ipLength == net.IPv6len {
|
||||
p = iptables.ProtocolIPv6
|
||||
}
|
||||
client, err := iptables.NewWithProtocol(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create iptables client: %v", err)
|
||||
}
|
||||
return &Controller{
|
||||
client: client,
|
||||
errors: make(chan error),
|
||||
rules: make(map[string]Rule),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run watches for changes to iptables rules and reconciles
|
||||
// the rules against the desired state.
|
||||
func (c *Controller) Run(stop <-chan struct{}) (<-chan error, error) {
|
||||
c.mu.Lock()
|
||||
if c.subscribed {
|
||||
c.mu.Unlock()
|
||||
return c.errors, nil
|
||||
}
|
||||
// Ensure a given instance only subscribes once.
|
||||
c.subscribed = true
|
||||
c.mu.Unlock()
|
||||
go func() {
|
||||
defer close(c.errors)
|
||||
for {
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
for _, r := range c.rules {
|
||||
ok, err := r.Exists()
|
||||
if err != nil {
|
||||
nonBlockingSend(c.errors, fmt.Errorf("failed to check if rule exists: %v", err))
|
||||
}
|
||||
if !ok {
|
||||
if err := r.Add(); err != nil {
|
||||
nonBlockingSend(c.errors, fmt.Errorf("failed to add rule: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
return c.errors, nil
|
||||
}
|
||||
|
||||
// Set idempotently overwrites any iptables rules previously defined
|
||||
// for the controller with the given set of rules.
|
||||
func (c *Controller) Set(rules []Rule) error {
|
||||
r := make(map[string]struct{})
|
||||
for i := range rules {
|
||||
if rules[i] == nil {
|
||||
continue
|
||||
}
|
||||
switch v := rules[i].(type) {
|
||||
case *rule:
|
||||
v.client = c.client
|
||||
case *chain:
|
||||
v.client = c.client
|
||||
}
|
||||
r[rules[i].String()] = struct{}{}
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for k, rule := range c.rules {
|
||||
if _, ok := r[k]; !ok {
|
||||
if err := rule.Delete(); err != nil {
|
||||
return fmt.Errorf("failed to delete rule: %v", err)
|
||||
}
|
||||
delete(c.rules, k)
|
||||
}
|
||||
}
|
||||
// Iterate over the slice rather than the map
|
||||
// to ensure the rules are added in order.
|
||||
for _, rule := range rules {
|
||||
if _, ok := c.rules[rule.String()]; !ok {
|
||||
if err := rule.Add(); err != nil {
|
||||
return fmt.Errorf("failed to add rule: %v", err)
|
||||
}
|
||||
c.rules[rule.String()] = rule
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp will clean up any rules created by the controller.
|
||||
func (c *Controller) CleanUp() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for k, rule := range c.rules {
|
||||
if err := rule.Delete(); err != nil {
|
||||
return fmt.Errorf("failed to delete rule: %v", err)
|
||||
}
|
||||
delete(c.rules, k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncapsulateRules returns a set of iptables rules that are necessary
|
||||
// when traffic between nodes must be encapsulated.
|
||||
func EncapsulateRules(nodes []*net.IPNet) []Rule {
|
||||
var rules []Rule
|
||||
for _, n := range nodes {
|
||||
// Accept encapsulated traffic from peers.
|
||||
rules = append(rules, &rule{"filter", "INPUT", []string{"-m", "comment", "--comment", "Kilo: allow IPIP traffic", "-s", n.IP.String(), "-p", "4", "-j", "ACCEPT"}, nil})
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
// ForwardRules returns a set of iptables rules that are necessary
|
||||
// when traffic must be forwarded for the overlay.
|
||||
func ForwardRules(subnet *net.IPNet) []Rule {
|
||||
s := subnet.String()
|
||||
return []Rule{
|
||||
// Forward traffic to and from the overlay.
|
||||
&rule{"filter", "FORWARD", []string{"-s", s, "-j", "ACCEPT"}, nil},
|
||||
&rule{"filter", "FORWARD", []string{"-d", s, "-j", "ACCEPT"}, nil},
|
||||
}
|
||||
}
|
||||
|
||||
// MasqueradeRules returns a set of iptables rules that are necessary
|
||||
// when traffic must be masqueraded for Kilo.
|
||||
func MasqueradeRules(subnet, localPodSubnet *net.IPNet, remotePodSubnet []*net.IPNet) []Rule {
|
||||
var rules []Rule
|
||||
rules = append(rules, &chain{"mangle", "KILO-MARK", nil})
|
||||
rules = append(rules, &rule{"mangle", "PREROUTING", []string{"-m", "comment", "--comment", "Kilo: jump to mark chain", "-i", "kilo+", "-j", "KILO-MARK"}, nil})
|
||||
rules = append(rules, &rule{"mangle", "KILO-MARK", []string{"-m", "comment", "--comment", "Kilo: do not mark packets destined for the local Pod subnet", "-d", localPodSubnet.String(), "-j", "RETURN"}, nil})
|
||||
if subnet != nil {
|
||||
rules = append(rules, &rule{"mangle", "KILO-MARK", []string{"-m", "comment", "--comment", "Kilo: do not mark packets destined for the local private subnet", "-d", subnet.String(), "-j", "RETURN"}, nil})
|
||||
}
|
||||
rules = append(rules, &rule{"mangle", "KILO-MARK", []string{"-m", "comment", "--comment", "Kilo: remaining packets should be marked for NAT", "-j", "MARK", "--set-xmark", "0x1107/0x1107"}, nil})
|
||||
rules = append(rules, &rule{"nat", "POSTROUTING", []string{"-m", "comment", "--comment", "Kilo: NAT packets from Kilo interface", "-m", "mark", "--mark", "0x1107/0x1107", "-j", "MASQUERADE"}, nil})
|
||||
for _, r := range remotePodSubnet {
|
||||
rules = append(rules, &rule{"nat", "POSTROUTING", []string{"-m", "comment", "--comment", "Kilo: NAT packets from local pod subnet to remote pod subnets", "-s", localPodSubnet.String(), "-d", r.String(), "-j", "MASQUERADE"}, nil})
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func nonBlockingSend(errors chan<- error, err error) {
|
||||
select {
|
||||
case errors <- err:
|
||||
default:
|
||||
}
|
||||
}
|
101
pkg/iptables/iptables_test.go
Normal file
101
pkg/iptables/iptables_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var rules = []Rule{
|
||||
&rule{"filter", "FORWARD", []string{"-s", "10.4.0.0/16", "-j", "ACCEPT"}, nil},
|
||||
&rule{"filter", "FORWARD", []string{"-d", "10.4.0.0/16", "-j", "ACCEPT"}, nil},
|
||||
}
|
||||
|
||||
func newController() *Controller {
|
||||
return &Controller{
|
||||
rules: make(map[string]Rule),
|
||||
}
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
rules []Rule
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
rules: nil,
|
||||
},
|
||||
{
|
||||
name: "single",
|
||||
rules: []Rule{rules[0]},
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
rules: []Rule{rules[0], rules[1]},
|
||||
},
|
||||
} {
|
||||
backend := make(map[string]Rule)
|
||||
controller := newController()
|
||||
controller.client = fakeClient(backend)
|
||||
if err := controller.Set(tc.rules); err != nil {
|
||||
t.Fatalf("test case %q: got unexpected error: %v", tc.name, err)
|
||||
}
|
||||
for _, r := range tc.rules {
|
||||
r1 := backend[r.String()]
|
||||
r2 := controller.rules[r.String()]
|
||||
if r.String() != r1.String() || r.String() != r2.String() {
|
||||
t.Errorf("test case %q: expected all rules to be equal: expected %v, got %v and %v", tc.name, r, r1, r2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanUp(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
rules []Rule
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
rules: nil,
|
||||
},
|
||||
{
|
||||
name: "single",
|
||||
rules: []Rule{rules[0]},
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
rules: []Rule{rules[0], rules[1]},
|
||||
},
|
||||
} {
|
||||
backend := make(map[string]Rule)
|
||||
controller := newController()
|
||||
controller.client = fakeClient(backend)
|
||||
if err := controller.Set(tc.rules); err != nil {
|
||||
t.Fatalf("test case %q: Set should not fail: %v", tc.name, err)
|
||||
}
|
||||
if err := controller.CleanUp(); err != nil {
|
||||
t.Errorf("test case %q: got unexpected error: %v", tc.name, err)
|
||||
}
|
||||
for _, r := range tc.rules {
|
||||
r1 := backend[r.String()]
|
||||
r2 := controller.rules[r.String()]
|
||||
if r1 != nil || r2 != nil {
|
||||
t.Errorf("test case %q: expected all rules to be nil: expected got %v and %v", tc.name, r1, r2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
229
pkg/k8s/backend.go
Normal file
229
pkg/k8s/backend.go
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
v1informers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
v1listers "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
)
|
||||
|
||||
const (
|
||||
// Backend is the name of this mesh backend.
|
||||
Backend = "kubernetes"
|
||||
externalIPAnnotationKey = "kilo.squat.ai/external-ip"
|
||||
forceExternalIPAnnotationKey = "kilo.squat.ai/force-external-ip"
|
||||
internalIPAnnotationKey = "kilo.squat.ai/internal-ip"
|
||||
keyAnnotationKey = "kilo.squat.ai/key"
|
||||
leaderAnnotationKey = "kilo.squat.ai/leader"
|
||||
locationAnnotationKey = "kilo.squat.ai/location"
|
||||
regionLabelKey = "failure-domain.beta.kubernetes.io/region"
|
||||
jsonPatchSlash = "~1"
|
||||
jsonRemovePatch = `{"op": "remove", "path": "%s"}`
|
||||
)
|
||||
|
||||
type backend struct {
|
||||
client kubernetes.Interface
|
||||
events chan *mesh.Event
|
||||
informer cache.SharedIndexInformer
|
||||
lister v1listers.NodeLister
|
||||
}
|
||||
|
||||
// New creates a new instance of a mesh.Backend.
|
||||
func New(client kubernetes.Interface) mesh.Backend {
|
||||
informer := v1informers.NewNodeInformer(client, 5*time.Minute, nil)
|
||||
|
||||
b := &backend{
|
||||
client: client,
|
||||
events: make(chan *mesh.Event),
|
||||
informer: informer,
|
||||
lister: v1listers.NewNodeLister(informer.GetIndexer()),
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// CleanUp removes configuration applied to the backend.
|
||||
func (b *backend) CleanUp(name string) error {
|
||||
patch := []byte("[" + strings.Join([]string{
|
||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(externalIPAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(internalIPAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(keyAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||
}, ",") + "]")
|
||||
if _, err := b.client.CoreV1().Nodes().Patch(name, types.JSONPatchType, patch); err != nil {
|
||||
return fmt.Errorf("failed to patch node: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets a single Node by name.
|
||||
func (b *backend) Get(name string) (*mesh.Node, error) {
|
||||
n, err := b.lister.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return translateNode(n), nil
|
||||
}
|
||||
|
||||
// Init initializes the backend; for this backend that means
|
||||
// syncing the informer cache.
|
||||
func (b *backend) Init(stop <-chan struct{}) error {
|
||||
go b.informer.Run(stop)
|
||||
if ok := cache.WaitForCacheSync(stop, func() bool {
|
||||
return b.informer.HasSynced()
|
||||
}); !ok {
|
||||
return errors.New("failed to start sync node cache")
|
||||
}
|
||||
b.informer.AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
n, ok := obj.(*v1.Node)
|
||||
if !ok {
|
||||
// Failed to decode Node; ignoring...
|
||||
return
|
||||
}
|
||||
b.events <- &mesh.Event{Type: mesh.AddEvent, Node: translateNode(n)}
|
||||
},
|
||||
UpdateFunc: func(_, obj interface{}) {
|
||||
n, ok := obj.(*v1.Node)
|
||||
if !ok {
|
||||
// Failed to decode Node; ignoring...
|
||||
return
|
||||
}
|
||||
b.events <- &mesh.Event{Type: mesh.UpdateEvent, Node: translateNode(n)}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
n, ok := obj.(*v1.Node)
|
||||
if !ok {
|
||||
// Failed to decode Node; ignoring...
|
||||
return
|
||||
}
|
||||
b.events <- &mesh.Event{Type: mesh.DeleteEvent, Node: translateNode(n)}
|
||||
},
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List gets all the Nodes in the cluster.
|
||||
func (b *backend) List() ([]*mesh.Node, error) {
|
||||
ns, err := b.lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes := make([]*mesh.Node, len(ns))
|
||||
for i := range ns {
|
||||
nodes[i] = translateNode(ns[i])
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// Set sets the fields of a node.
|
||||
func (b *backend) Set(name string, node *mesh.Node) error {
|
||||
old, err := b.lister.Get(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find node: %v", err)
|
||||
}
|
||||
n := old.DeepCopy()
|
||||
n.ObjectMeta.Annotations[externalIPAnnotationKey] = node.ExternalIP.String()
|
||||
n.ObjectMeta.Annotations[internalIPAnnotationKey] = node.InternalIP.String()
|
||||
n.ObjectMeta.Annotations[keyAnnotationKey] = string(node.Key)
|
||||
oldData, err := json.Marshal(old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newData, err := json.Marshal(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create patch for node %q: %v", n.Name, err)
|
||||
}
|
||||
if _, err = b.client.CoreV1().Nodes().Patch(name, types.StrategicMergePatchType, patch); err != nil {
|
||||
return fmt.Errorf("failed to patch node: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch returns a chan of node events.
|
||||
func (b *backend) Watch() <-chan *mesh.Event {
|
||||
return b.events
|
||||
}
|
||||
|
||||
// translateNode translates a Kubernetes Node to a mesh.Node.
|
||||
func translateNode(node *v1.Node) *mesh.Node {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
_, subnet, err := net.ParseCIDR(node.Spec.PodCIDR)
|
||||
// The subnet should only ever fail to parse if the pod CIDR has not been set,
|
||||
// so in this case set the subnet to nil and let the node be updated.
|
||||
if err != nil {
|
||||
subnet = nil
|
||||
}
|
||||
_, leader := node.ObjectMeta.Annotations[leaderAnnotationKey]
|
||||
// Allow the region to be overridden by an explicit location.
|
||||
location, ok := node.ObjectMeta.Annotations[locationAnnotationKey]
|
||||
if !ok {
|
||||
location = node.ObjectMeta.Labels[regionLabelKey]
|
||||
}
|
||||
// Allow the external IP to be overridden.
|
||||
externalIP, ok := node.ObjectMeta.Annotations[forceExternalIPAnnotationKey]
|
||||
if !ok {
|
||||
externalIP = node.ObjectMeta.Annotations[externalIPAnnotationKey]
|
||||
}
|
||||
return &mesh.Node{
|
||||
// ExternalIP and InternalIP should only ever fail to parse if the
|
||||
// remote node's mesh has not yet set its IP address;
|
||||
// in this case the IP will be nil and
|
||||
// the mesh can wait for the node to be updated.
|
||||
ExternalIP: normalizeIP(externalIP),
|
||||
InternalIP: normalizeIP(node.ObjectMeta.Annotations[internalIPAnnotationKey]),
|
||||
Key: []byte(node.ObjectMeta.Annotations[keyAnnotationKey]),
|
||||
Leader: leader,
|
||||
Location: location,
|
||||
Name: node.Name,
|
||||
Subnet: subnet,
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeIP(ip string) *net.IPNet {
|
||||
i, ipNet, _ := net.ParseCIDR(ip)
|
||||
if ipNet == nil {
|
||||
return ipNet
|
||||
}
|
||||
if ip4 := i.To4(); ip4 != nil {
|
||||
ipNet.IP = ip4
|
||||
return ipNet
|
||||
}
|
||||
ipNet.IP = i.To16()
|
||||
return ipNet
|
||||
}
|
145
pkg/k8s/backend_test.go
Normal file
145
pkg/k8s/backend_test.go
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"k8s.io/api/core/v1"
|
||||
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
)
|
||||
|
||||
func TestTranslateNode(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
annotations map[string]string
|
||||
labels map[string]string
|
||||
out *mesh.Node
|
||||
subnet string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
annotations: nil,
|
||||
out: &mesh.Node{},
|
||||
},
|
||||
{
|
||||
name: "invalid ip",
|
||||
annotations: map[string]string{
|
||||
externalIPAnnotationKey: "10.0.0.1",
|
||||
internalIPAnnotationKey: "10.0.0.1",
|
||||
},
|
||||
out: &mesh.Node{},
|
||||
},
|
||||
{
|
||||
name: "valid ip",
|
||||
annotations: map[string]string{
|
||||
externalIPAnnotationKey: "10.0.0.1/24",
|
||||
internalIPAnnotationKey: "10.0.0.2/32",
|
||||
},
|
||||
out: &mesh.Node{
|
||||
ExternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)},
|
||||
InternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(32, 32)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid subnet",
|
||||
annotations: map[string]string{},
|
||||
out: &mesh.Node{},
|
||||
subnet: "foo",
|
||||
},
|
||||
{
|
||||
name: "normalize subnet",
|
||||
annotations: map[string]string{},
|
||||
out: &mesh.Node{
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
subnet: "10.2.0.1/24",
|
||||
},
|
||||
{
|
||||
name: "valid subnet",
|
||||
annotations: map[string]string{},
|
||||
out: &mesh.Node{
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
subnet: "10.2.1.0/24",
|
||||
},
|
||||
{
|
||||
name: "region",
|
||||
labels: map[string]string{
|
||||
regionLabelKey: "a",
|
||||
},
|
||||
out: &mesh.Node{
|
||||
Location: "a",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "region override",
|
||||
annotations: map[string]string{
|
||||
locationAnnotationKey: "b",
|
||||
},
|
||||
labels: map[string]string{
|
||||
regionLabelKey: "a",
|
||||
},
|
||||
out: &mesh.Node{
|
||||
Location: "b",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "external IP override",
|
||||
annotations: map[string]string{
|
||||
externalIPAnnotationKey: "10.0.0.1/24",
|
||||
forceExternalIPAnnotationKey: "10.0.0.2/24",
|
||||
},
|
||||
out: &mesh.Node{
|
||||
ExternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complete",
|
||||
annotations: map[string]string{
|
||||
externalIPAnnotationKey: "10.0.0.1/24",
|
||||
forceExternalIPAnnotationKey: "10.0.0.2/24",
|
||||
internalIPAnnotationKey: "10.0.0.2/32",
|
||||
keyAnnotationKey: "foo",
|
||||
leaderAnnotationKey: "",
|
||||
locationAnnotationKey: "b",
|
||||
},
|
||||
labels: map[string]string{
|
||||
regionLabelKey: "a",
|
||||
},
|
||||
out: &mesh.Node{
|
||||
ExternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)},
|
||||
InternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(32, 32)},
|
||||
Key: []byte("foo"),
|
||||
Leader: true,
|
||||
Location: "b",
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
||||
},
|
||||
subnet: "10.2.1.0/24",
|
||||
},
|
||||
} {
|
||||
n := &v1.Node{}
|
||||
n.ObjectMeta.Annotations = tc.annotations
|
||||
n.ObjectMeta.Labels = tc.labels
|
||||
n.Spec.PodCIDR = tc.subnet
|
||||
node := translateNode(n)
|
||||
if diff := pretty.Compare(node, tc.out); diff != "" {
|
||||
t.Errorf("test case %q: got diff: %v", tc.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
101
pkg/mesh/graph.go
Normal file
101
pkg/mesh/graph.go
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/awalterschulze/gographviz"
|
||||
)
|
||||
|
||||
// Dot generates a Graphviz graph of the Topology in DOT fomat.
|
||||
func (t *Topology) Dot() (string, error) {
|
||||
g := gographviz.NewGraph()
|
||||
g.Name = "kilo"
|
||||
if err := g.AddAttr("kilo", string(gographviz.Label), graphEscape(t.subnet.String())); err != nil {
|
||||
return "", fmt.Errorf("failed to add label to graph")
|
||||
}
|
||||
if err := g.AddAttr("kilo", string(gographviz.LabelLOC), "t"); err != nil {
|
||||
return "", fmt.Errorf("failed to add label location to graph")
|
||||
}
|
||||
if err := g.AddAttr("kilo", string(gographviz.Overlap), "false"); err != nil {
|
||||
return "", fmt.Errorf("failed to disable graph overlap")
|
||||
}
|
||||
if err := g.SetDir(true); err != nil {
|
||||
return "", fmt.Errorf("failed to set direction")
|
||||
}
|
||||
leaders := make([]string, len(t.Segments))
|
||||
nodeAttrs := map[string]string{
|
||||
string(gographviz.Shape): "ellipse",
|
||||
}
|
||||
for i, s := range t.Segments {
|
||||
if err := g.AddSubGraph("kilo", subGraphName(s.Location), nil); err != nil {
|
||||
return "", fmt.Errorf("failed to add subgraph")
|
||||
}
|
||||
if err := g.AddAttr(subGraphName(s.Location), string(gographviz.Label), graphEscape(s.Location)); err != nil {
|
||||
return "", fmt.Errorf("failed to add label to subgraph")
|
||||
}
|
||||
if err := g.AddAttr(subGraphName(s.Location), string(gographviz.Style), `"dashed,rounded"`); err != nil {
|
||||
return "", fmt.Errorf("failed to add style to subgraph")
|
||||
}
|
||||
for j := range s.cidrs {
|
||||
if err := g.AddNode(subGraphName(s.Location), graphEscape(s.hostnames[j]), nodeAttrs); err != nil {
|
||||
return "", fmt.Errorf("failed to add node to subgraph")
|
||||
}
|
||||
var wg net.IP
|
||||
if j == s.leader {
|
||||
wg = s.wireGuardIP
|
||||
if err := g.Nodes.Lookup[graphEscape(s.hostnames[j])].Attrs.Add(string(gographviz.Rank), "1"); err != nil {
|
||||
return "", fmt.Errorf("failed to add rank to node")
|
||||
}
|
||||
}
|
||||
if err := g.Nodes.Lookup[graphEscape(s.hostnames[j])].Attrs.Add(string(gographviz.Label), nodeLabel(s.Location, s.hostnames[j], s.cidrs[j], s.privateIPs[j], wg)); err != nil {
|
||||
return "", fmt.Errorf("failed to add label to node")
|
||||
}
|
||||
}
|
||||
meshSubGraph(g, g.Relations.SortedChildren(subGraphName(s.Location)), s.leader)
|
||||
leaders[i] = graphEscape(s.hostnames[s.leader])
|
||||
}
|
||||
meshSubGraph(g, leaders, 0)
|
||||
return g.String(), nil
|
||||
}
|
||||
|
||||
func meshSubGraph(g *gographviz.Graph, nodes []string, leader int) {
|
||||
for i := range nodes {
|
||||
if i == leader {
|
||||
continue
|
||||
}
|
||||
a := make(gographviz.Attrs)
|
||||
a[gographviz.Dir] = "both"
|
||||
g.Edges.Add(&gographviz.Edge{Src: nodes[leader], Dst: nodes[i], Dir: true, Attrs: a})
|
||||
}
|
||||
}
|
||||
|
||||
func graphEscape(s string) string {
|
||||
return fmt.Sprintf("\"%s\"", s)
|
||||
}
|
||||
|
||||
func subGraphName(name string) string {
|
||||
return graphEscape(fmt.Sprintf("cluster_%s", name))
|
||||
}
|
||||
|
||||
func nodeLabel(location, name string, cidr *net.IPNet, priv, wgIP net.IP) string {
|
||||
var wg string
|
||||
if wgIP != nil {
|
||||
wg = wgIP.String()
|
||||
}
|
||||
return graphEscape(fmt.Sprintf("%s\n%s\n%s\n%s\n%s", location, name, cidr.String(), priv.String(), wg))
|
||||
}
|
348
pkg/mesh/ip.go
Normal file
348
pkg/mesh/ip.go
Normal file
@ -0,0 +1,348 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// getIP returns a private and public IP address for the local node.
|
||||
// It selects the private IP address in the following order:
|
||||
// - private IP to which hostname resolves
|
||||
// - private IP assigned to interface of default route
|
||||
// - private IP assigned to local interface
|
||||
// - public IP to which hostname resolves
|
||||
// - public IP assigned to interface of default route
|
||||
// - public IP assigned to local interface
|
||||
// It selects the public IP address in the following order:
|
||||
// - public IP to which hostname resolves
|
||||
// - public IP assigned to interface of default route
|
||||
// - public IP assigned to local interface
|
||||
// - private IP to which hostname resolves
|
||||
// - private IP assigned to interface of default route
|
||||
// - private IP assigned to local interface
|
||||
// - if no IP was found, return nil and an error.
|
||||
func getIP(hostname string) (*net.IPNet, *net.IPNet, error) {
|
||||
var hostPriv, hostPub []*net.IPNet
|
||||
{
|
||||
// Check IPs to which hostname resolves first.
|
||||
ips, err := ipsForHostname(hostname)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, ip := range ips {
|
||||
ok, mask, err := assignedToInterface(ip)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to search locally assigned addresses: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ip.Mask = mask
|
||||
if isPublic(ip) {
|
||||
hostPub = append(hostPub, ip)
|
||||
continue
|
||||
}
|
||||
hostPriv = append(hostPriv, ip)
|
||||
}
|
||||
sortIPs(hostPriv)
|
||||
sortIPs(hostPub)
|
||||
}
|
||||
|
||||
var defaultPriv, defaultPub []*net.IPNet
|
||||
{
|
||||
// Check IPs on interface for default route next.
|
||||
iface, err := defaultInterface()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ips, err := ipsForInterface(iface)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, ip := range ips {
|
||||
if isLocal(ip.IP) {
|
||||
continue
|
||||
}
|
||||
if isPublic(ip) {
|
||||
defaultPub = append(defaultPub, ip)
|
||||
continue
|
||||
}
|
||||
defaultPriv = append(defaultPriv, ip)
|
||||
}
|
||||
sortIPs(defaultPriv)
|
||||
sortIPs(defaultPub)
|
||||
}
|
||||
|
||||
var interfacePriv, interfacePub []*net.IPNet
|
||||
{
|
||||
// Finally look for IPs on all interfaces.
|
||||
ips, err := ipsForAllInterfaces()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, ip := range ips {
|
||||
if isLocal(ip.IP) {
|
||||
continue
|
||||
}
|
||||
if isPublic(ip) {
|
||||
interfacePub = append(interfacePub, ip)
|
||||
continue
|
||||
}
|
||||
interfacePriv = append(interfacePriv, ip)
|
||||
}
|
||||
sortIPs(interfacePriv)
|
||||
sortIPs(interfacePub)
|
||||
}
|
||||
|
||||
var priv, pub []*net.IPNet
|
||||
priv = append(priv, hostPriv...)
|
||||
priv = append(priv, defaultPriv...)
|
||||
priv = append(priv, interfacePriv...)
|
||||
pub = append(pub, hostPub...)
|
||||
pub = append(pub, defaultPub...)
|
||||
pub = append(pub, interfacePub...)
|
||||
if len(priv) == 0 && len(pub) == 0 {
|
||||
return nil, nil, errors.New("no valid IP was found")
|
||||
}
|
||||
if len(priv) == 0 {
|
||||
priv = pub
|
||||
}
|
||||
if len(pub) == 0 {
|
||||
pub = priv
|
||||
}
|
||||
return priv[0], pub[0], nil
|
||||
}
|
||||
|
||||
// sortIPs sorts IPs so the result is stable.
|
||||
// It will first sort IPs by type, to prefer selecting
|
||||
// IPs of the same type, and then by value.
|
||||
func sortIPs(ips []*net.IPNet) {
|
||||
sort.Slice(ips, func(i, j int) bool {
|
||||
i4, j4 := ips[i].IP.To4(), ips[j].IP.To4()
|
||||
if i4 != nil && j4 == nil {
|
||||
return true
|
||||
}
|
||||
if j4 != nil && i4 == nil {
|
||||
return false
|
||||
}
|
||||
return ips[i].String() < ips[j].String()
|
||||
})
|
||||
}
|
||||
|
||||
func assignedToInterface(ip *net.IPNet) (bool, net.IPMask, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("failed to list interfaces: %v", err)
|
||||
}
|
||||
// Sort the links for stability.
|
||||
sort.Slice(links, func(i, j int) bool {
|
||||
return links[i].Attrs().Name < links[j].Attrs().Name
|
||||
})
|
||||
for _, link := range links {
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("failed to list addresses for %s: %v", link.Attrs().Name, err)
|
||||
}
|
||||
// Sort the IPs for stability.
|
||||
sort.Slice(addrs, func(i, j int) bool {
|
||||
return addrs[i].String() < addrs[j].String()
|
||||
})
|
||||
for i := range addrs {
|
||||
if ip.IP.Equal(addrs[i].IP) {
|
||||
return true, addrs[i].Mask, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
func isLocal(ip net.IP) bool {
|
||||
return ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast()
|
||||
}
|
||||
|
||||
func isPublic(ip *net.IPNet) bool {
|
||||
// Check RFC 1918 addresses.
|
||||
if ip4 := ip.IP.To4(); ip4 != nil {
|
||||
switch true {
|
||||
// Check for 10.0.0.0/8.
|
||||
case ip4[0] == 10:
|
||||
return false
|
||||
// Check for 172.16.0.0/12.
|
||||
case ip4[0] == 172 && ip4[1]&0xf0 == 0x01:
|
||||
return false
|
||||
// Check for 192.168.0.0/16.
|
||||
case ip4[0] == 192 && ip4[1] == 168:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Check RFC 4193 addresses.
|
||||
if len(ip.IP) == net.IPv6len {
|
||||
switch true {
|
||||
// Check for fd00::/8.
|
||||
case ip.IP[0] == 0xfd && ip.IP[1] == 0x00:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ipsForHostname returns a slice of IPs to which the
|
||||
// given hostname resolves.
|
||||
func ipsForHostname(hostname string) ([]*net.IPNet, error) {
|
||||
if ip := net.ParseIP(hostname); ip != nil {
|
||||
return []*net.IPNet{oneAddressCIDR(ip)}, nil
|
||||
}
|
||||
ips, err := net.LookupIP(hostname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to lookip IPs of hostname: %v", err)
|
||||
}
|
||||
nets := make([]*net.IPNet, len(ips))
|
||||
for i := range ips {
|
||||
nets[i] = oneAddressCIDR(ips[i])
|
||||
}
|
||||
return nets, nil
|
||||
}
|
||||
|
||||
// ipsForAllInterfaces returns a slice of IPs assigned to all the
|
||||
// interfaces on the host.
|
||||
func ipsForAllInterfaces() ([]*net.IPNet, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list interfaces: %v", err)
|
||||
}
|
||||
var nets []*net.IPNet
|
||||
for _, iface := range ifaces {
|
||||
ips, err := ipsForInterface(&iface)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list addresses for %s: %v", iface.Name, err)
|
||||
}
|
||||
nets = append(nets, ips...)
|
||||
}
|
||||
return nets, nil
|
||||
}
|
||||
|
||||
// ipsForInterface returns a slice of IPs assigned to the given interface.
|
||||
func ipsForInterface(iface *net.Interface) ([]*net.IPNet, error) {
|
||||
link, err := netlink.LinkByIndex(iface.Index)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get link: %s", err)
|
||||
}
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list addresses for %s: %v", iface.Name, err)
|
||||
}
|
||||
var ips []*net.IPNet
|
||||
for _, a := range addrs {
|
||||
if a.IPNet != nil {
|
||||
ips = append(ips, a.IPNet)
|
||||
}
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// interfacesForIP returns a slice of interfaces withthe given IP.
|
||||
func interfacesForIP(ip *net.IPNet) ([]net.Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list interfaces: %v", err)
|
||||
}
|
||||
var interfaces []net.Interface
|
||||
for _, iface := range ifaces {
|
||||
ips, err := ipsForInterface(&iface)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list addresses for %s: %v", iface.Name, err)
|
||||
}
|
||||
for i := range ips {
|
||||
if ip.IP.Equal(ips[i].IP) {
|
||||
interfaces = append(interfaces, iface)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(interfaces) == 0 {
|
||||
return nil, fmt.Errorf("no interface has %s assigned", ip.String())
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// defaultInterface returns the interface for the default route of the host.
|
||||
func defaultInterface() (*net.Interface, error) {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Dst == nil || route.Dst.String() == "0.0.0.0/0" || route.Dst.String() == "::/0" {
|
||||
if route.LinkIndex <= 0 {
|
||||
return nil, errors.New("failed to determine interface of route")
|
||||
}
|
||||
return net.InterfaceByIndex(route.LinkIndex)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("failed to find default route")
|
||||
}
|
||||
|
||||
type allocator struct {
|
||||
bits int
|
||||
cidr *net.IPNet
|
||||
current net.IP
|
||||
}
|
||||
|
||||
func newAllocator(cidr net.IPNet) *allocator {
|
||||
_, bits := cidr.Mask.Size()
|
||||
current := make(net.IP, len(cidr.IP))
|
||||
copy(current, cidr.IP)
|
||||
if ip4 := current.To4(); ip4 != nil {
|
||||
current = ip4
|
||||
}
|
||||
|
||||
return &allocator{
|
||||
bits: bits,
|
||||
cidr: &cidr,
|
||||
current: current,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *allocator) next() *net.IPNet {
|
||||
if a.current == nil {
|
||||
return nil
|
||||
}
|
||||
for i := len(a.current) - 1; i >= 0; i-- {
|
||||
a.current[i]++
|
||||
// if we haven't overflowed, then we can exit.
|
||||
if a.current[i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !a.cidr.Contains(a.current) {
|
||||
a.current = nil
|
||||
}
|
||||
ip := make(net.IP, len(a.current))
|
||||
copy(ip, a.current)
|
||||
|
||||
return &net.IPNet{IP: ip, Mask: net.CIDRMask(a.bits, a.bits)}
|
||||
}
|
75
pkg/mesh/ip_test.go
Normal file
75
pkg/mesh/ip_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSortIPs(t *testing.T) {
|
||||
ip1 := oneAddressCIDR(net.ParseIP("10.0.0.1"))
|
||||
ip2 := oneAddressCIDR(net.ParseIP("10.0.0.2"))
|
||||
ip3 := oneAddressCIDR(net.ParseIP("192.168.0.1"))
|
||||
ip4 := oneAddressCIDR(net.ParseIP("2001::7"))
|
||||
ip5 := oneAddressCIDR(net.ParseIP("fd68:da49:09da:b27f::"))
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
ips []*net.IPNet
|
||||
out []*net.IPNet
|
||||
}{
|
||||
{
|
||||
name: "single",
|
||||
ips: []*net.IPNet{ip1},
|
||||
out: []*net.IPNet{ip1},
|
||||
},
|
||||
{
|
||||
name: "IPv4s",
|
||||
ips: []*net.IPNet{ip2, ip3, ip1},
|
||||
out: []*net.IPNet{ip1, ip2, ip3},
|
||||
},
|
||||
{
|
||||
name: "IPv4 and IPv6",
|
||||
ips: []*net.IPNet{ip4, ip1},
|
||||
out: []*net.IPNet{ip1, ip4},
|
||||
},
|
||||
{
|
||||
name: "IPv6s",
|
||||
ips: []*net.IPNet{ip5, ip4},
|
||||
out: []*net.IPNet{ip4, ip5},
|
||||
},
|
||||
{
|
||||
name: "all",
|
||||
ips: []*net.IPNet{ip3, ip4, ip2, ip5, ip1},
|
||||
out: []*net.IPNet{ip1, ip2, ip3, ip4, ip5},
|
||||
},
|
||||
} {
|
||||
sortIPs(tc.ips)
|
||||
equal := true
|
||||
if len(tc.ips) != len(tc.out) {
|
||||
equal = false
|
||||
} else {
|
||||
for i := range tc.ips {
|
||||
if !ipNetsEqual(tc.ips[i], tc.out[i]) {
|
||||
equal = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !equal {
|
||||
t.Errorf("test case %q: expected %s, got %s", tc.name, tc.out, tc.ips)
|
||||
}
|
||||
}
|
||||
}
|
581
pkg/mesh/mesh.go
Normal file
581
pkg/mesh/mesh.go
Normal file
@ -0,0 +1,581 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/squat/kilo/pkg/iproute"
|
||||
"github.com/squat/kilo/pkg/ipset"
|
||||
"github.com/squat/kilo/pkg/iptables"
|
||||
"github.com/squat/kilo/pkg/route"
|
||||
"github.com/squat/kilo/pkg/wireguard"
|
||||
)
|
||||
|
||||
const resyncPeriod = 30 * time.Second
|
||||
|
||||
const (
|
||||
// KiloPath is the directory where Kilo stores its configuration.
|
||||
KiloPath = "/var/lib/kilo"
|
||||
// PrivateKeyPath is the filepath where the WireGuard private key is stored.
|
||||
PrivateKeyPath = KiloPath + "/key"
|
||||
// ConfPath is the filepath where the WireGuard configuration is stored.
|
||||
ConfPath = KiloPath + "/conf"
|
||||
)
|
||||
|
||||
// Granularity represents the abstraction level at which the network
|
||||
// should be meshed.
|
||||
type Granularity string
|
||||
|
||||
// Encapsulate identifies what packets within a location should
|
||||
// be encapsulated.
|
||||
type Encapsulate string
|
||||
|
||||
const (
|
||||
// DataCenterGranularity indicates that the network should create
|
||||
// a mesh between data-centers but not between nodes within a
|
||||
// single data-center.
|
||||
DataCenterGranularity Granularity = "data-center"
|
||||
// NodeGranularity indicates that the network should create
|
||||
// a mesh between every node.
|
||||
NodeGranularity Granularity = "node"
|
||||
// NeverEncapsulate indicates that no packets within a location
|
||||
// should be encapsulated.
|
||||
NeverEncapsulate Encapsulate = "never"
|
||||
// CrossSubnetEncapsulate indicates that only packets that
|
||||
// traverse subnets within a location should be encapsulated.
|
||||
CrossSubnetEncapsulate Encapsulate = "crosssubnet"
|
||||
// AlwaysEncapsulate indicates that all packets within a location
|
||||
// should be encapsulated.
|
||||
AlwaysEncapsulate Encapsulate = "always"
|
||||
)
|
||||
|
||||
// Node represents a node in the network.
|
||||
type Node struct {
|
||||
ExternalIP *net.IPNet
|
||||
Key []byte
|
||||
InternalIP *net.IPNet
|
||||
// Leader is a suggestion to Kilo that
|
||||
// the node wants to lead its segment.
|
||||
Leader bool
|
||||
Location string
|
||||
Name string
|
||||
Subnet *net.IPNet
|
||||
}
|
||||
|
||||
// Ready indicates whether or not the node is ready.
|
||||
func (n *Node) Ready() bool {
|
||||
return n != nil && n.ExternalIP != nil && n.Key != nil && n.InternalIP != nil && n.Subnet != nil
|
||||
}
|
||||
|
||||
// EventType describes what kind of an action an event represents.
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
// AddEvent represents an action where an item was added.
|
||||
AddEvent EventType = "add"
|
||||
// DeleteEvent represents an action where an item was removed.
|
||||
DeleteEvent EventType = "delete"
|
||||
// UpdateEvent represents an action where an item was updated.
|
||||
UpdateEvent EventType = "update"
|
||||
)
|
||||
|
||||
// Event represents an update event concerning a node in the cluster.
|
||||
type Event struct {
|
||||
Type EventType
|
||||
Node *Node
|
||||
}
|
||||
|
||||
// Backend can get nodes by name, init itself,
|
||||
// list the nodes that should be meshed,
|
||||
// set Kilo properties for a node,
|
||||
// clean up any changes applied to the backend,
|
||||
// and watch for changes to nodes.
|
||||
type Backend interface {
|
||||
CleanUp(string) error
|
||||
Get(string) (*Node, error)
|
||||
Init(<-chan struct{}) error
|
||||
List() ([]*Node, error)
|
||||
Set(string, *Node) error
|
||||
Watch() <-chan *Event
|
||||
}
|
||||
|
||||
// Mesh is able to create Kilo network meshes.
|
||||
type Mesh struct {
|
||||
Backend
|
||||
encapsulate Encapsulate
|
||||
externalIP *net.IPNet
|
||||
granularity Granularity
|
||||
hostname string
|
||||
internalIP *net.IPNet
|
||||
ipset *ipset.Set
|
||||
ipTables *iptables.Controller
|
||||
kiloIface int
|
||||
key []byte
|
||||
local bool
|
||||
port int
|
||||
priv []byte
|
||||
privIface int
|
||||
pub []byte
|
||||
pubIface int
|
||||
stop chan struct{}
|
||||
subnet *net.IPNet
|
||||
table *route.Table
|
||||
tunlIface int
|
||||
|
||||
// nodes is a mutable field in the struct
|
||||
// and needs to be guarded.
|
||||
nodes map[string]*Node
|
||||
mu sync.Mutex
|
||||
|
||||
errorCounter *prometheus.CounterVec
|
||||
nodesGuage prometheus.Gauge
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// New returns a new Mesh instance.
|
||||
func New(backend Backend, encapsulate Encapsulate, granularity Granularity, hostname string, port int, subnet *net.IPNet, local bool, logger log.Logger) (*Mesh, error) {
|
||||
if err := os.MkdirAll(KiloPath, 0700); err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory to store configuration: %v", err)
|
||||
}
|
||||
private, err := ioutil.ReadFile(PrivateKeyPath)
|
||||
if err != nil {
|
||||
level.Warn(logger).Log("msg", "no private key found on disk; generating one now")
|
||||
if private, err = wireguard.GenKey(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
public, err := wireguard.PubKey(private)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ioutil.WriteFile(PrivateKeyPath, private, 0600); err != nil {
|
||||
return nil, fmt.Errorf("failed to write private key to disk: %v", err)
|
||||
}
|
||||
privateIP, publicIP, err := getIP(hostname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find public IP: %v", err)
|
||||
}
|
||||
ifaces, err := interfacesForIP(privateIP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find interface for private IP: %v", err)
|
||||
}
|
||||
privIface := ifaces[0].Index
|
||||
ifaces, err = interfacesForIP(publicIP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find interface for public IP: %v", err)
|
||||
}
|
||||
pubIface := ifaces[0].Index
|
||||
kiloIface, err := wireguard.New("kilo")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create WireGuard interface: %v", err)
|
||||
}
|
||||
var tunlIface int
|
||||
if encapsulate != NeverEncapsulate {
|
||||
if tunlIface, err = iproute.NewIPIP(privIface); err != nil {
|
||||
return nil, fmt.Errorf("failed to create tunnel interface: %v", err)
|
||||
}
|
||||
if err := iproute.Set(tunlIface, true); err != nil {
|
||||
return nil, fmt.Errorf("failed to set tunnel interface up: %v", err)
|
||||
}
|
||||
}
|
||||
level.Debug(logger).Log("msg", fmt.Sprintf("using %s as the private IP address", privateIP.String()))
|
||||
level.Debug(logger).Log("msg", fmt.Sprintf("using %s as the public IP address", publicIP.String()))
|
||||
ipTables, err := iptables.New(len(subnet.IP))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to IP tables controller: %v", err)
|
||||
}
|
||||
return &Mesh{
|
||||
Backend: backend,
|
||||
encapsulate: encapsulate,
|
||||
externalIP: publicIP,
|
||||
granularity: granularity,
|
||||
hostname: hostname,
|
||||
internalIP: privateIP,
|
||||
// This is a patch until Calico supports
|
||||
// other hosts adding IPIP iptables rules.
|
||||
ipset: ipset.New("cali40all-hosts-net"),
|
||||
ipTables: ipTables,
|
||||
kiloIface: kiloIface,
|
||||
nodes: make(map[string]*Node),
|
||||
port: port,
|
||||
priv: private,
|
||||
privIface: privIface,
|
||||
pub: public,
|
||||
pubIface: pubIface,
|
||||
local: local,
|
||||
stop: make(chan struct{}),
|
||||
subnet: subnet,
|
||||
table: route.NewTable(),
|
||||
tunlIface: tunlIface,
|
||||
errorCounter: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "kilo_errors_total",
|
||||
Help: "Number of errors that occurred while administering the mesh.",
|
||||
}, []string{"event"}),
|
||||
nodesGuage: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "kilo_nodes",
|
||||
Help: "Number of in the mesh.",
|
||||
}),
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run starts the mesh.
|
||||
func (m *Mesh) Run() error {
|
||||
if err := m.Init(m.stop); err != nil {
|
||||
return fmt.Errorf("failed to initialize backend: %v", err)
|
||||
}
|
||||
ipsetErrors, err := m.ipset.Run(m.stop)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to watch for ipset updates: %v", err)
|
||||
}
|
||||
ipTablesErrors, err := m.ipTables.Run(m.stop)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to watch for IP tables updates: %v", err)
|
||||
}
|
||||
routeErrors, err := m.table.Run(m.stop)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to watch for route table updates: %v", err)
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
var err error
|
||||
select {
|
||||
case err = <-ipsetErrors:
|
||||
case err = <-ipTablesErrors:
|
||||
case err = <-routeErrors:
|
||||
case <-m.stop:
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("run").Inc()
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer m.cleanUp()
|
||||
t := time.NewTimer(resyncPeriod)
|
||||
w := m.Watch()
|
||||
for {
|
||||
var e *Event
|
||||
select {
|
||||
case e = <-w:
|
||||
m.sync(e)
|
||||
case <-t.C:
|
||||
m.applyTopology()
|
||||
t.Reset(resyncPeriod)
|
||||
case <-m.stop:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mesh) sync(e *Event) {
|
||||
logger := log.With(m.logger, "event", e.Type)
|
||||
level.Debug(logger).Log("msg", "syncing", "event", e.Type)
|
||||
if isSelf(m.hostname, e.Node) {
|
||||
level.Debug(logger).Log("msg", "processing local node", "node", e.Node)
|
||||
m.handleLocal(e.Node)
|
||||
return
|
||||
}
|
||||
var diff bool
|
||||
m.mu.Lock()
|
||||
if !e.Node.Ready() {
|
||||
level.Debug(logger).Log("msg", "received incomplete node", "node", e.Node)
|
||||
// An existing node is no longer valid
|
||||
// so remove it from the mesh.
|
||||
if _, ok := m.nodes[e.Node.Name]; ok {
|
||||
level.Info(logger).Log("msg", "node is no longer in the mesh", "node", e.Node)
|
||||
delete(m.nodes, e.Node.Name)
|
||||
diff = true
|
||||
}
|
||||
} else {
|
||||
switch e.Type {
|
||||
case AddEvent:
|
||||
fallthrough
|
||||
case UpdateEvent:
|
||||
if !nodesAreEqual(m.nodes[e.Node.Name], e.Node) {
|
||||
m.nodes[e.Node.Name] = e.Node
|
||||
diff = true
|
||||
}
|
||||
case DeleteEvent:
|
||||
delete(m.nodes, e.Node.Name)
|
||||
diff = true
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
if diff {
|
||||
level.Info(logger).Log("node", e.Node)
|
||||
m.applyTopology()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mesh) handleLocal(n *Node) {
|
||||
// Allow the external IP to be overridden.
|
||||
if n.ExternalIP == nil {
|
||||
n.ExternalIP = m.externalIP
|
||||
}
|
||||
// Compare the given node to the calculated local node.
|
||||
// Take leader, location, and subnet from the argument, as these
|
||||
// are not determined by kilo.
|
||||
local := &Node{ExternalIP: n.ExternalIP, Key: m.pub, InternalIP: m.internalIP, Leader: n.Leader, Location: n.Location, Name: m.hostname, Subnet: n.Subnet}
|
||||
if !nodesAreEqual(n, local) {
|
||||
level.Debug(m.logger).Log("msg", "local node differs from backend")
|
||||
if err := m.Set(m.hostname, local); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to set local node: %v", err), "node", local)
|
||||
m.errorCounter.WithLabelValues("local").Inc()
|
||||
return
|
||||
}
|
||||
level.Debug(m.logger).Log("msg", "successfully reconciled local node against backend")
|
||||
}
|
||||
m.mu.Lock()
|
||||
n = m.nodes[m.hostname]
|
||||
if n == nil {
|
||||
n = &Node{}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
if !nodesAreEqual(n, local) {
|
||||
m.mu.Lock()
|
||||
m.nodes[local.Name] = local
|
||||
m.mu.Unlock()
|
||||
m.applyTopology()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mesh) applyTopology() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
// Ensure all unready nodes are removed.
|
||||
var ready float64
|
||||
for n := range m.nodes {
|
||||
if !m.nodes[n].Ready() {
|
||||
delete(m.nodes, n)
|
||||
continue
|
||||
}
|
||||
ready++
|
||||
}
|
||||
m.nodesGuage.Set(ready)
|
||||
// We cannot do anything with the topology until the local node is available.
|
||||
if m.nodes[m.hostname] == nil {
|
||||
return
|
||||
}
|
||||
t, err := NewTopology(m.nodes, m.granularity, m.hostname, m.port, m.priv, m.subnet)
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
conf, err := t.Conf()
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
}
|
||||
if err := ioutil.WriteFile(ConfPath, conf, 0600); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
var private *net.IPNet
|
||||
// If we are not encapsulating packets to the local private network,
|
||||
// then pass the private IP to add an exception to the NAT rule.
|
||||
if m.encapsulate != AlwaysEncapsulate {
|
||||
private = t.privateIP
|
||||
}
|
||||
rules := iptables.MasqueradeRules(private, m.nodes[m.hostname].Subnet, t.RemoteSubnets())
|
||||
rules = append(rules, iptables.ForwardRules(m.subnet)...)
|
||||
if err := m.ipTables.Set(rules); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
if m.encapsulate != NeverEncapsulate {
|
||||
var peers []net.IP
|
||||
for _, s := range t.Segments {
|
||||
if s.Location == m.nodes[m.hostname].Location {
|
||||
peers = s.privateIPs
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := m.ipset.Set(peers); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
if m.local {
|
||||
if err := iproute.SetAddress(m.tunlIface, oneAddressCIDR(newAllocator(*m.nodes[m.hostname].Subnet).next().IP)); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.leader {
|
||||
if err := iproute.SetAddress(m.kiloIface, t.wireGuardCIDR); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
link, err := linkByIndex(m.kiloIface)
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
oldConf, err := wireguard.ShowConf(link.Attrs().Name)
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
// Setting the WireGuard configuration interrupts existing connections
|
||||
// so only set the configuration if it has changed.
|
||||
equal, err := wireguard.CompareConf(conf, oldConf)
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
// Don't return here, simply overwrite the old configuration.
|
||||
equal = false
|
||||
}
|
||||
if !equal {
|
||||
if err := wireguard.SetConf(link.Attrs().Name, ConfPath); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := iproute.Set(m.kiloIface, true); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
level.Debug(m.logger).Log("msg", "local node is not the leader")
|
||||
if err := iproute.Set(m.kiloIface, false); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
}
|
||||
// We need to add routes last since they may depend
|
||||
// on the WireGuard interface.
|
||||
routes := t.Routes(m.kiloIface, m.privIface, m.tunlIface, m.local, m.encapsulate)
|
||||
if err := m.table.Set(routes); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterMetrics registers Prometheus metrics on the given Prometheus
|
||||
// registerer.
|
||||
func (m *Mesh) RegisterMetrics(r prometheus.Registerer) {
|
||||
r.MustRegister(
|
||||
m.errorCounter,
|
||||
m.nodesGuage,
|
||||
)
|
||||
}
|
||||
|
||||
// Stop stops the mesh.
|
||||
func (m *Mesh) Stop() {
|
||||
close(m.stop)
|
||||
}
|
||||
|
||||
func (m *Mesh) cleanUp() {
|
||||
if err := m.ipTables.CleanUp(); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up IP tables: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := m.table.CleanUp(); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up routes: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := os.Remove(PrivateKeyPath); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to delete private key: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := os.Remove(ConfPath); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to delete configuration file: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := iproute.RemoveInterface(m.kiloIface); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to remove wireguard interface: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := m.CleanUp(m.hostname); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up backend: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := m.ipset.CleanUp(); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up ipset: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func isSelf(hostname string, node *Node) bool {
|
||||
return node != nil && node.Name == hostname
|
||||
}
|
||||
|
||||
func nodesAreEqual(a, b *Node) bool {
|
||||
if !(a != nil) == (b != nil) {
|
||||
return false
|
||||
}
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
return ipNetsEqual(a.ExternalIP, b.ExternalIP) && string(a.Key) == string(b.Key) && ipNetsEqual(a.InternalIP, b.InternalIP) && a.Leader == b.Leader && a.Location == b.Location && a.Name == b.Name && subnetsEqual(a.Subnet, b.Subnet)
|
||||
}
|
||||
|
||||
func ipNetsEqual(a, b *net.IPNet) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
if (a != nil) != (b != nil) {
|
||||
return false
|
||||
}
|
||||
if a.Mask.String() != b.Mask.String() {
|
||||
return false
|
||||
}
|
||||
return a.IP.Equal(b.IP)
|
||||
}
|
||||
|
||||
func subnetsEqual(a, b *net.IPNet) bool {
|
||||
if a.Mask.String() != b.Mask.String() {
|
||||
return false
|
||||
}
|
||||
if !a.Contains(b.IP) {
|
||||
return false
|
||||
}
|
||||
if !b.Contains(a.IP) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func linkByIndex(index int) (netlink.Link, error) {
|
||||
link, err := netlink.LinkByIndex(index)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get interface: %v", err)
|
||||
}
|
||||
return link, nil
|
||||
}
|
146
pkg/mesh/mesh_test.go
Normal file
146
pkg/mesh/mesh_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewAllocator(t *testing.T) {
|
||||
_, c1, err := net.ParseCIDR("10.1.0.0/16")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CIDR: %v", err)
|
||||
}
|
||||
a1 := newAllocator(*c1)
|
||||
_, c2, err := net.ParseCIDR("10.1.0.0/32")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CIDR: %v", err)
|
||||
}
|
||||
a2 := newAllocator(*c2)
|
||||
_, c3, err := net.ParseCIDR("10.1.0.0/31")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CIDR: %v", err)
|
||||
}
|
||||
a3 := newAllocator(*c3)
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
a *allocator
|
||||
next string
|
||||
}{
|
||||
{
|
||||
name: "10.1.0.0/16 first",
|
||||
a: a1,
|
||||
next: "10.1.0.1/32",
|
||||
},
|
||||
{
|
||||
name: "10.1.0.0/16 second",
|
||||
a: a1,
|
||||
next: "10.1.0.2/32",
|
||||
},
|
||||
{
|
||||
name: "10.1.0.0/32",
|
||||
a: a2,
|
||||
next: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "10.1.0.0/31 first",
|
||||
a: a3,
|
||||
next: "10.1.0.1/32",
|
||||
},
|
||||
{
|
||||
name: "10.1.0.0/31 second",
|
||||
a: a3,
|
||||
next: "<nil>",
|
||||
},
|
||||
} {
|
||||
next := tc.a.next()
|
||||
if next.String() != tc.next {
|
||||
t.Errorf("test case %q: expected %s, got %s", tc.name, tc.next, next.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReady(t *testing.T) {
|
||||
internalIP := oneAddressCIDR(net.ParseIP("1.1.1.1"))
|
||||
externalIP := oneAddressCIDR(net.ParseIP("2.2.2.2"))
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
node *Node
|
||||
ready bool
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
node: nil,
|
||||
ready: false,
|
||||
},
|
||||
{
|
||||
name: "empty fields",
|
||||
node: &Node{},
|
||||
ready: false,
|
||||
},
|
||||
{
|
||||
name: "empty external IP",
|
||||
node: &Node{
|
||||
InternalIP: internalIP,
|
||||
Key: []byte{},
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||
},
|
||||
ready: false,
|
||||
},
|
||||
{
|
||||
name: "empty internal IP",
|
||||
node: &Node{
|
||||
ExternalIP: externalIP,
|
||||
Key: []byte{},
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||
},
|
||||
ready: false,
|
||||
},
|
||||
{
|
||||
name: "empty key",
|
||||
node: &Node{
|
||||
ExternalIP: externalIP,
|
||||
InternalIP: internalIP,
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||
},
|
||||
ready: false,
|
||||
},
|
||||
{
|
||||
name: "empty subnet",
|
||||
node: &Node{
|
||||
ExternalIP: externalIP,
|
||||
InternalIP: internalIP,
|
||||
Key: []byte{},
|
||||
},
|
||||
ready: false,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
node: &Node{
|
||||
ExternalIP: externalIP,
|
||||
InternalIP: internalIP,
|
||||
Key: []byte{},
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||
},
|
||||
ready: true,
|
||||
},
|
||||
} {
|
||||
ready := tc.node.Ready()
|
||||
if ready != tc.ready {
|
||||
t.Errorf("test case %q: expected %t, got %t", tc.name, tc.ready, ready)
|
||||
}
|
||||
}
|
||||
}
|
334
pkg/mesh/topology.go
Normal file
334
pkg/mesh/topology.go
Normal file
@ -0,0 +1,334 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
confTemplate = template.Must(template.New("").Parse(`[Interface]
|
||||
PrivateKey = {{.Key}}
|
||||
ListenPort = {{.Port}}
|
||||
{{range .Segments -}}
|
||||
{{if ne .Location $.Location}}
|
||||
[Peer]
|
||||
PublicKey = {{.Key}}
|
||||
Endpoint = {{.Endpoint}}:{{$.Port}}
|
||||
AllowedIPs = {{.AllowedIPs}}
|
||||
{{end}}
|
||||
{{- end -}}
|
||||
`))
|
||||
)
|
||||
|
||||
// Topology represents the logical structure of the overlay network.
|
||||
type Topology struct {
|
||||
// Some fields need to be exported so that the template can read them.
|
||||
Key string
|
||||
Port int
|
||||
// Location is the logical location of the local host.
|
||||
Location string
|
||||
Segments []*segment
|
||||
|
||||
// hostname is the hostname of the local host.
|
||||
hostname string
|
||||
// leader represents whether or not the local host
|
||||
// is the segment leader.
|
||||
leader bool
|
||||
// subnet is the entire subnet from which IPs
|
||||
// for the WireGuard interfaces will be allocated.
|
||||
subnet *net.IPNet
|
||||
// privateIP is the private IP address of the local node.
|
||||
privateIP *net.IPNet
|
||||
// wireGuardCIDR is the allocated CIDR of the WireGuard
|
||||
// interface of the local node. If the local node is not
|
||||
// the leader, then it is nil.
|
||||
wireGuardCIDR *net.IPNet
|
||||
}
|
||||
|
||||
type segment struct {
|
||||
// Some fields need to be exported so that the template can read them.
|
||||
AllowedIPs string
|
||||
Endpoint string
|
||||
Key string
|
||||
// Location is the logical location of this segment.
|
||||
Location string
|
||||
|
||||
// cidrs is a slice of subnets of all peers in the segment.
|
||||
cidrs []*net.IPNet
|
||||
// hostnames is a slice of the hostnames of the peers in the segment.
|
||||
hostnames []string
|
||||
// leader is the index of the leader of the segment.
|
||||
leader int
|
||||
// privateIPs is a slice of private IPs of all peers in the segment.
|
||||
privateIPs []net.IP
|
||||
// wireGuardIP is the allocated IP address of the WireGuard
|
||||
// interface on the leader of the segment.
|
||||
wireGuardIP net.IP
|
||||
}
|
||||
|
||||
// NewTopology creates a new Topology struct from a given set of nodes.
|
||||
func NewTopology(nodes map[string]*Node, granularity Granularity, hostname string, port int, key []byte, subnet *net.IPNet) (*Topology, error) {
|
||||
topoMap := make(map[string][]*Node)
|
||||
for _, node := range nodes {
|
||||
var location string
|
||||
switch granularity {
|
||||
case DataCenterGranularity:
|
||||
location = node.Location
|
||||
case NodeGranularity:
|
||||
location = node.Name
|
||||
}
|
||||
topoMap[location] = append(topoMap[location], node)
|
||||
}
|
||||
var localLocation string
|
||||
switch granularity {
|
||||
case DataCenterGranularity:
|
||||
localLocation = nodes[hostname].Location
|
||||
case NodeGranularity:
|
||||
localLocation = hostname
|
||||
}
|
||||
|
||||
t := Topology{Key: strings.TrimSpace(string(key)), Port: port, hostname: hostname, Location: localLocation, subnet: subnet, privateIP: nodes[hostname].InternalIP}
|
||||
for location := range topoMap {
|
||||
// Sort the location so the result is stable.
|
||||
sort.Slice(topoMap[location], func(i, j int) bool {
|
||||
return topoMap[location][i].Name < topoMap[location][j].Name
|
||||
})
|
||||
leader := findLeader(topoMap[location])
|
||||
if location == localLocation && topoMap[location][leader].Name == hostname {
|
||||
t.leader = true
|
||||
}
|
||||
var allowedIPs []string
|
||||
var cidrs []*net.IPNet
|
||||
var hostnames []string
|
||||
var privateIPs []net.IP
|
||||
for _, node := range topoMap[location] {
|
||||
// Allowed IPs should include:
|
||||
// - the node's allocated subnet
|
||||
// - the node's WireGuard IP
|
||||
// - the node's internal IP
|
||||
allowedIPs = append(allowedIPs, node.Subnet.String(), oneAddressCIDR(node.InternalIP.IP).String())
|
||||
cidrs = append(cidrs, node.Subnet)
|
||||
hostnames = append(hostnames, node.Name)
|
||||
privateIPs = append(privateIPs, node.InternalIP.IP)
|
||||
}
|
||||
t.Segments = append(t.Segments, &segment{
|
||||
AllowedIPs: strings.Join(allowedIPs, ", "),
|
||||
Endpoint: topoMap[location][leader].ExternalIP.IP.String(),
|
||||
Key: strings.TrimSpace(string(topoMap[location][leader].Key)),
|
||||
Location: location,
|
||||
cidrs: cidrs,
|
||||
hostnames: hostnames,
|
||||
leader: leader,
|
||||
privateIPs: privateIPs,
|
||||
})
|
||||
}
|
||||
// Sort the Topology so the result is stable.
|
||||
sort.Slice(t.Segments, func(i, j int) bool {
|
||||
return t.Segments[i].Location < t.Segments[j].Location
|
||||
})
|
||||
|
||||
// Allocate IPs to the segment leaders in a stable, coordination-free manner.
|
||||
a := newAllocator(*subnet)
|
||||
for _, segment := range t.Segments {
|
||||
ipNet := a.next()
|
||||
if ipNet == nil {
|
||||
return nil, errors.New("failed to allocate an IP address; ran out of IP addresses")
|
||||
}
|
||||
segment.wireGuardIP = ipNet.IP
|
||||
segment.AllowedIPs = fmt.Sprintf("%s, %s", segment.AllowedIPs, ipNet.String())
|
||||
if t.leader && segment.Location == t.Location {
|
||||
t.wireGuardCIDR = &net.IPNet{IP: ipNet.IP, Mask: t.subnet.Mask}
|
||||
}
|
||||
}
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// RemoteSubnets identifies the subnets of the hosts in segments different than the host's.
|
||||
func (t *Topology) RemoteSubnets() []*net.IPNet {
|
||||
var remote []*net.IPNet
|
||||
for _, s := range t.Segments {
|
||||
if s == nil || s.Location == t.Location {
|
||||
continue
|
||||
}
|
||||
remote = append(remote, s.cidrs...)
|
||||
}
|
||||
return remote
|
||||
}
|
||||
|
||||
// Routes generates a slice of routes for a given Topology.
|
||||
func (t *Topology) Routes(kiloIface, privIface, tunlIface int, local bool, encapsulate Encapsulate) []*netlink.Route {
|
||||
var routes []*netlink.Route
|
||||
if !t.leader {
|
||||
// Find the leader for this segment.
|
||||
var leader net.IP
|
||||
for _, segment := range t.Segments {
|
||||
if segment.Location == t.Location {
|
||||
leader = segment.privateIPs[segment.leader]
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, segment := range t.Segments {
|
||||
// First, add a route to the WireGuard IP of the segment.
|
||||
routes = append(routes, encapsulateRoute(&netlink.Route{
|
||||
Dst: oneAddressCIDR(segment.wireGuardIP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: leader,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
}, encapsulate, t.privateIP, tunlIface))
|
||||
// Add routes for the current segment if local is true.
|
||||
if segment.Location == t.Location {
|
||||
if local {
|
||||
for i := range segment.cidrs {
|
||||
// Don't add routes for the local node.
|
||||
if segment.privateIPs[i].Equal(t.privateIP.IP) {
|
||||
continue
|
||||
}
|
||||
routes = append(routes, encapsulateRoute(&netlink.Route{
|
||||
Dst: segment.cidrs[i],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: segment.privateIPs[i],
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
}, encapsulate, t.privateIP, tunlIface))
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
for i := range segment.cidrs {
|
||||
// Add routes to the Pod CIDRs of nodes in other segments.
|
||||
routes = append(routes, encapsulateRoute(&netlink.Route{
|
||||
Dst: segment.cidrs[i],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: leader,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
}, encapsulate, t.privateIP, tunlIface))
|
||||
// Add routes to the private IPs of nodes in other segments.
|
||||
// Number of CIDRs and private IPs always match so
|
||||
// we can reuse the loop.
|
||||
routes = append(routes, encapsulateRoute(&netlink.Route{
|
||||
Dst: oneAddressCIDR(segment.privateIPs[i]),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: leader,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
}, encapsulate, t.privateIP, tunlIface))
|
||||
}
|
||||
}
|
||||
return routes
|
||||
}
|
||||
for _, segment := range t.Segments {
|
||||
// Add routes for the current segment if local is true.
|
||||
if segment.Location == t.Location {
|
||||
if local {
|
||||
for i := range segment.cidrs {
|
||||
// Don't add routes for the local node.
|
||||
if segment.privateIPs[i].Equal(t.privateIP.IP) {
|
||||
continue
|
||||
}
|
||||
routes = append(routes, encapsulateRoute(&netlink.Route{
|
||||
Dst: segment.cidrs[i],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: segment.privateIPs[i],
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
}, encapsulate, t.privateIP, tunlIface))
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
for i := range segment.cidrs {
|
||||
// Add routes to the Pod CIDRs of nodes in other segments.
|
||||
routes = append(routes, &netlink.Route{
|
||||
Dst: segment.cidrs[i],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: segment.wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
})
|
||||
// Add routes to the private IPs of nodes in other segments.
|
||||
// Number of CIDRs and private IPs always match so
|
||||
// we can reuse the loop.
|
||||
routes = append(routes, &netlink.Route{
|
||||
Dst: oneAddressCIDR(segment.privateIPs[i]),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: segment.wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
})
|
||||
}
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
func encapsulateRoute(route *netlink.Route, encapsulate Encapsulate, subnet *net.IPNet, tunlIface int) *netlink.Route {
|
||||
if encapsulate == AlwaysEncapsulate || (encapsulate == CrossSubnetEncapsulate && !subnet.Contains(route.Gw)) {
|
||||
route.LinkIndex = tunlIface
|
||||
}
|
||||
return route
|
||||
}
|
||||
|
||||
// Conf generates a WireGuard configuration file for a given Topology.
|
||||
func (t *Topology) Conf() ([]byte, error) {
|
||||
conf := new(bytes.Buffer)
|
||||
if err := confTemplate.Execute(conf, t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conf.Bytes(), nil
|
||||
}
|
||||
|
||||
// oneAddressCIDR takes an IP address and returns a CIDR
|
||||
// that contains only that address.
|
||||
func oneAddressCIDR(ip net.IP) *net.IPNet {
|
||||
return &net.IPNet{IP: ip, Mask: net.CIDRMask(len(ip)*8, len(ip)*8)}
|
||||
}
|
||||
|
||||
// findLeader selects a leader for the nodes in a segment;
|
||||
// it will select the first node that says it should lead
|
||||
// or the first node in the segment if none have volunteered,
|
||||
// always preferring those with a public external IP address,
|
||||
func findLeader(nodes []*Node) int {
|
||||
var leaders, public []int
|
||||
for i := range nodes {
|
||||
if nodes[i].Leader {
|
||||
if isPublic(nodes[i].ExternalIP) {
|
||||
return i
|
||||
}
|
||||
leaders = append(leaders, i)
|
||||
}
|
||||
if isPublic(nodes[i].ExternalIP) {
|
||||
public = append(public, i)
|
||||
}
|
||||
}
|
||||
if len(leaders) != 0 {
|
||||
return leaders[0]
|
||||
}
|
||||
if len(public) != 0 {
|
||||
return public[0]
|
||||
}
|
||||
return 0
|
||||
}
|
982
pkg/mesh/topology_test.go
Normal file
982
pkg/mesh/topology_test.go
Normal file
@ -0,0 +1,982 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func allowedIPs(ips ...string) string {
|
||||
return strings.Join(ips, ", ")
|
||||
}
|
||||
|
||||
func setup(t *testing.T) (map[string]*Node, []byte, int, *net.IPNet) {
|
||||
key := []byte("private")
|
||||
port := 51820
|
||||
_, kiloNet, err := net.ParseCIDR("10.4.0.0/16")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse Kilo subnet CIDR: %v", err)
|
||||
}
|
||||
ip, e1, err := net.ParseCIDR("10.1.0.1/16")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse external IP CIDR: %v", err)
|
||||
}
|
||||
e1.IP = ip
|
||||
ip, e2, err := net.ParseCIDR("10.1.0.2/16")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse external IP CIDR: %v", err)
|
||||
}
|
||||
e2.IP = ip
|
||||
ip, e3, err := net.ParseCIDR("10.1.0.3/16")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse external IP CIDR: %v", err)
|
||||
}
|
||||
e3.IP = ip
|
||||
ip, i1, err := net.ParseCIDR("192.168.0.1/24")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse internal IP CIDR: %v", err)
|
||||
}
|
||||
i1.IP = ip
|
||||
ip, i2, err := net.ParseCIDR("192.168.0.2/24")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse internal IP CIDR: %v", err)
|
||||
}
|
||||
i2.IP = ip
|
||||
nodes := map[string]*Node{
|
||||
"a": {
|
||||
Name: "a",
|
||||
ExternalIP: e1,
|
||||
InternalIP: i1,
|
||||
Location: "1",
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
|
||||
Key: []byte("key1"),
|
||||
},
|
||||
"b": {
|
||||
Name: "b",
|
||||
ExternalIP: e2,
|
||||
InternalIP: i1,
|
||||
Location: "2",
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.2.0"), Mask: net.CIDRMask(24, 32)},
|
||||
Key: []byte("key2"),
|
||||
},
|
||||
"c": {
|
||||
Name: "c",
|
||||
ExternalIP: e3,
|
||||
InternalIP: i2,
|
||||
// Same location a node b.
|
||||
Location: "2",
|
||||
Subnet: &net.IPNet{IP: net.ParseIP("10.2.3.0"), Mask: net.CIDRMask(24, 32)},
|
||||
Key: []byte("key3"),
|
||||
},
|
||||
}
|
||||
return nodes, key, port, kiloNet
|
||||
}
|
||||
|
||||
func TestNewTopology(t *testing.T) {
|
||||
nodes, key, port, kiloNet := setup(t)
|
||||
|
||||
w1 := net.ParseIP("10.4.0.1").To4()
|
||||
w2 := net.ParseIP("10.4.0.2").To4()
|
||||
w3 := net.ParseIP("10.4.0.3").To4()
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
granularity Granularity
|
||||
hostname string
|
||||
result *Topology
|
||||
}{
|
||||
{
|
||||
name: "datacenter from a",
|
||||
granularity: DataCenterGranularity,
|
||||
hostname: nodes["a"].Name,
|
||||
result: &Topology{
|
||||
hostname: nodes["a"].Name,
|
||||
leader: true,
|
||||
Location: nodes["a"].Location,
|
||||
subnet: kiloNet,
|
||||
privateIP: nodes["a"].InternalIP,
|
||||
wireGuardCIDR: &net.IPNet{IP: w1, Mask: net.CIDRMask(16, 32)},
|
||||
Segments: []*segment{
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["a"].Subnet.String(), "192.168.0.1/32", "10.4.0.1/32"),
|
||||
Endpoint: nodes["a"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["a"].Key),
|
||||
Location: nodes["a"].Location,
|
||||
cidrs: []*net.IPNet{nodes["a"].Subnet},
|
||||
hostnames: []string{"a"},
|
||||
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
|
||||
wireGuardIP: w1,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["b"].Subnet.String(), "192.168.0.1/32", nodes["c"].Subnet.String(), "192.168.0.2/32", "10.4.0.2/32"),
|
||||
Endpoint: nodes["b"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["b"].Key),
|
||||
Location: nodes["b"].Location,
|
||||
cidrs: []*net.IPNet{nodes["b"].Subnet, nodes["c"].Subnet},
|
||||
hostnames: []string{"b", "c"},
|
||||
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
|
||||
wireGuardIP: w2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datacenter from b",
|
||||
granularity: DataCenterGranularity,
|
||||
hostname: nodes["b"].Name,
|
||||
result: &Topology{
|
||||
hostname: nodes["b"].Name,
|
||||
leader: true,
|
||||
Location: nodes["b"].Location,
|
||||
subnet: kiloNet,
|
||||
privateIP: nodes["b"].InternalIP,
|
||||
wireGuardCIDR: &net.IPNet{IP: w2, Mask: net.CIDRMask(16, 32)},
|
||||
Segments: []*segment{
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["a"].Subnet.String(), "192.168.0.1/32", "10.4.0.1/32"),
|
||||
Endpoint: nodes["a"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["a"].Key),
|
||||
Location: nodes["a"].Location,
|
||||
cidrs: []*net.IPNet{nodes["a"].Subnet},
|
||||
hostnames: []string{"a"},
|
||||
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
|
||||
wireGuardIP: w1,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["b"].Subnet.String(), "192.168.0.1/32", nodes["c"].Subnet.String(), "192.168.0.2/32", "10.4.0.2/32"),
|
||||
Endpoint: nodes["b"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["b"].Key),
|
||||
Location: nodes["b"].Location,
|
||||
cidrs: []*net.IPNet{nodes["b"].Subnet, nodes["c"].Subnet},
|
||||
hostnames: []string{"b", "c"},
|
||||
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
|
||||
wireGuardIP: w2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datacenter from c",
|
||||
granularity: DataCenterGranularity,
|
||||
hostname: nodes["c"].Name,
|
||||
result: &Topology{
|
||||
hostname: nodes["c"].Name,
|
||||
leader: false,
|
||||
Location: nodes["b"].Location,
|
||||
subnet: kiloNet,
|
||||
privateIP: nodes["c"].InternalIP,
|
||||
wireGuardCIDR: nil,
|
||||
Segments: []*segment{
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["a"].Subnet.String(), "192.168.0.1/32", "10.4.0.1/32"),
|
||||
Endpoint: nodes["a"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["a"].Key),
|
||||
Location: nodes["a"].Location,
|
||||
cidrs: []*net.IPNet{nodes["a"].Subnet},
|
||||
hostnames: []string{"a"},
|
||||
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
|
||||
wireGuardIP: w1,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["b"].Subnet.String(), "192.168.0.1/32", nodes["c"].Subnet.String(), "192.168.0.2/32", "10.4.0.2/32"),
|
||||
Endpoint: nodes["b"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["b"].Key),
|
||||
Location: nodes["b"].Location,
|
||||
cidrs: []*net.IPNet{nodes["b"].Subnet, nodes["c"].Subnet},
|
||||
hostnames: []string{"b", "c"},
|
||||
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
|
||||
wireGuardIP: w2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from a",
|
||||
granularity: NodeGranularity,
|
||||
hostname: nodes["a"].Name,
|
||||
result: &Topology{
|
||||
hostname: nodes["a"].Name,
|
||||
leader: true,
|
||||
Location: nodes["a"].Name,
|
||||
subnet: kiloNet,
|
||||
privateIP: nodes["a"].InternalIP,
|
||||
wireGuardCIDR: &net.IPNet{IP: w1, Mask: net.CIDRMask(16, 32)},
|
||||
Segments: []*segment{
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["a"].Subnet.String(), "192.168.0.1/32", "10.4.0.1/32"),
|
||||
Endpoint: nodes["a"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["a"].Key),
|
||||
Location: nodes["a"].Name,
|
||||
cidrs: []*net.IPNet{nodes["a"].Subnet},
|
||||
hostnames: []string{"a"},
|
||||
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
|
||||
wireGuardIP: w1,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["b"].Subnet.String(), "192.168.0.1/32", "10.4.0.2/32"),
|
||||
Endpoint: nodes["b"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["b"].Key),
|
||||
Location: nodes["b"].Name,
|
||||
cidrs: []*net.IPNet{nodes["b"].Subnet},
|
||||
hostnames: []string{"b"},
|
||||
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
|
||||
wireGuardIP: w2,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["c"].Subnet.String(), "192.168.0.2/32", "10.4.0.3/32"),
|
||||
Endpoint: nodes["c"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["c"].Key),
|
||||
Location: nodes["c"].Name,
|
||||
cidrs: []*net.IPNet{nodes["c"].Subnet},
|
||||
hostnames: []string{"c"},
|
||||
privateIPs: []net.IP{nodes["c"].InternalIP.IP},
|
||||
wireGuardIP: w3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from b",
|
||||
granularity: NodeGranularity,
|
||||
hostname: nodes["b"].Name,
|
||||
result: &Topology{
|
||||
hostname: nodes["b"].Name,
|
||||
leader: true,
|
||||
Location: nodes["b"].Name,
|
||||
subnet: kiloNet,
|
||||
privateIP: nodes["b"].InternalIP,
|
||||
wireGuardCIDR: &net.IPNet{IP: w2, Mask: net.CIDRMask(16, 32)},
|
||||
Segments: []*segment{
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["a"].Subnet.String(), "192.168.0.1/32", "10.4.0.1/32"),
|
||||
Endpoint: nodes["a"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["a"].Key),
|
||||
Location: nodes["a"].Name,
|
||||
cidrs: []*net.IPNet{nodes["a"].Subnet},
|
||||
hostnames: []string{"a"},
|
||||
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
|
||||
wireGuardIP: w1,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["b"].Subnet.String(), "192.168.0.1/32", "10.4.0.2/32"),
|
||||
Endpoint: nodes["b"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["b"].Key),
|
||||
Location: nodes["b"].Name,
|
||||
cidrs: []*net.IPNet{nodes["b"].Subnet},
|
||||
hostnames: []string{"b"},
|
||||
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
|
||||
wireGuardIP: w2,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["c"].Subnet.String(), "192.168.0.2/32", "10.4.0.3/32"),
|
||||
Endpoint: nodes["c"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["c"].Key),
|
||||
Location: nodes["c"].Name,
|
||||
cidrs: []*net.IPNet{nodes["c"].Subnet},
|
||||
hostnames: []string{"c"},
|
||||
privateIPs: []net.IP{nodes["c"].InternalIP.IP},
|
||||
wireGuardIP: w3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from c",
|
||||
granularity: NodeGranularity,
|
||||
hostname: nodes["c"].Name,
|
||||
result: &Topology{
|
||||
hostname: nodes["c"].Name,
|
||||
leader: true,
|
||||
Location: nodes["c"].Name,
|
||||
subnet: kiloNet,
|
||||
privateIP: nodes["c"].InternalIP,
|
||||
wireGuardCIDR: &net.IPNet{IP: w3, Mask: net.CIDRMask(16, 32)},
|
||||
Segments: []*segment{
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["a"].Subnet.String(), "192.168.0.1/32", "10.4.0.1/32"),
|
||||
Endpoint: nodes["a"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["a"].Key),
|
||||
Location: nodes["a"].Name,
|
||||
cidrs: []*net.IPNet{nodes["a"].Subnet},
|
||||
hostnames: []string{"a"},
|
||||
privateIPs: []net.IP{nodes["a"].InternalIP.IP},
|
||||
wireGuardIP: w1,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["b"].Subnet.String(), "192.168.0.1/32", "10.4.0.2/32"),
|
||||
Endpoint: nodes["b"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["b"].Key),
|
||||
Location: nodes["b"].Name,
|
||||
cidrs: []*net.IPNet{nodes["b"].Subnet},
|
||||
hostnames: []string{"b"},
|
||||
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
|
||||
wireGuardIP: w2,
|
||||
},
|
||||
{
|
||||
AllowedIPs: allowedIPs(nodes["c"].Subnet.String(), "192.168.0.2/32", "10.4.0.3/32"),
|
||||
Endpoint: nodes["c"].ExternalIP.IP.String(),
|
||||
Key: string(nodes["c"].Key),
|
||||
Location: nodes["c"].Name,
|
||||
cidrs: []*net.IPNet{nodes["c"].Subnet},
|
||||
hostnames: []string{"c"},
|
||||
privateIPs: []net.IP{nodes["c"].InternalIP.IP},
|
||||
wireGuardIP: w3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
tc.result.Key = string(key)
|
||||
tc.result.Port = port
|
||||
topo, err := NewTopology(nodes, tc.granularity, tc.hostname, port, key, kiloNet)
|
||||
if err != nil {
|
||||
t.Errorf("test case %q: failed to generate Topology: %v", tc.name, err)
|
||||
}
|
||||
if diff := pretty.Compare(topo, tc.result); diff != "" {
|
||||
t.Errorf("test case %q: got diff: %v", tc.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustTopo(t *testing.T, nodes map[string]*Node, granularity Granularity, hostname string, port int, key []byte, subnet *net.IPNet) *Topology {
|
||||
topo, err := NewTopology(nodes, granularity, hostname, port, key, subnet)
|
||||
if err != nil {
|
||||
t.Errorf("failed to generate Topology: %v", err)
|
||||
}
|
||||
return topo
|
||||
}
|
||||
|
||||
func TestRoutes(t *testing.T) {
|
||||
nodes, key, port, kiloNet := setup(t)
|
||||
kiloIface := 0
|
||||
privIface := 1
|
||||
pubIface := 2
|
||||
mustTopoForGranularityAndHost := func(granularity Granularity, hostname string) *Topology {
|
||||
return mustTopo(t, nodes, granularity, hostname, port, key, kiloNet)
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
local bool
|
||||
topology *Topology
|
||||
result []*netlink.Route
|
||||
}{
|
||||
{
|
||||
name: "datacenter from a",
|
||||
topology: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["b"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].cidrs[1],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["c"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datacenter from b",
|
||||
topology: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["b"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["b"].Name).Segments[0].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["b"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["b"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datacenter from c",
|
||||
topology: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["c"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: oneAddressCIDR(mustTopoForGranularityAndHost(DataCenterGranularity, nodes["c"].Name).Segments[0].wireGuardIP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["c"].Name).Segments[0].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(mustTopoForGranularityAndHost(DataCenterGranularity, nodes["c"].Name).Segments[1].wireGuardIP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from a",
|
||||
topology: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[1].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["b"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[2].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[2].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["c"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[2].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from b",
|
||||
topology: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[0].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[2].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[2].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["c"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[2].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from c",
|
||||
topology: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[0].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[1].cidrs[0],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["b"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datacenter from a local",
|
||||
local: true,
|
||||
topology: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: nodes["b"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["b"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: nodes["c"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["c"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datacenter from b local",
|
||||
local: true,
|
||||
topology: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["b"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: nodes["a"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["b"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["b"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: nodes["c"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["c"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datacenter from c local",
|
||||
local: true,
|
||||
topology: mustTopoForGranularityAndHost(DataCenterGranularity, nodes["c"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: oneAddressCIDR(mustTopoForGranularityAndHost(DataCenterGranularity, nodes["c"].Name).Segments[0].wireGuardIP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: nodes["a"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(mustTopoForGranularityAndHost(DataCenterGranularity, nodes["c"].Name).Segments[1].wireGuardIP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: nodes["b"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: nodes["b"].InternalIP.IP,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from a local",
|
||||
local: true,
|
||||
topology: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: nodes["b"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["b"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: nodes["c"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[2].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["c"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["a"].Name).Segments[2].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from b local",
|
||||
local: true,
|
||||
topology: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: nodes["a"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: nodes["c"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[2].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["c"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["b"].Name).Segments[2].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node from c local",
|
||||
local: true,
|
||||
topology: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name),
|
||||
result: []*netlink.Route{
|
||||
{
|
||||
Dst: nodes["a"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["a"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[0].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: nodes["b"].Subnet,
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
{
|
||||
Dst: oneAddressCIDR(nodes["b"].InternalIP.IP),
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: mustTopoForGranularityAndHost(NodeGranularity, nodes["c"].Name).Segments[1].wireGuardIP,
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
routes := tc.topology.Routes(kiloIface, privIface, pubIface, tc.local, NeverEncapsulate)
|
||||
if diff := pretty.Compare(routes, tc.result); diff != "" {
|
||||
t.Errorf("test case %q: got diff: %v", tc.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConf(t *testing.T) {
|
||||
nodes, key, port, kiloNet := setup(t)
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
topology *Topology
|
||||
result string
|
||||
}{
|
||||
{
|
||||
name: "datacenter from a",
|
||||
topology: mustTopo(t, nodes, DataCenterGranularity, nodes["a"].Name, port, key, kiloNet),
|
||||
result: `[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
PublicKey = key2
|
||||
Endpoint = 10.1.0.2:51820
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "datacenter from b",
|
||||
topology: mustTopo(t, nodes, DataCenterGranularity, nodes["b"].Name, port, key, kiloNet),
|
||||
result: `[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
PublicKey = key1
|
||||
Endpoint = 10.1.0.1:51820
|
||||
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "datacenter from c",
|
||||
topology: mustTopo(t, nodes, DataCenterGranularity, nodes["c"].Name, port, key, kiloNet),
|
||||
result: `[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
PublicKey = key1
|
||||
Endpoint = 10.1.0.1:51820
|
||||
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "node from a",
|
||||
topology: mustTopo(t, nodes, NodeGranularity, nodes["a"].Name, port, key, kiloNet),
|
||||
result: `[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
PublicKey = key2
|
||||
Endpoint = 10.1.0.2:51820
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.4.0.2/32
|
||||
|
||||
[Peer]
|
||||
PublicKey = key3
|
||||
Endpoint = 10.1.0.3:51820
|
||||
AllowedIPs = 10.2.3.0/24, 192.168.0.2/32, 10.4.0.3/32
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "node from b",
|
||||
topology: mustTopo(t, nodes, NodeGranularity, nodes["b"].Name, port, key, kiloNet),
|
||||
result: `[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
PublicKey = key1
|
||||
Endpoint = 10.1.0.1:51820
|
||||
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
|
||||
|
||||
[Peer]
|
||||
PublicKey = key3
|
||||
Endpoint = 10.1.0.3:51820
|
||||
AllowedIPs = 10.2.3.0/24, 192.168.0.2/32, 10.4.0.3/32
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "node from c",
|
||||
topology: mustTopo(t, nodes, NodeGranularity, nodes["c"].Name, port, key, kiloNet),
|
||||
result: `[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
PublicKey = key1
|
||||
Endpoint = 10.1.0.1:51820
|
||||
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
|
||||
|
||||
[Peer]
|
||||
PublicKey = key2
|
||||
Endpoint = 10.1.0.2:51820
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.4.0.2/32
|
||||
`,
|
||||
},
|
||||
} {
|
||||
conf, err := tc.topology.Conf()
|
||||
if err != nil {
|
||||
t.Errorf("test case %q: failed to generate conf: %v", tc.name, err)
|
||||
}
|
||||
if string(conf) != tc.result {
|
||||
t.Errorf("test case %q: expected %s got %s", tc.name, tc.result, string(conf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindLeader(t *testing.T) {
|
||||
ip, e1, err := net.ParseCIDR("10.0.0.1/32")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse external IP CIDR: %v", err)
|
||||
}
|
||||
e1.IP = ip
|
||||
ip, e2, err := net.ParseCIDR("8.8.8.8/32")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse external IP CIDR: %v", err)
|
||||
}
|
||||
e2.IP = ip
|
||||
|
||||
nodes := []*Node{
|
||||
{
|
||||
Name: "a",
|
||||
ExternalIP: e1,
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
ExternalIP: e2,
|
||||
},
|
||||
{
|
||||
Name: "c",
|
||||
ExternalIP: e2,
|
||||
},
|
||||
{
|
||||
Name: "d",
|
||||
ExternalIP: e1,
|
||||
Leader: true,
|
||||
},
|
||||
{
|
||||
Name: "2",
|
||||
ExternalIP: e2,
|
||||
Leader: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
nodes []*Node
|
||||
out int
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
nodes: nil,
|
||||
out: 0,
|
||||
},
|
||||
{
|
||||
name: "one",
|
||||
nodes: []*Node{nodes[0]},
|
||||
out: 0,
|
||||
},
|
||||
{
|
||||
name: "non-leaders",
|
||||
nodes: []*Node{nodes[0], nodes[1], nodes[2]},
|
||||
out: 1,
|
||||
},
|
||||
{
|
||||
name: "leaders",
|
||||
nodes: []*Node{nodes[3], nodes[4]},
|
||||
out: 1,
|
||||
},
|
||||
{
|
||||
name: "public",
|
||||
nodes: []*Node{nodes[1], nodes[2], nodes[4]},
|
||||
out: 2,
|
||||
},
|
||||
{
|
||||
name: "private",
|
||||
nodes: []*Node{nodes[0], nodes[3]},
|
||||
out: 1,
|
||||
},
|
||||
{
|
||||
name: "all",
|
||||
nodes: nodes,
|
||||
out: 4,
|
||||
},
|
||||
} {
|
||||
l := findLeader(tc.nodes)
|
||||
if l != tc.out {
|
||||
t.Errorf("test case %q: expected %d got %d", tc.name, tc.out, l)
|
||||
}
|
||||
}
|
||||
}
|
173
pkg/route/route.go
Normal file
173
pkg/route/route.go
Normal file
@ -0,0 +1,173 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Table represents a routing table.
|
||||
// Table can safely be used concurrently.
|
||||
type Table struct {
|
||||
errors chan error
|
||||
mu sync.Mutex
|
||||
routes map[string]*netlink.Route
|
||||
subscribed bool
|
||||
|
||||
// Make these functions fields to allow
|
||||
// for testing.
|
||||
add func(*netlink.Route) error
|
||||
del func(*netlink.Route) error
|
||||
}
|
||||
|
||||
// NewTable generates a new table.
|
||||
func NewTable() *Table {
|
||||
return &Table{
|
||||
errors: make(chan error),
|
||||
routes: make(map[string]*netlink.Route),
|
||||
add: netlink.RouteReplace,
|
||||
del: func(r *netlink.Route) error {
|
||||
name := routeToString(r)
|
||||
if name == "" {
|
||||
return errors.New("attempting to delete invalid route")
|
||||
}
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list routes before deletion: %v", err)
|
||||
}
|
||||
for _, route := range routes {
|
||||
if routeToString(&route) == name {
|
||||
return netlink.RouteDel(r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run watches for changes to routes in the table and reconciles
|
||||
// the table against the desired state.
|
||||
func (t *Table) Run(stop <-chan struct{}) (<-chan error, error) {
|
||||
t.mu.Lock()
|
||||
if t.subscribed {
|
||||
t.mu.Unlock()
|
||||
return t.errors, nil
|
||||
}
|
||||
// Ensure a given instance only subscribes once.
|
||||
t.subscribed = true
|
||||
t.mu.Unlock()
|
||||
events := make(chan netlink.RouteUpdate)
|
||||
if err := netlink.RouteSubscribe(events, stop); err != nil {
|
||||
return t.errors, fmt.Errorf("failed to subscribe to route events: %v", err)
|
||||
}
|
||||
go func() {
|
||||
defer close(t.errors)
|
||||
for {
|
||||
var e netlink.RouteUpdate
|
||||
select {
|
||||
case e = <-events:
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
switch e.Type {
|
||||
// Watch for deleted routes to reconcile this table's routes.
|
||||
case unix.RTM_DELROUTE:
|
||||
t.mu.Lock()
|
||||
for _, r := range t.routes {
|
||||
// If any deleted route's destination matches a destination
|
||||
// in the table, reset the corresponding route just in case.
|
||||
if r.Dst.IP.Equal(e.Route.Dst.IP) && r.Dst.Mask.String() == e.Route.Dst.Mask.String() {
|
||||
if err := t.add(r); err != nil {
|
||||
nonBlockingSend(t.errors, fmt.Errorf("failed add route: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
t.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return t.errors, nil
|
||||
}
|
||||
|
||||
// CleanUp will clean up any routes created by the instance.
|
||||
func (t *Table) CleanUp() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
for k, route := range t.routes {
|
||||
if err := t.del(route); err != nil {
|
||||
return fmt.Errorf("failed to delete route: %v", err)
|
||||
}
|
||||
delete(t.routes, k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set idempotently overwrites any routes previously defined
|
||||
// for the table with the given set of routes.
|
||||
func (t *Table) Set(routes []*netlink.Route) error {
|
||||
r := make(map[string]*netlink.Route)
|
||||
for _, route := range routes {
|
||||
if route == nil {
|
||||
continue
|
||||
}
|
||||
r[routeToString(route)] = route
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
for k := range t.routes {
|
||||
if _, ok := r[k]; !ok {
|
||||
if err := t.del(t.routes[k]); err != nil {
|
||||
return fmt.Errorf("failed to delete route: %v", err)
|
||||
}
|
||||
delete(t.routes, k)
|
||||
}
|
||||
}
|
||||
for k := range r {
|
||||
if _, ok := t.routes[k]; !ok {
|
||||
if err := t.add(r[k]); err != nil {
|
||||
return fmt.Errorf("failed to add route %q: %v", routeToString(r[k]), err)
|
||||
}
|
||||
t.routes[k] = r[k]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nonBlockingSend(errors chan<- error, err error) {
|
||||
select {
|
||||
case errors <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func routeToString(route *netlink.Route) string {
|
||||
if route == nil || route.Dst == nil {
|
||||
return ""
|
||||
}
|
||||
src := "-"
|
||||
if route.Src != nil {
|
||||
src = route.Src.String()
|
||||
}
|
||||
gw := "-"
|
||||
if route.Gw != nil {
|
||||
gw = route.Gw.String()
|
||||
}
|
||||
return fmt.Sprintf("dst: %s, via: %s, src: %s, dev: %d", route.Dst.String(), gw, src, route.LinkIndex)
|
||||
}
|
262
pkg/route/route_test.go
Normal file
262
pkg/route/route_test.go
Normal file
@ -0,0 +1,262 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
_, c1, err := net.ParseCIDR("10.2.0.0/24")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CIDR: %v", err)
|
||||
}
|
||||
_, c2, err := net.ParseCIDR("10.1.0.0/24")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CIDR: %v", err)
|
||||
}
|
||||
add := func(backend map[string]*netlink.Route) func(*netlink.Route) error {
|
||||
return func(r *netlink.Route) error {
|
||||
backend[routeToString(r)] = r
|
||||
return nil
|
||||
}
|
||||
}
|
||||
del := func(backend map[string]*netlink.Route) func(*netlink.Route) error {
|
||||
return func(r *netlink.Route) error {
|
||||
delete(backend, routeToString(r))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
adderr := func(backend map[string]*netlink.Route) func(*netlink.Route) error {
|
||||
return func(r *netlink.Route) error {
|
||||
return errors.New(routeToString(r))
|
||||
}
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
routes []*netlink.Route
|
||||
err bool
|
||||
add func(map[string]*netlink.Route) func(*netlink.Route) error
|
||||
del func(map[string]*netlink.Route) func(*netlink.Route) error
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
routes: nil,
|
||||
err: false,
|
||||
add: add,
|
||||
del: del,
|
||||
},
|
||||
{
|
||||
name: "single",
|
||||
routes: []*netlink.Route{
|
||||
{
|
||||
Dst: c1,
|
||||
Gw: net.ParseIP("10.1.0.1"),
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
add: add,
|
||||
del: del,
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
routes: []*netlink.Route{
|
||||
{
|
||||
Dst: c1,
|
||||
Gw: net.ParseIP("10.1.0.1"),
|
||||
},
|
||||
{
|
||||
Dst: c2,
|
||||
Gw: net.ParseIP("127.0.0.1"),
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
add: add,
|
||||
del: del,
|
||||
},
|
||||
{
|
||||
name: "err empty",
|
||||
routes: nil,
|
||||
err: false,
|
||||
add: adderr,
|
||||
del: del,
|
||||
},
|
||||
{
|
||||
name: "err",
|
||||
routes: []*netlink.Route{
|
||||
{
|
||||
Dst: c1,
|
||||
Gw: net.ParseIP("10.1.0.1"),
|
||||
},
|
||||
{
|
||||
Dst: c2,
|
||||
Gw: net.ParseIP("127.0.0.1"),
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
add: adderr,
|
||||
del: del,
|
||||
},
|
||||
} {
|
||||
backend := make(map[string]*netlink.Route)
|
||||
a := tc.add(backend)
|
||||
d := tc.del(backend)
|
||||
table := NewTable()
|
||||
table.add = a
|
||||
table.del = d
|
||||
if err := table.Set(tc.routes); (err != nil) != tc.err {
|
||||
no := "no"
|
||||
if tc.err {
|
||||
no = "an"
|
||||
}
|
||||
t.Errorf("test case %q: got unexpected result: expected %s error, got %v", tc.name, no, err)
|
||||
}
|
||||
// If no error was expected, then compare the backend to the input.
|
||||
if !tc.err {
|
||||
for _, r := range tc.routes {
|
||||
r1 := backend[routeToString(r)]
|
||||
r2 := table.routes[routeToString(r)]
|
||||
if r != r1 || r != r2 {
|
||||
t.Errorf("test case %q: expected all routes to be equal: expected %v, got %v and %v", tc.name, r, r1, r2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanUp(t *testing.T) {
|
||||
_, c1, err := net.ParseCIDR("10.2.0.0/24")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CIDR: %v", err)
|
||||
}
|
||||
_, c2, err := net.ParseCIDR("10.1.0.0/24")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CIDR: %v", err)
|
||||
}
|
||||
add := func(backend map[string]*netlink.Route) func(*netlink.Route) error {
|
||||
return func(r *netlink.Route) error {
|
||||
backend[routeToString(r)] = r
|
||||
return nil
|
||||
}
|
||||
}
|
||||
del := func(backend map[string]*netlink.Route) func(*netlink.Route) error {
|
||||
return func(r *netlink.Route) error {
|
||||
delete(backend, routeToString(r))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
delerr := func(backend map[string]*netlink.Route) func(*netlink.Route) error {
|
||||
return func(r *netlink.Route) error {
|
||||
return errors.New(routeToString(r))
|
||||
}
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
routes []*netlink.Route
|
||||
err bool
|
||||
add func(map[string]*netlink.Route) func(*netlink.Route) error
|
||||
del func(map[string]*netlink.Route) func(*netlink.Route) error
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
routes: nil,
|
||||
err: false,
|
||||
add: add,
|
||||
del: del,
|
||||
},
|
||||
{
|
||||
name: "single",
|
||||
routes: []*netlink.Route{
|
||||
{
|
||||
Dst: c1,
|
||||
Gw: net.ParseIP("10.1.0.1"),
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
add: add,
|
||||
del: del,
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
routes: []*netlink.Route{
|
||||
{
|
||||
Dst: c1,
|
||||
Gw: net.ParseIP("10.1.0.1"),
|
||||
},
|
||||
{
|
||||
Dst: c2,
|
||||
Gw: net.ParseIP("127.0.0.1"),
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
add: add,
|
||||
del: del,
|
||||
},
|
||||
{
|
||||
name: "err empty",
|
||||
routes: nil,
|
||||
err: false,
|
||||
add: add,
|
||||
del: delerr,
|
||||
},
|
||||
{
|
||||
name: "err",
|
||||
routes: []*netlink.Route{
|
||||
{
|
||||
Dst: c1,
|
||||
Gw: net.ParseIP("10.1.0.1"),
|
||||
},
|
||||
{
|
||||
Dst: c2,
|
||||
Gw: net.ParseIP("127.0.0.1"),
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
add: add,
|
||||
del: delerr,
|
||||
},
|
||||
} {
|
||||
backend := make(map[string]*netlink.Route)
|
||||
a := tc.add(backend)
|
||||
d := tc.del(backend)
|
||||
table := NewTable()
|
||||
table.add = a
|
||||
table.del = d
|
||||
if err := table.Set(tc.routes); err != nil {
|
||||
t.Fatalf("test case %q: Set should not fail: %v", tc.name, err)
|
||||
}
|
||||
if err := table.CleanUp(); (err != nil) != tc.err {
|
||||
no := "no"
|
||||
if tc.err {
|
||||
no = "an"
|
||||
}
|
||||
t.Errorf("test case %q: got unexpected result: expected %s error, got %v", tc.name, no, err)
|
||||
}
|
||||
// If no error was expected, then compare the backend to the input.
|
||||
if !tc.err {
|
||||
for _, r := range tc.routes {
|
||||
r1 := backend[routeToString(r)]
|
||||
r2 := table.routes[routeToString(r)]
|
||||
if r1 != nil || r2 != nil {
|
||||
t.Errorf("test case %q: expected all routes to be nil: expected got %v and %v", tc.name, r1, r2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
pkg/version/version.go
Normal file
18
pkg/version/version.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package version
|
||||
|
||||
// Version is the version of Kilo.
|
||||
var Version = "was not built properly"
|
183
pkg/wireguard/wireguard.go
Normal file
183
pkg/wireguard/wireguard.go
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
type wgLink struct {
|
||||
a netlink.LinkAttrs
|
||||
t string
|
||||
}
|
||||
|
||||
func (w wgLink) Attrs() *netlink.LinkAttrs {
|
||||
return &w.a
|
||||
}
|
||||
|
||||
func (w wgLink) Type() string {
|
||||
return w.t
|
||||
}
|
||||
|
||||
// New creates a new WireGuard interface.
|
||||
func New(prefix string) (int, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to list links: %v", err)
|
||||
}
|
||||
max := 0
|
||||
re := regexp.MustCompile(fmt.Sprintf("^%s([0-9]+)$", prefix))
|
||||
for _, link := range links {
|
||||
if matches := re.FindStringSubmatch(link.Attrs().Name); len(matches) == 2 {
|
||||
i, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
// This should never happen.
|
||||
return 0, fmt.Errorf("failed to parse digits as an integer: %v", err)
|
||||
}
|
||||
if i >= max {
|
||||
max = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
name := fmt.Sprintf("%s%d", prefix, max)
|
||||
wl := wgLink{a: netlink.NewLinkAttrs(), t: "wireguard"}
|
||||
wl.a.Name = name
|
||||
if err := netlink.LinkAdd(wl); err != nil {
|
||||
return 0, fmt.Errorf("failed to create interface %s: %v", name, err)
|
||||
}
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get interface index: %v", err)
|
||||
}
|
||||
return link.Attrs().Index, nil
|
||||
}
|
||||
|
||||
// Keys generates a WireGuard private and public key-pair.
|
||||
func Keys() ([]byte, []byte, error) {
|
||||
private, err := GenKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate private key: %v", err)
|
||||
}
|
||||
public, err := PubKey(private)
|
||||
return private, public, err
|
||||
}
|
||||
|
||||
// GenKey generates a WireGuard private key.
|
||||
func GenKey() ([]byte, error) {
|
||||
return exec.Command("wg", "genkey").Output()
|
||||
}
|
||||
|
||||
// PubKey generates a WireGuard public key for a given private key.
|
||||
func PubKey(key []byte) ([]byte, error) {
|
||||
cmd := exec.Command("wg", "pubkey")
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open pipe to stdin: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
stdin.Write(key)
|
||||
}()
|
||||
|
||||
public, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate public key: %v", err)
|
||||
}
|
||||
return public, nil
|
||||
}
|
||||
|
||||
// SetConf applies a WireGuard configuration file to the given interface.
|
||||
func SetConf(iface string, path string) error {
|
||||
cmd := exec.Command("wg", "setconf", iface, path)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to apply the WireGuard configuration: %s", stderr.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowConf gets the WireGuard configuration for the given interface.
|
||||
func ShowConf(iface string) ([]byte, error) {
|
||||
cmd := exec.Command("wg", "showconf", iface)
|
||||
var stderr, stdout bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("failed to read the WireGuard configuration: %s", stderr.String())
|
||||
}
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
// CompareConf compares two WireGuard configurations.
|
||||
// It returns true if they are equal, false if they are not,
|
||||
// and any error that was encountered.
|
||||
// Note: CompareConf only goes one level deep, as WireGuard
|
||||
// configurations are not nested further than that.
|
||||
func CompareConf(a, b []byte) (bool, error) {
|
||||
iniA, err := ini.Load(a)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse configuration: %v", err)
|
||||
}
|
||||
iniB, err := ini.Load(b)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse configuration: %v", err)
|
||||
}
|
||||
secsA, secsB := iniA.SectionStrings(), iniB.SectionStrings()
|
||||
if len(secsA) != len(secsB) {
|
||||
return false, nil
|
||||
}
|
||||
sort.Strings(secsA)
|
||||
sort.Strings(secsB)
|
||||
var keysA, keysB []string
|
||||
var valsA, valsB []string
|
||||
for i := range secsA {
|
||||
if secsA[i] != secsB[i] {
|
||||
return false, nil
|
||||
}
|
||||
keysA, keysB = iniA.Section(secsA[i]).KeyStrings(), iniB.Section(secsB[i]).KeyStrings()
|
||||
if len(keysA) != len(keysB) {
|
||||
return false, nil
|
||||
}
|
||||
sort.Strings(keysA)
|
||||
sort.Strings(keysB)
|
||||
for j := range keysA {
|
||||
if keysA[j] != keysB[j] {
|
||||
return false, nil
|
||||
}
|
||||
valsA, valsB = iniA.Section(secsA[i]).Key(keysA[j]).Strings(","), iniB.Section(secsB[i]).Key(keysB[j]).Strings(",")
|
||||
if len(valsA) != len(valsB) {
|
||||
return false, nil
|
||||
}
|
||||
sort.Strings(valsA)
|
||||
sort.Strings(valsB)
|
||||
for k := range valsA {
|
||||
if valsA[k] != valsB[k] {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
143
pkg/wireguard/wireguard_test.go
Normal file
143
pkg/wireguard/wireguard_test.go
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareConf(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
a []byte
|
||||
b []byte
|
||||
out bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
a: []byte{},
|
||||
b: []byte{},
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
name: "key and value order",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Interface]
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
|
||||
[Peer]
|
||||
PublicKey = key
|
||||
AllowedIPs = 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32, 10.2.2.0/24
|
||||
Endpoint = 10.1.0.2:51820
|
||||
`),
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
name: "whitespace",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Interface]
|
||||
PrivateKey=private
|
||||
ListenPort=51820
|
||||
[Peer]
|
||||
Endpoint=10.1.0.2:51820
|
||||
PublicKey=key
|
||||
AllowedIPs=10.2.2.0/24,192.168.0.1/32,10.2.3.0/24,192.168.0.2/32,10.4.0.2/32
|
||||
`),
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
name: "missing key",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
name: "section order",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
|
||||
[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
`),
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
name: "one empty",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(``),
|
||||
out: false,
|
||||
},
|
||||
} {
|
||||
equal, err := CompareConf(tc.a, tc.b)
|
||||
if err != nil {
|
||||
t.Errorf("test case %q: got unexpected error: %v", tc.name, err)
|
||||
}
|
||||
if equal != tc.out {
|
||||
t.Errorf("test case %q: expected %t, got %t", tc.name, tc.out, equal)
|
||||
}
|
||||
}
|
||||
}
|
10
vendor/github.com/awalterschulze/gographviz/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/awalterschulze/gographviz/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
before_install:
|
||||
- ./install-godeps.sh
|
||||
|
||||
script:
|
||||
- make travis
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8
|
15
vendor/github.com/awalterschulze/gographviz/AUTHORS
generated
vendored
Normal file
15
vendor/github.com/awalterschulze/gographviz/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# This is the official list of GoGraphviz authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS file, which
|
||||
# lists people. For example, employees are listed in CONTRIBUTORS,
|
||||
# but not in AUTHORS, because the employer holds the copyright.
|
||||
|
||||
# Names should be added to this file as one of
|
||||
# Organization's name
|
||||
# Individual's name <submission email address>
|
||||
# Individual's name <submission email address> <email2> <emailN>
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Vastech SA (PTY) LTD
|
||||
Xavier Chassin <xavier.chassin@live.fr>
|
||||
Walter Schulze <awalterschulze@gmail.com>
|
5
vendor/github.com/awalterschulze/gographviz/CONTRIBUTORS
generated
vendored
Normal file
5
vendor/github.com/awalterschulze/gographviz/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
Robin Eklind <r.eklind.87@gmail.com>
|
||||
Walter Schulze <awalterschulze@gmail.com>
|
||||
Xuanyi Chew <chewxy@gmail.com>
|
||||
Nathan Kitchen <nathan.kitchen@gmail.com>
|
||||
Ruud Kamphuis <https://github.com/ruudk>
|
46
vendor/github.com/awalterschulze/gographviz/LICENSE
generated
vendored
Normal file
46
vendor/github.com/awalterschulze/gographviz/LICENSE
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
Copyright 2013 GoGraphviz Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Portions of gocc's source code has been derived from Go, and are covered by the
|
||||
following license:
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
16
vendor/github.com/awalterschulze/gographviz/Makefile
generated
vendored
Normal file
16
vendor/github.com/awalterschulze/gographviz/Makefile
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
regenerate:
|
||||
go install github.com/goccmack/gocc
|
||||
gocc -zip -o ./internal/ dot.bnf
|
||||
find . -type f -name '*.go' | xargs goimports -w
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
travis:
|
||||
make regenerate
|
||||
go build ./...
|
||||
go test ./...
|
||||
errcheck -ignore 'fmt:[FS]?[Pp]rint*' ./...
|
||||
gofmt -l -s -w .
|
||||
golint -set_exit_status
|
||||
git diff --exit-code
|
39
vendor/github.com/awalterschulze/gographviz/Readme.md
generated
vendored
Normal file
39
vendor/github.com/awalterschulze/gographviz/Readme.md
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
Parses the Graphviz DOT language and creates an interface, in golang, with which to easily create new and manipulate existing graphs which can be written back to the DOT format.
|
||||
|
||||
This parser has been created using [gocc](http://code.google.com/p/gocc).
|
||||
|
||||
### Example (Parse and Edit) ###
|
||||
|
||||
```
|
||||
graphAst, _ := gographviz.ParseString(`digraph G {}`)
|
||||
graph := gographviz.NewGraph()
|
||||
if err := gographviz.Analyse(graphAst, graph); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
graph.AddNode("G", "a", nil)
|
||||
graph.AddNode("G", "b", nil)
|
||||
graph.AddEdge("a", "b", true, nil)
|
||||
output := graph.String()
|
||||
```
|
||||
|
||||
### Documentation ###
|
||||
|
||||
The [godoc](https://godoc.org/github.com/awalterschulze/gographviz) includes some more examples.
|
||||
|
||||
### Installation ###
|
||||
go get github.com/awalterschulze/gographviz
|
||||
|
||||
### Tests ###
|
||||
|
||||
[![Build Status](https://travis-ci.org/awalterschulze/gographviz.svg?branch=master)](https://travis-ci.org/awalterschulze/gographviz)
|
||||
|
||||
### Users ###
|
||||
|
||||
- [aptly](https://github.com/smira/aptly) - Debian repository management tool
|
||||
- [gorgonia](https://github.com/chewxy/gorgonia) - A Library that helps facilitate machine learning in Go
|
||||
- [imagemonkey](https://imagemonkey.io/graph?editor=true) - Let's create our own image dataset
|
||||
- [depviz](https://github.com/moul/depviz) - GitHub dependency visualizer (auto-roadmap)
|
||||
|
||||
### Mentions ###
|
||||
|
||||
[Using Golang and GraphViz to Visualize Complex Grails Applications](http://ilikeorangutans.github.io/2014/05/03/using-golang-and-graphviz-to-visualize-complex-grails-applications/)
|
188
vendor/github.com/awalterschulze/gographviz/analyse.go
generated
vendored
Normal file
188
vendor/github.com/awalterschulze/gographviz/analyse.go
generated
vendored
Normal file
@ -0,0 +1,188 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"github.com/awalterschulze/gographviz/ast"
|
||||
)
|
||||
|
||||
// NewAnalysedGraph creates a Graph structure by analysing an Abstract Syntax Tree representing a parsed graph.
|
||||
func NewAnalysedGraph(graph *ast.Graph) (*Graph, error) {
|
||||
g := NewGraph()
|
||||
if err := Analyse(graph, g); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Analyse analyses an Abstract Syntax Tree representing a parsed graph into a newly created graph structure Interface.
|
||||
func Analyse(graph *ast.Graph, g Interface) error {
|
||||
gerr := newErrCatcher(g)
|
||||
graph.Walk(&graphVisitor{gerr})
|
||||
return gerr.getError()
|
||||
}
|
||||
|
||||
type nilVisitor struct {
|
||||
}
|
||||
|
||||
func (w *nilVisitor) Visit(v ast.Elem) ast.Visitor {
|
||||
return w
|
||||
}
|
||||
|
||||
type graphVisitor struct {
|
||||
g errInterface
|
||||
}
|
||||
|
||||
func (w *graphVisitor) Visit(v ast.Elem) ast.Visitor {
|
||||
graph, ok := v.(*ast.Graph)
|
||||
if !ok {
|
||||
return w
|
||||
}
|
||||
w.g.SetStrict(graph.Strict)
|
||||
w.g.SetDir(graph.Type == ast.DIGRAPH)
|
||||
graphName := graph.ID.String()
|
||||
w.g.SetName(graphName)
|
||||
return newStmtVisitor(w.g, graphName, nil, nil)
|
||||
}
|
||||
|
||||
func newStmtVisitor(g errInterface, graphName string, nodeAttrs, edgeAttrs map[string]string) *stmtVisitor {
|
||||
nodeAttrs = ammend(make(map[string]string), nodeAttrs)
|
||||
edgeAttrs = ammend(make(map[string]string), edgeAttrs)
|
||||
return &stmtVisitor{g, graphName, nodeAttrs, edgeAttrs, make(map[string]string), make(map[string]struct{})}
|
||||
}
|
||||
|
||||
type stmtVisitor struct {
|
||||
g errInterface
|
||||
graphName string
|
||||
currentNodeAttrs map[string]string
|
||||
currentEdgeAttrs map[string]string
|
||||
currentGraphAttrs map[string]string
|
||||
createdNodes map[string]struct{}
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) Visit(v ast.Elem) ast.Visitor {
|
||||
switch s := v.(type) {
|
||||
case ast.NodeStmt:
|
||||
return w.nodeStmt(s)
|
||||
case ast.EdgeStmt:
|
||||
return w.edgeStmt(s)
|
||||
case ast.NodeAttrs:
|
||||
return w.nodeAttrs(s)
|
||||
case ast.EdgeAttrs:
|
||||
return w.edgeAttrs(s)
|
||||
case ast.GraphAttrs:
|
||||
return w.graphAttrs(s)
|
||||
case *ast.SubGraph:
|
||||
return w.subGraph(s)
|
||||
case *ast.Attr:
|
||||
return w.attr(s)
|
||||
case ast.AttrList:
|
||||
return &nilVisitor{}
|
||||
default:
|
||||
//fmt.Fprintf(os.Stderr, "unknown stmt %T\n", v)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func ammend(attrs map[string]string, add map[string]string) map[string]string {
|
||||
for key, value := range add {
|
||||
if _, ok := attrs[key]; !ok {
|
||||
attrs[key] = value
|
||||
}
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func overwrite(attrs map[string]string, overwrite map[string]string) map[string]string {
|
||||
for key, value := range overwrite {
|
||||
attrs[key] = value
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) addNodeFromEdge(nodeID string) {
|
||||
if _, ok := w.createdNodes[nodeID]; !ok {
|
||||
w.createdNodes[nodeID] = struct{}{}
|
||||
w.g.AddNode(w.graphName, nodeID, w.currentNodeAttrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) nodeStmt(stmt ast.NodeStmt) ast.Visitor {
|
||||
nodeID := stmt.NodeID.String()
|
||||
var defaultAttrs map[string]string
|
||||
if _, ok := w.createdNodes[nodeID]; !ok {
|
||||
defaultAttrs = w.currentNodeAttrs
|
||||
w.createdNodes[nodeID] = struct{}{}
|
||||
}
|
||||
// else the defaults were already inherited
|
||||
attrs := ammend(stmt.Attrs.GetMap(), defaultAttrs)
|
||||
w.g.AddNode(w.graphName, nodeID, attrs)
|
||||
return &nilVisitor{}
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) edgeStmt(stmt ast.EdgeStmt) ast.Visitor {
|
||||
attrs := stmt.Attrs.GetMap()
|
||||
attrs = ammend(attrs, w.currentEdgeAttrs)
|
||||
src := stmt.Source.GetID()
|
||||
srcName := src.String()
|
||||
if stmt.Source.IsNode() {
|
||||
w.addNodeFromEdge(srcName)
|
||||
}
|
||||
srcPort := stmt.Source.GetPort()
|
||||
for i := range stmt.EdgeRHS {
|
||||
directed := bool(stmt.EdgeRHS[i].Op)
|
||||
dst := stmt.EdgeRHS[i].Destination.GetID()
|
||||
dstName := dst.String()
|
||||
if stmt.EdgeRHS[i].Destination.IsNode() {
|
||||
w.addNodeFromEdge(dstName)
|
||||
}
|
||||
dstPort := stmt.EdgeRHS[i].Destination.GetPort()
|
||||
w.g.AddPortEdge(srcName, srcPort.String(), dstName, dstPort.String(), directed, attrs)
|
||||
src = dst
|
||||
srcPort = dstPort
|
||||
srcName = dstName
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) nodeAttrs(stmt ast.NodeAttrs) ast.Visitor {
|
||||
w.currentNodeAttrs = overwrite(w.currentNodeAttrs, ast.AttrList(stmt).GetMap())
|
||||
return &nilVisitor{}
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) edgeAttrs(stmt ast.EdgeAttrs) ast.Visitor {
|
||||
w.currentEdgeAttrs = overwrite(w.currentEdgeAttrs, ast.AttrList(stmt).GetMap())
|
||||
return &nilVisitor{}
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) graphAttrs(stmt ast.GraphAttrs) ast.Visitor {
|
||||
attrs := ast.AttrList(stmt).GetMap()
|
||||
for key, value := range attrs {
|
||||
w.g.AddAttr(w.graphName, key, value)
|
||||
}
|
||||
w.currentGraphAttrs = overwrite(w.currentGraphAttrs, attrs)
|
||||
return &nilVisitor{}
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) subGraph(stmt *ast.SubGraph) ast.Visitor {
|
||||
subGraphName := stmt.ID.String()
|
||||
w.g.AddSubGraph(w.graphName, subGraphName, w.currentGraphAttrs)
|
||||
return newStmtVisitor(w.g, subGraphName, w.currentNodeAttrs, w.currentEdgeAttrs)
|
||||
}
|
||||
|
||||
func (w *stmtVisitor) attr(stmt *ast.Attr) ast.Visitor {
|
||||
w.g.AddAttr(w.graphName, stmt.Field.String(), stmt.Value.String())
|
||||
return w
|
||||
}
|
684
vendor/github.com/awalterschulze/gographviz/ast/ast.go
generated
vendored
Normal file
684
vendor/github.com/awalterschulze/gographviz/ast/ast.go
generated
vendored
Normal file
@ -0,0 +1,684 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
//Abstract Syntax Tree representing the DOT grammar
|
||||
package ast
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/awalterschulze/gographviz/internal/token"
|
||||
)
|
||||
|
||||
var (
|
||||
r = rand.New(rand.NewSource(1234))
|
||||
)
|
||||
|
||||
type Visitor interface {
|
||||
Visit(e Elem) Visitor
|
||||
}
|
||||
|
||||
type Elem interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
type Walkable interface {
|
||||
Walk(v Visitor)
|
||||
}
|
||||
|
||||
type Attrib interface{}
|
||||
|
||||
type Bool bool
|
||||
|
||||
const (
|
||||
FALSE = Bool(false)
|
||||
TRUE = Bool(true)
|
||||
)
|
||||
|
||||
func (this Bool) String() string {
|
||||
if this {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (this Bool) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v.Visit(this)
|
||||
}
|
||||
|
||||
type GraphType bool
|
||||
|
||||
const (
|
||||
GRAPH = GraphType(false)
|
||||
DIGRAPH = GraphType(true)
|
||||
)
|
||||
|
||||
func (this GraphType) String() string {
|
||||
if this {
|
||||
return "digraph"
|
||||
}
|
||||
return "graph"
|
||||
}
|
||||
|
||||
func (this GraphType) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v.Visit(this)
|
||||
}
|
||||
|
||||
type Graph struct {
|
||||
Type GraphType
|
||||
Strict bool
|
||||
ID ID
|
||||
StmtList StmtList
|
||||
}
|
||||
|
||||
func NewGraph(t, strict, id, l Attrib) (*Graph, error) {
|
||||
g := &Graph{Type: t.(GraphType), Strict: bool(strict.(Bool)), ID: ID("")}
|
||||
if id != nil {
|
||||
g.ID = id.(ID)
|
||||
}
|
||||
if l != nil {
|
||||
g.StmtList = l.(StmtList)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (this *Graph) String() string {
|
||||
var s string
|
||||
if this.Strict {
|
||||
s += "strict "
|
||||
}
|
||||
s += this.Type.String() + " " + this.ID.String() + " {\n"
|
||||
if this.StmtList != nil {
|
||||
s += this.StmtList.String()
|
||||
}
|
||||
s += "\n}\n"
|
||||
return s
|
||||
}
|
||||
|
||||
func (this *Graph) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
this.Type.Walk(v)
|
||||
this.ID.Walk(v)
|
||||
this.StmtList.Walk(v)
|
||||
}
|
||||
|
||||
type StmtList []Stmt
|
||||
|
||||
func NewStmtList(s Attrib) (StmtList, error) {
|
||||
ss := make(StmtList, 1)
|
||||
ss[0] = s.(Stmt)
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
func AppendStmtList(ss, s Attrib) (StmtList, error) {
|
||||
this := ss.(StmtList)
|
||||
this = append(this, s.(Stmt))
|
||||
return this, nil
|
||||
}
|
||||
|
||||
func (this StmtList) String() string {
|
||||
if len(this) == 0 {
|
||||
return ""
|
||||
}
|
||||
s := ""
|
||||
for i := 0; i < len(this); i++ {
|
||||
ss := this[i].String()
|
||||
if len(ss) > 0 {
|
||||
s += "\t" + ss + ";\n"
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (this StmtList) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
for i := range this {
|
||||
this[i].Walk(v)
|
||||
}
|
||||
}
|
||||
|
||||
type Stmt interface {
|
||||
Elem
|
||||
Walkable
|
||||
isStmt()
|
||||
}
|
||||
|
||||
func (this NodeStmt) isStmt() {}
|
||||
func (this EdgeStmt) isStmt() {}
|
||||
func (this EdgeAttrs) isStmt() {}
|
||||
func (this NodeAttrs) isStmt() {}
|
||||
func (this GraphAttrs) isStmt() {}
|
||||
func (this *SubGraph) isStmt() {}
|
||||
func (this *Attr) isStmt() {}
|
||||
|
||||
type SubGraph struct {
|
||||
ID ID
|
||||
StmtList StmtList
|
||||
}
|
||||
|
||||
func NewSubGraph(id, l Attrib) (*SubGraph, error) {
|
||||
g := &SubGraph{ID: ID(fmt.Sprintf("anon%d", r.Int63()))}
|
||||
if id != nil {
|
||||
if len(id.(ID)) > 0 {
|
||||
g.ID = id.(ID)
|
||||
}
|
||||
}
|
||||
if l != nil {
|
||||
g.StmtList = l.(StmtList)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (this *SubGraph) GetID() ID {
|
||||
return this.ID
|
||||
}
|
||||
|
||||
func (this *SubGraph) GetPort() Port {
|
||||
return NewPort(nil, nil)
|
||||
}
|
||||
|
||||
func (this *SubGraph) String() string {
|
||||
gName := this.ID.String()
|
||||
if strings.HasPrefix(gName, "anon") {
|
||||
gName = ""
|
||||
}
|
||||
s := "subgraph " + this.ID.String() + " {\n"
|
||||
if this.StmtList != nil {
|
||||
s += this.StmtList.String()
|
||||
}
|
||||
s += "\n}\n"
|
||||
return s
|
||||
}
|
||||
|
||||
func (this *SubGraph) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
this.ID.Walk(v)
|
||||
this.StmtList.Walk(v)
|
||||
}
|
||||
|
||||
type EdgeAttrs AttrList
|
||||
|
||||
func NewEdgeAttrs(a Attrib) (EdgeAttrs, error) {
|
||||
return EdgeAttrs(a.(AttrList)), nil
|
||||
}
|
||||
|
||||
func (this EdgeAttrs) String() string {
|
||||
s := AttrList(this).String()
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return `edge ` + s
|
||||
}
|
||||
|
||||
func (this EdgeAttrs) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
for i := range this {
|
||||
this[i].Walk(v)
|
||||
}
|
||||
}
|
||||
|
||||
type NodeAttrs AttrList
|
||||
|
||||
func NewNodeAttrs(a Attrib) (NodeAttrs, error) {
|
||||
return NodeAttrs(a.(AttrList)), nil
|
||||
}
|
||||
|
||||
func (this NodeAttrs) String() string {
|
||||
s := AttrList(this).String()
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return `node ` + s
|
||||
}
|
||||
|
||||
func (this NodeAttrs) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
for i := range this {
|
||||
this[i].Walk(v)
|
||||
}
|
||||
}
|
||||
|
||||
type GraphAttrs AttrList
|
||||
|
||||
func NewGraphAttrs(a Attrib) (GraphAttrs, error) {
|
||||
return GraphAttrs(a.(AttrList)), nil
|
||||
}
|
||||
|
||||
func (this GraphAttrs) String() string {
|
||||
s := AttrList(this).String()
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return `graph ` + s
|
||||
}
|
||||
|
||||
func (this GraphAttrs) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
for i := range this {
|
||||
this[i].Walk(v)
|
||||
}
|
||||
}
|
||||
|
||||
type AttrList []AList
|
||||
|
||||
func NewAttrList(a Attrib) (AttrList, error) {
|
||||
as := make(AttrList, 0)
|
||||
if a != nil {
|
||||
as = append(as, a.(AList))
|
||||
}
|
||||
return as, nil
|
||||
}
|
||||
|
||||
func AppendAttrList(as, a Attrib) (AttrList, error) {
|
||||
this := as.(AttrList)
|
||||
if a == nil {
|
||||
return this, nil
|
||||
}
|
||||
this = append(this, a.(AList))
|
||||
return this, nil
|
||||
}
|
||||
|
||||
func (this AttrList) String() string {
|
||||
s := ""
|
||||
for _, alist := range this {
|
||||
ss := alist.String()
|
||||
if len(ss) > 0 {
|
||||
s += "[ " + ss + " ] "
|
||||
}
|
||||
}
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (this AttrList) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
for i := range this {
|
||||
this[i].Walk(v)
|
||||
}
|
||||
}
|
||||
|
||||
func PutMap(attrmap map[string]string) AttrList {
|
||||
attrlist := make(AttrList, 1)
|
||||
attrlist[0] = make(AList, 0)
|
||||
keys := make([]string, 0, len(attrmap))
|
||||
for key := range attrmap {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, name := range keys {
|
||||
value := attrmap[name]
|
||||
attrlist[0] = append(attrlist[0], &Attr{ID(name), ID(value)})
|
||||
}
|
||||
return attrlist
|
||||
}
|
||||
|
||||
func (this AttrList) GetMap() map[string]string {
|
||||
attrs := make(map[string]string)
|
||||
for _, alist := range this {
|
||||
for _, attr := range alist {
|
||||
attrs[attr.Field.String()] = attr.Value.String()
|
||||
}
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
type AList []*Attr
|
||||
|
||||
func NewAList(a Attrib) (AList, error) {
|
||||
as := make(AList, 1)
|
||||
as[0] = a.(*Attr)
|
||||
return as, nil
|
||||
}
|
||||
|
||||
func AppendAList(as, a Attrib) (AList, error) {
|
||||
this := as.(AList)
|
||||
attr := a.(*Attr)
|
||||
this = append(this, attr)
|
||||
return this, nil
|
||||
}
|
||||
|
||||
func (this AList) String() string {
|
||||
if len(this) == 0 {
|
||||
return ""
|
||||
}
|
||||
str := this[0].String()
|
||||
for i := 1; i < len(this); i++ {
|
||||
str += `, ` + this[i].String()
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (this AList) Walk(v Visitor) {
|
||||
v = v.Visit(this)
|
||||
for i := range this {
|
||||
this[i].Walk(v)
|
||||
}
|
||||
}
|
||||
|
||||
type Attr struct {
|
||||
Field ID
|
||||
Value ID
|
||||
}
|
||||
|
||||
func NewAttr(f, v Attrib) (*Attr, error) {
|
||||
a := &Attr{Field: f.(ID)}
|
||||
a.Value = ID("true")
|
||||
if v != nil {
|
||||
ok := false
|
||||
a.Value, ok = v.(ID)
|
||||
if !ok {
|
||||
return nil, errors.New(fmt.Sprintf("value = %v", v))
|
||||
}
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (this *Attr) String() string {
|
||||
return this.Field.String() + `=` + this.Value.String()
|
||||
}
|
||||
|
||||
func (this *Attr) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
this.Field.Walk(v)
|
||||
this.Value.Walk(v)
|
||||
}
|
||||
|
||||
type Location interface {
|
||||
Elem
|
||||
Walkable
|
||||
isLocation()
|
||||
GetID() ID
|
||||
GetPort() Port
|
||||
IsNode() bool
|
||||
}
|
||||
|
||||
func (this *NodeID) isLocation() {}
|
||||
func (this *NodeID) IsNode() bool { return true }
|
||||
func (this *SubGraph) isLocation() {}
|
||||
func (this *SubGraph) IsNode() bool { return false }
|
||||
|
||||
type EdgeStmt struct {
|
||||
Source Location
|
||||
EdgeRHS EdgeRHS
|
||||
Attrs AttrList
|
||||
}
|
||||
|
||||
func NewEdgeStmt(id, e, attrs Attrib) (*EdgeStmt, error) {
|
||||
var a AttrList = nil
|
||||
var err error = nil
|
||||
if attrs == nil {
|
||||
a, err = NewAttrList(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
a = attrs.(AttrList)
|
||||
}
|
||||
return &EdgeStmt{id.(Location), e.(EdgeRHS), a}, nil
|
||||
}
|
||||
|
||||
func (this EdgeStmt) String() string {
|
||||
return strings.TrimSpace(this.Source.String() + this.EdgeRHS.String() + this.Attrs.String())
|
||||
}
|
||||
|
||||
func (this EdgeStmt) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
this.Source.Walk(v)
|
||||
this.EdgeRHS.Walk(v)
|
||||
this.Attrs.Walk(v)
|
||||
}
|
||||
|
||||
type EdgeRHS []*EdgeRH
|
||||
|
||||
func NewEdgeRHS(op, id Attrib) (EdgeRHS, error) {
|
||||
return EdgeRHS{&EdgeRH{op.(EdgeOp), id.(Location)}}, nil
|
||||
}
|
||||
|
||||
func AppendEdgeRHS(e, op, id Attrib) (EdgeRHS, error) {
|
||||
erhs := e.(EdgeRHS)
|
||||
erhs = append(erhs, &EdgeRH{op.(EdgeOp), id.(Location)})
|
||||
return erhs, nil
|
||||
}
|
||||
|
||||
func (this EdgeRHS) String() string {
|
||||
s := ""
|
||||
for i := range this {
|
||||
s += this[i].String()
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func (this EdgeRHS) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
for i := range this {
|
||||
this[i].Walk(v)
|
||||
}
|
||||
}
|
||||
|
||||
type EdgeRH struct {
|
||||
Op EdgeOp
|
||||
Destination Location
|
||||
}
|
||||
|
||||
func (this *EdgeRH) String() string {
|
||||
return strings.TrimSpace(this.Op.String() + this.Destination.String())
|
||||
}
|
||||
|
||||
func (this *EdgeRH) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
this.Op.Walk(v)
|
||||
this.Destination.Walk(v)
|
||||
}
|
||||
|
||||
type NodeStmt struct {
|
||||
NodeID *NodeID
|
||||
Attrs AttrList
|
||||
}
|
||||
|
||||
func NewNodeStmt(id, attrs Attrib) (*NodeStmt, error) {
|
||||
nid := id.(*NodeID)
|
||||
var a AttrList = nil
|
||||
var err error = nil
|
||||
if attrs == nil {
|
||||
a, err = NewAttrList(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
a = attrs.(AttrList)
|
||||
}
|
||||
return &NodeStmt{nid, a}, nil
|
||||
}
|
||||
|
||||
func (this NodeStmt) String() string {
|
||||
return strings.TrimSpace(this.NodeID.String() + ` ` + this.Attrs.String())
|
||||
}
|
||||
|
||||
func (this NodeStmt) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
this.NodeID.Walk(v)
|
||||
this.Attrs.Walk(v)
|
||||
}
|
||||
|
||||
type EdgeOp bool
|
||||
|
||||
const (
|
||||
DIRECTED EdgeOp = true
|
||||
UNDIRECTED EdgeOp = false
|
||||
)
|
||||
|
||||
func (this EdgeOp) String() string {
|
||||
if this == DIRECTED {
|
||||
return "->"
|
||||
}
|
||||
return "--"
|
||||
}
|
||||
|
||||
func (this EdgeOp) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v.Visit(this)
|
||||
}
|
||||
|
||||
type NodeID struct {
|
||||
ID ID
|
||||
Port Port
|
||||
}
|
||||
|
||||
func NewNodeID(id, port Attrib) (*NodeID, error) {
|
||||
if port == nil {
|
||||
return &NodeID{id.(ID), Port{"", ""}}, nil
|
||||
}
|
||||
return &NodeID{id.(ID), port.(Port)}, nil
|
||||
}
|
||||
|
||||
func MakeNodeID(id string, port string) *NodeID {
|
||||
p := Port{"", ""}
|
||||
if len(port) > 0 {
|
||||
ps := strings.Split(port, ":")
|
||||
p.ID1 = ID(ps[0])
|
||||
if len(ps) > 1 {
|
||||
p.ID2 = ID(ps[1])
|
||||
}
|
||||
}
|
||||
return &NodeID{ID(id), p}
|
||||
}
|
||||
|
||||
func (this *NodeID) String() string {
|
||||
return this.ID.String() + this.Port.String()
|
||||
}
|
||||
|
||||
func (this *NodeID) GetID() ID {
|
||||
return this.ID
|
||||
}
|
||||
|
||||
func (this *NodeID) GetPort() Port {
|
||||
return this.Port
|
||||
}
|
||||
|
||||
func (this *NodeID) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
this.ID.Walk(v)
|
||||
this.Port.Walk(v)
|
||||
}
|
||||
|
||||
//TODO semantic analysis should decide which ID is an ID and which is a Compass Point
|
||||
type Port struct {
|
||||
ID1 ID
|
||||
ID2 ID
|
||||
}
|
||||
|
||||
func NewPort(id1, id2 Attrib) Port {
|
||||
port := Port{ID(""), ID("")}
|
||||
if id1 != nil {
|
||||
port.ID1 = id1.(ID)
|
||||
}
|
||||
if id2 != nil {
|
||||
port.ID2 = id2.(ID)
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
func (this Port) String() string {
|
||||
if len(this.ID1) == 0 {
|
||||
return ""
|
||||
}
|
||||
s := ":" + this.ID1.String()
|
||||
if len(this.ID2) > 0 {
|
||||
s += ":" + this.ID2.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (this Port) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v = v.Visit(this)
|
||||
this.ID1.Walk(v)
|
||||
this.ID2.Walk(v)
|
||||
}
|
||||
|
||||
type ID string
|
||||
|
||||
func NewID(id Attrib) (ID, error) {
|
||||
if id == nil {
|
||||
return ID(""), nil
|
||||
}
|
||||
id_lit := string(id.(*token.Token).Lit)
|
||||
return ID(id_lit), nil
|
||||
}
|
||||
|
||||
func (this ID) String() string {
|
||||
return string(this)
|
||||
}
|
||||
|
||||
func (this ID) Walk(v Visitor) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
v.Visit(this)
|
||||
}
|
559
vendor/github.com/awalterschulze/gographviz/attr.go
generated
vendored
Normal file
559
vendor/github.com/awalterschulze/gographviz/attr.go
generated
vendored
Normal file
@ -0,0 +1,559 @@
|
||||
//Copyright 2017 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http)://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Attr is an attribute key
|
||||
type Attr string
|
||||
|
||||
// NewAttr creates a new attribute key by checking whether it is a valid key
|
||||
func NewAttr(key string) (Attr, error) {
|
||||
a, ok := validAttrs[key]
|
||||
if !ok {
|
||||
return Attr(""), fmt.Errorf("%s is not a valid attribute", key)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
const (
|
||||
// Damping http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:Damping
|
||||
Damping Attr = "Damping"
|
||||
// K http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:K
|
||||
K Attr = "K"
|
||||
// URL http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:URL
|
||||
URL Attr = "URL"
|
||||
// Background http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:_background
|
||||
Background Attr = "_background"
|
||||
// Area http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:area
|
||||
Area Attr = "area"
|
||||
// ArrowHead http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:arrowhead
|
||||
ArrowHead Attr = "arrowhead"
|
||||
// ArrowSize http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:arrowsize
|
||||
ArrowSize Attr = "arrowsize"
|
||||
// ArrowTail http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:arrowtail
|
||||
ArrowTail Attr = "arrowtail"
|
||||
// BB http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:bb
|
||||
BB Attr = "bb"
|
||||
// BgColor http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:bgcolor
|
||||
BgColor Attr = "bgcolor"
|
||||
// Center http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:center
|
||||
Center Attr = "center"
|
||||
// Charset http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:charset
|
||||
Charset Attr = "charset"
|
||||
// ClusterRank http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:clusterrank
|
||||
ClusterRank Attr = "clusterrank"
|
||||
// Color http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:color
|
||||
Color Attr = "color"
|
||||
// ColorScheme http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:colorscheme
|
||||
ColorScheme Attr = "colorscheme"
|
||||
// Comment http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:comment
|
||||
Comment Attr = "comment"
|
||||
// Compound http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:compound
|
||||
Compound Attr = "compound"
|
||||
// Concentrate http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:concentrate
|
||||
Concentrate Attr = "concentrate"
|
||||
// Constraint http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:constraint
|
||||
Constraint Attr = "constraint"
|
||||
// Decorate http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:decorate
|
||||
Decorate Attr = "decorate"
|
||||
// DefaultDist http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:defaultdist
|
||||
DefaultDist Attr = "defaultdist"
|
||||
// Dim http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:dim
|
||||
Dim Attr = "dim"
|
||||
// Dimen http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:dimen
|
||||
Dimen Attr = "dimen"
|
||||
// Dir http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:dir
|
||||
Dir Attr = "dir"
|
||||
// DirEdgeConstraints http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:dir
|
||||
DirEdgeConstraints Attr = "diredgeconstraints"
|
||||
// Distortion http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:distortion
|
||||
Distortion Attr = "distortion"
|
||||
// DPI http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:dpi
|
||||
DPI Attr = "dpi"
|
||||
// EdgeURL http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d::edgeURL
|
||||
EdgeURL Attr = "edgeURL"
|
||||
// EdgeHREF http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d::edgehref
|
||||
EdgeHREF Attr = "edgehref"
|
||||
// EdgeTarget http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d::edgetarget
|
||||
EdgeTarget Attr = "edgetarget"
|
||||
// EdgeTooltip http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d::edgetooltip
|
||||
EdgeTooltip Attr = "edgetooltip"
|
||||
// Epsilon http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d::epsilon
|
||||
Epsilon Attr = "epsilon"
|
||||
// ESep http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d::epsilon
|
||||
ESep Attr = "esep"
|
||||
// FillColor http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:fillcolor
|
||||
FillColor Attr = "fillcolor"
|
||||
// FixedSize http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:fixedsize
|
||||
FixedSize Attr = "fixedsize"
|
||||
// FontColor http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:fontcolor
|
||||
FontColor Attr = "fontcolor"
|
||||
// FontName http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:fontname
|
||||
FontName Attr = "fontname"
|
||||
// FontNames http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:fontnames
|
||||
FontNames Attr = "fontnames"
|
||||
// FontPath http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:fontpath
|
||||
FontPath Attr = "fontpath"
|
||||
// FontSize http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:fontsize
|
||||
FontSize Attr = "fontsize"
|
||||
// ForceLabels http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:forcelabels
|
||||
ForceLabels Attr = "forcelabels"
|
||||
// GradientAngle http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:gradientangle
|
||||
GradientAngle Attr = "gradientangle"
|
||||
// Group http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:group
|
||||
Group Attr = "group"
|
||||
// HeadURL http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:headURL
|
||||
HeadURL Attr = "headURL"
|
||||
// HeadLP http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:head_lp
|
||||
HeadLP Attr = "head_lp"
|
||||
// HeadClip http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:headclip
|
||||
HeadClip Attr = "headclip"
|
||||
// HeadHREF http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:headhref
|
||||
HeadHREF Attr = "headhref"
|
||||
// HeadLabel http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:headlabel
|
||||
HeadLabel Attr = "headlabel"
|
||||
// HeadPort http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:headport
|
||||
HeadPort Attr = "headport"
|
||||
// HeadTarget http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:headtarget
|
||||
HeadTarget Attr = "headtarget"
|
||||
// HeadTooltip http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:headtooltip
|
||||
HeadTooltip Attr = "headtooltip"
|
||||
// Height http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:height
|
||||
Height Attr = "height"
|
||||
// HREF http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:href
|
||||
HREF Attr = "href"
|
||||
// ID http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:id
|
||||
ID Attr = "id"
|
||||
// Image http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:image
|
||||
Image Attr = "image"
|
||||
// ImagePath http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:imagepath
|
||||
ImagePath Attr = "imagepath"
|
||||
// ImageScale http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:imagescale
|
||||
ImageScale Attr = "imagescale"
|
||||
// InputScale http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:inputscale
|
||||
InputScale Attr = "inputscale"
|
||||
// Label http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:label
|
||||
Label Attr = "label"
|
||||
// LabelURL http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labelURL
|
||||
LabelURL Attr = "labelURL"
|
||||
// LabelScheme http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:label_scheme
|
||||
LabelScheme Attr = "label_scheme"
|
||||
// LabelAngle http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labelangle
|
||||
LabelAngle Attr = "labelangle"
|
||||
// LabelDistance http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labeldistance
|
||||
LabelDistance Attr = "labeldistance"
|
||||
// LabelFloat http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labelfloat
|
||||
LabelFloat Attr = "labelfloat"
|
||||
// LabelFontColor http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labelfontcolor
|
||||
LabelFontColor Attr = "labelfontcolor"
|
||||
// LabelFontName http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labelfontname
|
||||
LabelFontName Attr = "labelfontname"
|
||||
// LabelFontSize http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labelfontsize
|
||||
LabelFontSize Attr = "labelfontsize"
|
||||
// LabelHREF http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labelhref
|
||||
LabelHREF Attr = "labelhref"
|
||||
// LabelJust http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labeljust
|
||||
LabelJust Attr = "labeljust"
|
||||
// LabelLOC http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labelloc
|
||||
LabelLOC Attr = "labelloc"
|
||||
// LabelTarget http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labeltarget
|
||||
LabelTarget Attr = "labeltarget"
|
||||
// LabelTooltip http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:labeltooltip
|
||||
LabelTooltip Attr = "labeltooltip"
|
||||
// Landscape http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:landscape
|
||||
Landscape Attr = "landscape"
|
||||
// Layer http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:layer
|
||||
Layer Attr = "layer"
|
||||
// LayerListSep http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:layerlistsep
|
||||
LayerListSep Attr = "layerlistsep"
|
||||
// Layers http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:layers
|
||||
Layers Attr = "layers"
|
||||
// LayerSelect http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:layerselect
|
||||
LayerSelect Attr = "layerselect"
|
||||
// LayerSep http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:layersep
|
||||
LayerSep Attr = "layersep"
|
||||
// Layout http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:layout
|
||||
Layout Attr = "layout"
|
||||
// Len http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:len
|
||||
Len Attr = "len"
|
||||
// Levels http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:levels
|
||||
Levels Attr = "levels"
|
||||
// LevelsGap http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:levelsgap
|
||||
LevelsGap Attr = "levelsgap"
|
||||
// LHead http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:lhead
|
||||
LHead Attr = "lhead"
|
||||
// LHeight http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:lheight
|
||||
LHeight Attr = "lheight"
|
||||
// LP http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:lp
|
||||
LP Attr = "lp"
|
||||
// LTail http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:ltail
|
||||
LTail Attr = "ltail"
|
||||
// LWidth http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:lwidth
|
||||
LWidth Attr = "lwidth"
|
||||
// Margin http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:margin
|
||||
Margin Attr = "margin"
|
||||
// MaxIter http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:maxiter
|
||||
MaxIter Attr = "maxiter"
|
||||
// MCLimit http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:mclimit
|
||||
MCLimit Attr = "mclimit"
|
||||
// MinDist http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:mindist
|
||||
MinDist Attr = "mindist"
|
||||
// MinLen http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:mindist
|
||||
MinLen Attr = "minlen"
|
||||
// Mode http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:mode
|
||||
Mode Attr = "mode"
|
||||
// Model http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:model
|
||||
Model Attr = "model"
|
||||
// Mosek http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:mosek
|
||||
Mosek Attr = "mosek"
|
||||
// NewRank http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:newrank
|
||||
NewRank Attr = "newrank"
|
||||
// NodeSep http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:nodesep
|
||||
NodeSep Attr = "nodesep"
|
||||
// NoJustify http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:nojustify
|
||||
NoJustify Attr = "nojustify"
|
||||
// Normalize http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:normalize
|
||||
Normalize Attr = "normalize"
|
||||
// NoTranslate http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:notranslate
|
||||
NoTranslate Attr = "notranslate"
|
||||
// NSLimit http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:nslimit
|
||||
NSLimit Attr = "nslimit"
|
||||
// NSLimit1 http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:nslimit1
|
||||
NSLimit1 Attr = "nslimit1"
|
||||
// Ordering http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:nslimit1
|
||||
Ordering Attr = "ordering"
|
||||
// Orientation http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:orientation
|
||||
Orientation Attr = "orientation"
|
||||
// OutputOrder http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:outputorder
|
||||
OutputOrder Attr = "outputorder"
|
||||
// Overlap http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:overlap
|
||||
Overlap Attr = "overlap"
|
||||
// OverlapScaling http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:overlap_scaling
|
||||
OverlapScaling Attr = "overlap_scaling"
|
||||
// OverlapShrink http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:overlap_shrink
|
||||
OverlapShrink Attr = "overlap_shrink"
|
||||
// Pack http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:pack
|
||||
Pack Attr = "pack"
|
||||
// PackMode http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:packmode
|
||||
PackMode Attr = "packmode"
|
||||
// Pad http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:pad
|
||||
Pad Attr = "pad"
|
||||
// Page http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:page
|
||||
Page Attr = "page"
|
||||
// PageDir http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:pagedir
|
||||
PageDir Attr = "pagedir"
|
||||
// PenColor http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:pencolor
|
||||
PenColor Attr = "pencolor"
|
||||
// PenWidth http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:penwidth
|
||||
PenWidth Attr = "penwidth"
|
||||
// Peripheries http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:peripheries
|
||||
Peripheries Attr = "peripheries"
|
||||
// Pin http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:peripheries
|
||||
Pin Attr = "pin"
|
||||
// Pos http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:pos
|
||||
Pos Attr = "pos"
|
||||
// QuadTree http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:quadtree
|
||||
QuadTree Attr = "quadtree"
|
||||
// Quantum http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:quantum
|
||||
Quantum Attr = "quantum"
|
||||
// Rank http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:rank
|
||||
Rank Attr = "rank"
|
||||
// RankDir http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:rankdir
|
||||
RankDir Attr = "rankdir"
|
||||
// RankSep http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:ranksep
|
||||
RankSep Attr = "ranksep"
|
||||
// Ratio http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:ratio
|
||||
Ratio Attr = "ratio"
|
||||
// Rects http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:rects
|
||||
Rects Attr = "rects"
|
||||
// Regular http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:regular
|
||||
Regular Attr = "regular"
|
||||
// ReMinCross http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:remincross
|
||||
ReMinCross Attr = "remincross"
|
||||
// RepulsiveForce http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:repulsiveforce
|
||||
RepulsiveForce Attr = "repulsiveforce"
|
||||
// Resolution http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:resolution
|
||||
Resolution Attr = "resolution"
|
||||
// Root http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:root
|
||||
Root Attr = "root"
|
||||
// Rotate http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:rotate
|
||||
Rotate Attr = "rotate"
|
||||
// Rotation http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:rotation
|
||||
Rotation Attr = "rotation"
|
||||
// SameHead http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:samehead
|
||||
SameHead Attr = "samehead"
|
||||
// SameTail http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:sametail
|
||||
SameTail Attr = "sametail"
|
||||
// SamplePoints http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:samplepoints
|
||||
SamplePoints Attr = "samplepoints"
|
||||
// Scale http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:scale
|
||||
Scale Attr = "scale"
|
||||
// SearchSize http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:searchsize
|
||||
SearchSize Attr = "searchsize"
|
||||
// Sep http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:sep
|
||||
Sep Attr = "sep"
|
||||
// Shape http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:shape
|
||||
Shape Attr = "shape"
|
||||
// ShapeFile http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:shapefile
|
||||
ShapeFile Attr = "shapefile"
|
||||
// ShowBoxes http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:showboxes
|
||||
ShowBoxes Attr = "showboxes"
|
||||
// Sides http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:sides
|
||||
Sides Attr = "sides"
|
||||
// Size http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:size
|
||||
Size Attr = "size"
|
||||
// Skew http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:skew
|
||||
Skew Attr = "skew"
|
||||
// Smoothing http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:smoothing
|
||||
Smoothing Attr = "smoothing"
|
||||
// SortV http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:sortv
|
||||
SortV Attr = "sortv"
|
||||
// Splines http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:splines
|
||||
Splines Attr = "splines"
|
||||
// Start http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:start
|
||||
Start Attr = "start"
|
||||
// Style http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:style
|
||||
Style Attr = "style"
|
||||
// StyleSheet http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:stylesheet
|
||||
StyleSheet Attr = "stylesheet"
|
||||
// TailURL http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tailURL
|
||||
TailURL Attr = "tailURL"
|
||||
// TailLP http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tail_lp
|
||||
TailLP Attr = "tail_lp"
|
||||
// TailClip http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tailclip
|
||||
TailClip Attr = "tailclip"
|
||||
// TailHREF http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tailhref
|
||||
TailHREF Attr = "tailhref"
|
||||
// TailLabel http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:taillabel
|
||||
TailLabel Attr = "taillabel"
|
||||
// TailPort http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tailport
|
||||
TailPort Attr = "tailport"
|
||||
// TailTarget http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tailtarget
|
||||
TailTarget Attr = "tailtarget"
|
||||
// TailTooltip http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tailtooltip
|
||||
TailTooltip Attr = "tailtooltip"
|
||||
// Target http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:target
|
||||
Target Attr = "target"
|
||||
// Tooltip http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tooltip
|
||||
Tooltip Attr = "tooltip"
|
||||
// TrueColor http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:tooltip
|
||||
TrueColor Attr = "truecolor"
|
||||
// Vertices http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:vertices
|
||||
Vertices Attr = "vertices"
|
||||
// ViewPort http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:viewport
|
||||
ViewPort Attr = "viewport"
|
||||
// VoroMargin http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:voro_margin
|
||||
VoroMargin Attr = "voro_margin"
|
||||
// Weight http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:weight
|
||||
Weight Attr = "weight"
|
||||
// Width http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:width
|
||||
Width Attr = "width"
|
||||
// XDotVersion http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:xdotversion
|
||||
XDotVersion Attr = "xdotversion"
|
||||
// XLabel http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:xlabel
|
||||
XLabel Attr = "xlabel"
|
||||
// XLP http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:xlp
|
||||
XLP Attr = "xlp"
|
||||
// Z http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:z
|
||||
Z Attr = "z"
|
||||
|
||||
// MinCross is not in the documentation, but found in the Ped_Lion_Share (lion_share.gv.txt) example
|
||||
MinCross Attr = "mincross"
|
||||
// SSize is not in the documentation, but found in the siblings.gv.txt example
|
||||
SSize Attr = "ssize"
|
||||
// Outline is not in the documentation, but found in the siblings.gv.txt example
|
||||
Outline Attr = "outline"
|
||||
// F is not in the documentation, but found in the transparency.gv.txt example
|
||||
F Attr = "f"
|
||||
)
|
||||
|
||||
var validAttrs = map[string]Attr{
|
||||
string(Damping): Damping,
|
||||
string(K): K,
|
||||
string(URL): URL,
|
||||
string(Background): Background,
|
||||
string(Area): Area,
|
||||
string(ArrowHead): ArrowHead,
|
||||
string(ArrowSize): ArrowSize,
|
||||
string(ArrowTail): ArrowTail,
|
||||
string(BB): BB,
|
||||
string(BgColor): BgColor,
|
||||
string(Center): Center,
|
||||
string(Charset): Charset,
|
||||
string(ClusterRank): ClusterRank,
|
||||
string(Color): Color,
|
||||
string(ColorScheme): ColorScheme,
|
||||
string(Comment): Comment,
|
||||
string(Compound): Compound,
|
||||
string(Concentrate): Concentrate,
|
||||
string(Constraint): Constraint,
|
||||
string(Decorate): Decorate,
|
||||
string(DefaultDist): DefaultDist,
|
||||
string(Dim): Dim,
|
||||
string(Dimen): Dimen,
|
||||
string(Dir): Dir,
|
||||
string(DirEdgeConstraints): DirEdgeConstraints,
|
||||
string(Distortion): Distortion,
|
||||
string(DPI): DPI,
|
||||
string(EdgeURL): EdgeURL,
|
||||
string(EdgeHREF): EdgeHREF,
|
||||
string(EdgeTarget): EdgeTarget,
|
||||
string(EdgeTooltip): EdgeTooltip,
|
||||
string(Epsilon): Epsilon,
|
||||
string(ESep): ESep,
|
||||
string(FillColor): FillColor,
|
||||
string(FixedSize): FixedSize,
|
||||
string(FontColor): FontColor,
|
||||
string(FontName): FontName,
|
||||
string(FontNames): FontNames,
|
||||
string(FontPath): FontPath,
|
||||
string(FontSize): FontSize,
|
||||
string(ForceLabels): ForceLabels,
|
||||
string(GradientAngle): GradientAngle,
|
||||
string(Group): Group,
|
||||
string(HeadURL): HeadURL,
|
||||
string(HeadLP): HeadLP,
|
||||
string(HeadClip): HeadClip,
|
||||
string(HeadHREF): HeadHREF,
|
||||
string(HeadLabel): HeadLabel,
|
||||
string(HeadPort): HeadPort,
|
||||
string(HeadTarget): HeadTarget,
|
||||
string(HeadTooltip): HeadTooltip,
|
||||
string(Height): Height,
|
||||
string(HREF): HREF,
|
||||
string(ID): ID,
|
||||
string(Image): Image,
|
||||
string(ImagePath): ImagePath,
|
||||
string(ImageScale): ImageScale,
|
||||
string(InputScale): InputScale,
|
||||
string(Label): Label,
|
||||
string(LabelURL): LabelURL,
|
||||
string(LabelScheme): LabelScheme,
|
||||
string(LabelAngle): LabelAngle,
|
||||
string(LabelDistance): LabelDistance,
|
||||
string(LabelFloat): LabelFloat,
|
||||
string(LabelFontColor): LabelFontColor,
|
||||
string(LabelFontName): LabelFontName,
|
||||
string(LabelFontSize): LabelFontSize,
|
||||
string(LabelHREF): LabelHREF,
|
||||
string(LabelJust): LabelJust,
|
||||
string(LabelLOC): LabelLOC,
|
||||
string(LabelTarget): LabelTarget,
|
||||
string(LabelTooltip): LabelTooltip,
|
||||
string(Landscape): Landscape,
|
||||
string(Layer): Layer,
|
||||
string(LayerListSep): LayerListSep,
|
||||
string(Layers): Layers,
|
||||
string(LayerSelect): LayerSelect,
|
||||
string(LayerSep): LayerSep,
|
||||
string(Layout): Layout,
|
||||
string(Len): Len,
|
||||
string(Levels): Levels,
|
||||
string(LevelsGap): LevelsGap,
|
||||
string(LHead): LHead,
|
||||
string(LHeight): LHeight,
|
||||
string(LP): LP,
|
||||
string(LTail): LTail,
|
||||
string(LWidth): LWidth,
|
||||
string(Margin): Margin,
|
||||
string(MaxIter): MaxIter,
|
||||
string(MCLimit): MCLimit,
|
||||
string(MinDist): MinDist,
|
||||
string(MinLen): MinLen,
|
||||
string(Mode): Mode,
|
||||
string(Model): Model,
|
||||
string(Mosek): Mosek,
|
||||
string(NewRank): NewRank,
|
||||
string(NodeSep): NodeSep,
|
||||
string(NoJustify): NoJustify,
|
||||
string(Normalize): Normalize,
|
||||
string(NoTranslate): NoTranslate,
|
||||
string(NSLimit): NSLimit,
|
||||
string(NSLimit1): NSLimit1,
|
||||
string(Ordering): Ordering,
|
||||
string(Orientation): Orientation,
|
||||
string(OutputOrder): OutputOrder,
|
||||
string(Overlap): Overlap,
|
||||
string(OverlapScaling): OverlapScaling,
|
||||
string(OverlapShrink): OverlapShrink,
|
||||
string(Pack): Pack,
|
||||
string(PackMode): PackMode,
|
||||
string(Pad): Pad,
|
||||
string(Page): Page,
|
||||
string(PageDir): PageDir,
|
||||
string(PenColor): PenColor,
|
||||
string(PenWidth): PenWidth,
|
||||
string(Peripheries): Peripheries,
|
||||
string(Pin): Pin,
|
||||
string(Pos): Pos,
|
||||
string(QuadTree): QuadTree,
|
||||
string(Quantum): Quantum,
|
||||
string(Rank): Rank,
|
||||
string(RankDir): RankDir,
|
||||
string(RankSep): RankSep,
|
||||
string(Ratio): Ratio,
|
||||
string(Rects): Rects,
|
||||
string(Regular): Regular,
|
||||
string(ReMinCross): ReMinCross,
|
||||
string(RepulsiveForce): RepulsiveForce,
|
||||
string(Resolution): Resolution,
|
||||
string(Root): Root,
|
||||
string(Rotate): Rotate,
|
||||
string(Rotation): Rotation,
|
||||
string(SameHead): SameHead,
|
||||
string(SameTail): SameTail,
|
||||
string(SamplePoints): SamplePoints,
|
||||
string(Scale): Scale,
|
||||
string(SearchSize): SearchSize,
|
||||
string(Sep): Sep,
|
||||
string(Shape): Shape,
|
||||
string(ShapeFile): ShapeFile,
|
||||
string(ShowBoxes): ShowBoxes,
|
||||
string(Sides): Sides,
|
||||
string(Size): Size,
|
||||
string(Skew): Skew,
|
||||
string(Smoothing): Smoothing,
|
||||
string(SortV): SortV,
|
||||
string(Splines): Splines,
|
||||
string(Start): Start,
|
||||
string(Style): Style,
|
||||
string(StyleSheet): StyleSheet,
|
||||
string(TailURL): TailURL,
|
||||
string(TailLP): TailLP,
|
||||
string(TailClip): TailClip,
|
||||
string(TailHREF): TailHREF,
|
||||
string(TailLabel): TailLabel,
|
||||
string(TailPort): TailPort,
|
||||
string(TailTarget): TailTarget,
|
||||
string(TailTooltip): TailTooltip,
|
||||
string(Target): Target,
|
||||
string(Tooltip): Tooltip,
|
||||
string(TrueColor): TrueColor,
|
||||
string(Vertices): Vertices,
|
||||
string(ViewPort): ViewPort,
|
||||
string(VoroMargin): VoroMargin,
|
||||
string(Weight): Weight,
|
||||
string(Width): Width,
|
||||
string(XDotVersion): XDotVersion,
|
||||
string(XLabel): XLabel,
|
||||
string(XLP): XLP,
|
||||
string(Z): Z,
|
||||
|
||||
string(MinCross): MinCross,
|
||||
string(SSize): SSize,
|
||||
string(Outline): Outline,
|
||||
string(F): F,
|
||||
}
|
99
vendor/github.com/awalterschulze/gographviz/attrs.go
generated
vendored
Normal file
99
vendor/github.com/awalterschulze/gographviz/attrs.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Attrs represents attributes for an Edge, Node or Graph.
|
||||
type Attrs map[Attr]string
|
||||
|
||||
// NewAttrs creates an empty Attributes type.
|
||||
func NewAttrs(m map[string]string) (Attrs, error) {
|
||||
as := make(Attrs)
|
||||
for k, v := range m {
|
||||
if err := as.Add(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return as, nil
|
||||
}
|
||||
|
||||
// Add adds an attribute name and value.
|
||||
func (attrs Attrs) Add(field string, value string) error {
|
||||
a, err := NewAttr(field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrs.add(a, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (attrs Attrs) add(field Attr, value string) {
|
||||
attrs[field] = value
|
||||
}
|
||||
|
||||
// Extend adds the attributes into attrs Attrs type overwriting duplicates.
|
||||
func (attrs Attrs) Extend(more Attrs) {
|
||||
for key, value := range more {
|
||||
attrs.add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Ammend only adds the missing attributes to attrs Attrs type.
|
||||
func (attrs Attrs) Ammend(more Attrs) {
|
||||
for key, value := range more {
|
||||
if _, ok := attrs[key]; !ok {
|
||||
attrs.add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (attrs Attrs) toMap() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for k, v := range attrs {
|
||||
m[string(k)] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
type attrList []Attr
|
||||
|
||||
func (attrs attrList) Len() int { return len(attrs) }
|
||||
func (attrs attrList) Less(i, j int) bool {
|
||||
return attrs[i] < attrs[j]
|
||||
}
|
||||
func (attrs attrList) Swap(i, j int) {
|
||||
attrs[i], attrs[j] = attrs[j], attrs[i]
|
||||
}
|
||||
|
||||
func (attrs Attrs) sortedNames() []Attr {
|
||||
keys := make(attrList, 0)
|
||||
for key := range attrs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Sort(keys)
|
||||
return []Attr(keys)
|
||||
}
|
||||
|
||||
// Copy returns a copy of the attributes map
|
||||
func (attrs Attrs) Copy() Attrs {
|
||||
mm := make(Attrs)
|
||||
for k, v := range attrs {
|
||||
mm[k] = v
|
||||
}
|
||||
return mm
|
||||
}
|
101
vendor/github.com/awalterschulze/gographviz/catch.go
generated
vendored
Normal file
101
vendor/github.com/awalterschulze/gographviz/catch.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
//Copyright 2017 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type errInterface interface {
|
||||
SetStrict(strict bool)
|
||||
SetDir(directed bool)
|
||||
SetName(name string)
|
||||
AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string)
|
||||
AddEdge(src, dst string, directed bool, attrs map[string]string)
|
||||
AddNode(parentGraph string, name string, attrs map[string]string)
|
||||
AddAttr(parentGraph string, field, value string)
|
||||
AddSubGraph(parentGraph string, name string, attrs map[string]string)
|
||||
String() string
|
||||
getError() error
|
||||
}
|
||||
|
||||
func newErrCatcher(g Interface) errInterface {
|
||||
return &errCatcher{g, nil}
|
||||
}
|
||||
|
||||
type errCatcher struct {
|
||||
Interface
|
||||
errs []error
|
||||
}
|
||||
|
||||
func (e *errCatcher) SetStrict(strict bool) {
|
||||
if err := e.Interface.SetStrict(strict); err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *errCatcher) SetDir(directed bool) {
|
||||
if err := e.Interface.SetDir(directed); err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *errCatcher) SetName(name string) {
|
||||
if err := e.Interface.SetName(name); err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *errCatcher) AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) {
|
||||
if err := e.Interface.AddPortEdge(src, srcPort, dst, dstPort, directed, attrs); err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *errCatcher) AddEdge(src, dst string, directed bool, attrs map[string]string) {
|
||||
if err := e.Interface.AddEdge(src, dst, directed, attrs); err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *errCatcher) AddAttr(parentGraph string, field, value string) {
|
||||
if err := e.Interface.AddAttr(parentGraph, field, value); err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *errCatcher) AddSubGraph(parentGraph string, name string, attrs map[string]string) {
|
||||
if err := e.Interface.AddSubGraph(parentGraph, name, attrs); err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *errCatcher) AddNode(parentGraph string, name string, attrs map[string]string) {
|
||||
if err := e.Interface.AddNode(parentGraph, name, attrs); err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *errCatcher) getError() error {
|
||||
if len(e.errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
ss := make([]string, len(e.errs))
|
||||
for i, err := range e.errs {
|
||||
ss[i] = err.Error()
|
||||
}
|
||||
return fmt.Errorf("errors: [%s]", strings.Join(ss, ","))
|
||||
}
|
292
vendor/github.com/awalterschulze/gographviz/dot.bnf
generated
vendored
Normal file
292
vendor/github.com/awalterschulze/gographviz/dot.bnf
generated
vendored
Normal file
@ -0,0 +1,292 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
//This bnf has been derived from https://graphviz.gitlab.io/_pages/doc/info/lang.html
|
||||
//The rules have been copied and are shown in the comments, with their derived bnf rules below.
|
||||
|
||||
// ### [ Tokens ] ##############################################################
|
||||
|
||||
// The keywords node, edge, graph, digraph, subgraph, and strict are case-
|
||||
// independent.
|
||||
|
||||
node
|
||||
: 'n' 'o' 'd' 'e'
|
||||
| 'N' 'o' 'd' 'e'
|
||||
| 'N' 'O' 'D' 'E'
|
||||
;
|
||||
|
||||
edge
|
||||
: 'e' 'd' 'g' 'e'
|
||||
| 'E' 'd' 'g' 'e'
|
||||
| 'E' 'D' 'G' 'E'
|
||||
;
|
||||
|
||||
// TODO: Rename graphx to graph once gocc#20 is fixed [1].
|
||||
//
|
||||
// [1]: https://github.com/goccmack/gocc/issues/20
|
||||
|
||||
graphx
|
||||
: 'g' 'r' 'a' 'p' 'h'
|
||||
| 'G' 'r' 'a' 'p' 'h'
|
||||
| 'G' 'R' 'A' 'P' 'H'
|
||||
;
|
||||
|
||||
digraph
|
||||
: 'd' 'i' 'g' 'r' 'a' 'p' 'h'
|
||||
| 'D' 'i' 'g' 'r' 'a' 'p' 'h'
|
||||
| 'd' 'i' 'G' 'r' 'a' 'p' 'h'
|
||||
| 'D' 'i' 'G' 'r' 'a' 'p' 'h'
|
||||
| 'D' 'I' 'G' 'R' 'A' 'P' 'H'
|
||||
;
|
||||
|
||||
subgraph
|
||||
: 's' 'u' 'b' 'g' 'r' 'a' 'p' 'h'
|
||||
| 'S' 'u' 'b' 'g' 'r' 'a' 'p' 'h'
|
||||
| 's' 'u' 'b' 'G' 'r' 'a' 'p' 'h'
|
||||
| 'S' 'u' 'b' 'G' 'r' 'a' 'p' 'h'
|
||||
| 'S' 'U' 'B' 'G' 'R' 'A' 'P' 'H'
|
||||
;
|
||||
|
||||
strict
|
||||
: 's' 't' 'r' 'i' 'c' 't'
|
||||
| 'S' 't' 'r' 'i' 'c' 't'
|
||||
| 'S' 'T' 'R' 'I' 'C' 'T'
|
||||
;
|
||||
|
||||
// An arbitrary ASCII character except null (0x00), double quote (0x22) and
|
||||
// backslash (0x5C).
|
||||
_ascii_char
|
||||
// skip null (0x00)
|
||||
: '\x01' - '\x21'
|
||||
// skip double quote (0x22)
|
||||
| '\x23' - '\x5B'
|
||||
// skip backslash (0x5C)
|
||||
| '\x5D' - '\x7F'
|
||||
;
|
||||
|
||||
_ascii_letter
|
||||
: 'a' - 'z'
|
||||
| 'A' - 'Z'
|
||||
;
|
||||
|
||||
_ascii_digit : '0' - '9' ;
|
||||
|
||||
_unicode_char
|
||||
: _ascii_char
|
||||
| _unicode_byte
|
||||
;
|
||||
|
||||
_unicode_byte
|
||||
: '\u0080' - '\uFFFC'
|
||||
// skip invalid code point (\uFFFD)
|
||||
| '\uFFFE' - '\U0010FFFF'
|
||||
;
|
||||
|
||||
_letter : _ascii_letter | _unicode_byte | '_' ;
|
||||
_decimal_digit : _ascii_digit ;
|
||||
_decimals : _decimal_digit { _decimal_digit } ;
|
||||
|
||||
// An ID is one of the following:
|
||||
//
|
||||
// 1) Any string of alphabetic ([a-zA-Z\200-\377]) characters, underscores
|
||||
// ('_') or digits ([0-9]), not beginning with a digit;
|
||||
//
|
||||
// 2) a numeral [-]?(.[0-9]+ | [0-9]+(.[0-9]*)? );
|
||||
//
|
||||
// 3) any double-quoted string ("...") possibly containing escaped quotes
|
||||
// (\");
|
||||
//
|
||||
// 4) an HTML string (<...>).
|
||||
|
||||
id
|
||||
: _letter { _letter | _decimal_digit }
|
||||
| _int_lit
|
||||
| _string_lit
|
||||
| _html_lit
|
||||
;
|
||||
|
||||
_int_lit
|
||||
: [ '-' ] '.' _decimals
|
||||
| [ '-' ] _decimals [ '.' { _decimal_digit } ]
|
||||
;
|
||||
|
||||
// In quoted strings in DOT, the only escaped character is double-quote (").
|
||||
// That is, in quoted strings, the dyad \" is converted to "; all other
|
||||
// characters are left unchanged. In particular, \\ remains \\.
|
||||
|
||||
_escaped_char : '\\' ( _unicode_char | '"' | '\\' ) ;
|
||||
_char : _unicode_char | _escaped_char ;
|
||||
_string_lit : '"' { _char } '"' ;
|
||||
|
||||
// An arbitrary HTML character except null (0x00), left angle bracket (0x3C) and
|
||||
// right angle bracket (0x3E).
|
||||
_html_char
|
||||
// skip null (0x00)
|
||||
: '\x01' - '\x3B'
|
||||
// skip left angle bracket (0x3C)
|
||||
| '\x3D'
|
||||
// skip right angle bracket (0x3E)
|
||||
| '\x3F' - '\xFF'
|
||||
;
|
||||
|
||||
_html_chars : { _html_char } ;
|
||||
_html_tag : '<' _html_chars '>' ;
|
||||
_html_lit : '<' { _html_chars | _html_tag } '>' ;
|
||||
|
||||
// The language supports C++-style comments: /* */ and //. In addition, a line
|
||||
// beginning with a '#' character is considered a line output from a C
|
||||
// preprocessor (e.g., # 34 to indicate line 34 ) and discarded.
|
||||
|
||||
_line_comment
|
||||
: '/' '/' { . } '\n'
|
||||
| '#' { . } '\n'
|
||||
;
|
||||
|
||||
_block_comment : '/' '*' { . | '*' } '*' '/' ;
|
||||
!comment : _line_comment | _block_comment ;
|
||||
|
||||
!whitespace : ' ' | '\t' | '\r' | '\n' ;
|
||||
|
||||
// ### [ Syntax ] ##############################################################
|
||||
|
||||
<< import "github.com/awalterschulze/gographviz/ast" >>
|
||||
|
||||
//graph : [ strict ] (graph | digraph) [ ID ] '{' stmt_list '}'
|
||||
DotGraph
|
||||
: graphx "{" "}" << ast.NewGraph(ast.GRAPH, ast.FALSE, nil, nil) >>
|
||||
| strict graphx "{" "}" << ast.NewGraph(ast.GRAPH, ast.TRUE, nil, nil) >>
|
||||
| graphx Id "{" "}" << ast.NewGraph(ast.GRAPH, ast.FALSE, $1, nil) >>
|
||||
| strict graphx Id "{" "}" << ast.NewGraph(ast.GRAPH, ast.TRUE, $2, nil) >>
|
||||
| graphx "{" StmtList "}" << ast.NewGraph(ast.GRAPH, ast.FALSE, nil, $2) >>
|
||||
| graphx Id "{" StmtList "}" << ast.NewGraph(ast.GRAPH, ast.FALSE, $1, $3) >>
|
||||
| strict graphx "{" StmtList "}" << ast.NewGraph(ast.GRAPH, ast.TRUE, nil, $3) >>
|
||||
| strict graphx Id "{" StmtList "}" << ast.NewGraph(ast.GRAPH, ast.TRUE, $2, $4) >>
|
||||
| digraph "{" "}" << ast.NewGraph(ast.DIGRAPH, ast.FALSE, nil, nil) >>
|
||||
| strict digraph "{" "}" << ast.NewGraph(ast.DIGRAPH, ast.TRUE, nil, nil) >>
|
||||
| digraph Id "{" "}" << ast.NewGraph(ast.DIGRAPH, ast.FALSE, $1, nil) >>
|
||||
| strict digraph Id "{" "}" << ast.NewGraph(ast.DIGRAPH, ast.TRUE, $2, nil) >>
|
||||
| digraph "{" StmtList "}" << ast.NewGraph(ast.DIGRAPH, ast.FALSE, nil, $2) >>
|
||||
| digraph Id "{" StmtList "}" << ast.NewGraph(ast.DIGRAPH, ast.FALSE, $1, $3) >>
|
||||
| strict digraph "{" StmtList "}" << ast.NewGraph(ast.DIGRAPH, ast.TRUE, nil, $3) >>
|
||||
| strict digraph Id "{" StmtList "}" << ast.NewGraph(ast.DIGRAPH, ast.TRUE, $2, $4) >>
|
||||
;
|
||||
|
||||
//stmt_list : [ stmt [ ';' ] [ stmt_list ] ]
|
||||
StmtList
|
||||
: Stmt1 << ast.NewStmtList($0) >>
|
||||
| StmtList Stmt1 << ast.AppendStmtList($0, $1) >>
|
||||
;
|
||||
|
||||
Stmt1
|
||||
: Stmt << $0, nil >>
|
||||
| Stmt ";" << $0, nil >>
|
||||
;
|
||||
|
||||
//stmt : node_stmt | edge_stmt | attr_stmt | (ID '=' ID) | subgraph
|
||||
Stmt
|
||||
: Id "=" Id << ast.NewAttr($0, $2) >>
|
||||
| NodeStmt << $0, nil >>
|
||||
| EdgeStmt << $0, nil >>
|
||||
| AttrStmt << $0, nil >>
|
||||
| SubGraphStmt << $0, nil >>
|
||||
;
|
||||
|
||||
//attr_stmt : (graph | node | edge) attr_list
|
||||
AttrStmt
|
||||
: graphx AttrList << ast.NewGraphAttrs($1) >>
|
||||
| node AttrList << ast.NewNodeAttrs($1) >>
|
||||
| edge AttrList << ast.NewEdgeAttrs($1) >>
|
||||
;
|
||||
|
||||
//attr_list : '[' [ a_list ] ']' [ attr_list ]
|
||||
AttrList
|
||||
: "[" "]" << ast.NewAttrList(nil) >>
|
||||
| "[" AList "]" << ast.NewAttrList($1) >>
|
||||
| AttrList "[" "]" << ast.AppendAttrList($0, nil) >>
|
||||
| AttrList "[" AList "]" << ast.AppendAttrList($0, $2) >>
|
||||
;
|
||||
|
||||
//a_list : ID [ '=' ID ] [ ',' ] [ a_list ]
|
||||
AList
|
||||
: Attr << ast.NewAList($0) >>
|
||||
| AList Attr << ast.AppendAList($0, $1) >>
|
||||
| AList "," Attr << ast.AppendAList($0, $2) >>
|
||||
;
|
||||
|
||||
//An a_list clause of the form ID is equivalent to ID=true.
|
||||
Attr
|
||||
: Id << ast.NewAttr($0, nil) >>
|
||||
| Id "=" Id << ast.NewAttr($0, $2) >>
|
||||
;
|
||||
|
||||
//edge_stmt : (node_id | subgraph) edgeRHS [ attr_list ]
|
||||
EdgeStmt
|
||||
: NodeId EdgeRHS << ast.NewEdgeStmt($0, $1, nil) >>
|
||||
| NodeId EdgeRHS AttrList << ast.NewEdgeStmt($0, $1, $2) >>
|
||||
| SubGraphStmt EdgeRHS << ast.NewEdgeStmt($0, $1, nil) >>
|
||||
| SubGraphStmt EdgeRHS AttrList << ast.NewEdgeStmt($0, $1, $2) >>
|
||||
;
|
||||
|
||||
//edgeRHS : edgeop (node_id | subgraph) [ edgeRHS ]
|
||||
EdgeRHS
|
||||
: EdgeOp NodeId << ast.NewEdgeRHS($0, $1) >>
|
||||
| EdgeOp SubGraphStmt << ast.NewEdgeRHS($0, $1) >>
|
||||
| EdgeRHS EdgeOp NodeId << ast.AppendEdgeRHS($0, $1, $2) >>
|
||||
| EdgeRHS EdgeOp SubGraphStmt << ast.AppendEdgeRHS($0, $1, $2) >>
|
||||
;
|
||||
|
||||
//node_stmt : node_id [ attr_list ]
|
||||
NodeStmt
|
||||
: NodeId << ast.NewNodeStmt($0, nil) >>
|
||||
| NodeId AttrList << ast.NewNodeStmt($0, $1) >>
|
||||
;
|
||||
|
||||
//node_id : ID [ port ]
|
||||
NodeId
|
||||
: Id << ast.NewNodeID($0, nil) >>
|
||||
| Id Port << ast.NewNodeID($0, $1) >>
|
||||
;
|
||||
|
||||
//compass_pt : (n | ne | e | se | s | sw | w | nw | c | _)
|
||||
//Note also that the allowed compass point values are not keywords,
|
||||
//so these strings can be used elsewhere as ordinary identifiers and,
|
||||
//conversely, the parser will actually accept any identifier.
|
||||
//port : ':' ID [ ':' compass_pt ]
|
||||
// | ':' compass_pt
|
||||
Port
|
||||
: ":" Id << ast.NewPort($1, nil), nil >>
|
||||
| ":" Id ":" Id << ast.NewPort($1, $3), nil >>
|
||||
;
|
||||
|
||||
//TODO: Semicolons aid readability but are not required except in the rare case that a named subgraph with no body immediately preceeds an anonymous subgraph,
|
||||
//since the precedence rules cause this sequence to be parsed as a subgraph with a heading and a body. Also, any amount of whitespace may be inserted between terminals.
|
||||
|
||||
//subgraph : [ subgraph [ ID ] ] '{' stmt_list '}'
|
||||
SubGraphStmt
|
||||
: "{" StmtList "}" << ast.NewSubGraph(nil, $1) >>
|
||||
| subgraph "{" StmtList "}" << ast.NewSubGraph(nil, $2) >>
|
||||
| subgraph Id "{" StmtList "}" << ast.NewSubGraph($1, $3) >>
|
||||
| subgraph "{" "}" << ast.NewSubGraph(nil, nil) >>
|
||||
| subgraph Id "{" "}" << ast.NewSubGraph($1, nil) >>
|
||||
;
|
||||
|
||||
//An edgeop is -> in directed graphs and -- in undirected graphs.
|
||||
EdgeOp
|
||||
: "->" << ast.DIRECTED, nil >>
|
||||
| "--" << ast.UNDIRECTED, nil >>
|
||||
;
|
||||
|
||||
Id
|
||||
: id << ast.NewID($0) >>
|
||||
;
|
119
vendor/github.com/awalterschulze/gographviz/edges.go
generated
vendored
Normal file
119
vendor/github.com/awalterschulze/gographviz/edges.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Edge represents an Edge.
|
||||
type Edge struct {
|
||||
Src string
|
||||
SrcPort string
|
||||
Dst string
|
||||
DstPort string
|
||||
Dir bool
|
||||
Attrs Attrs
|
||||
}
|
||||
|
||||
// Edges represents a set of Edges.
|
||||
type Edges struct {
|
||||
SrcToDsts map[string]map[string][]*Edge
|
||||
DstToSrcs map[string]map[string][]*Edge
|
||||
Edges []*Edge
|
||||
}
|
||||
|
||||
// NewEdges creates a blank set of Edges.
|
||||
func NewEdges() *Edges {
|
||||
return &Edges{make(map[string]map[string][]*Edge), make(map[string]map[string][]*Edge), make([]*Edge, 0)}
|
||||
}
|
||||
|
||||
// Add adds an Edge to the set of Edges.
|
||||
func (edges *Edges) Add(edge *Edge) {
|
||||
if _, ok := edges.SrcToDsts[edge.Src]; !ok {
|
||||
edges.SrcToDsts[edge.Src] = make(map[string][]*Edge)
|
||||
}
|
||||
if _, ok := edges.SrcToDsts[edge.Src][edge.Dst]; !ok {
|
||||
edges.SrcToDsts[edge.Src][edge.Dst] = make([]*Edge, 0)
|
||||
}
|
||||
edges.SrcToDsts[edge.Src][edge.Dst] = append(edges.SrcToDsts[edge.Src][edge.Dst], edge)
|
||||
|
||||
if _, ok := edges.DstToSrcs[edge.Dst]; !ok {
|
||||
edges.DstToSrcs[edge.Dst] = make(map[string][]*Edge)
|
||||
}
|
||||
if _, ok := edges.DstToSrcs[edge.Dst][edge.Src]; !ok {
|
||||
edges.DstToSrcs[edge.Dst][edge.Src] = make([]*Edge, 0)
|
||||
}
|
||||
edges.DstToSrcs[edge.Dst][edge.Src] = append(edges.DstToSrcs[edge.Dst][edge.Src], edge)
|
||||
|
||||
edges.Edges = append(edges.Edges, edge)
|
||||
}
|
||||
|
||||
// Sorted returns a sorted list of Edges.
|
||||
func (edges Edges) Sorted() []*Edge {
|
||||
es := make(edgeSorter, len(edges.Edges))
|
||||
copy(es, edges.Edges)
|
||||
sort.Sort(es)
|
||||
return es
|
||||
}
|
||||
|
||||
type edgeSorter []*Edge
|
||||
|
||||
func (es edgeSorter) Len() int { return len(es) }
|
||||
func (es edgeSorter) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
|
||||
func (es edgeSorter) Less(i, j int) bool {
|
||||
if es[i].Src < es[j].Src {
|
||||
return true
|
||||
} else if es[i].Src > es[j].Src {
|
||||
return false
|
||||
}
|
||||
|
||||
if es[i].Dst < es[j].Dst {
|
||||
return true
|
||||
} else if es[i].Dst > es[j].Dst {
|
||||
return false
|
||||
}
|
||||
|
||||
if es[i].SrcPort < es[j].SrcPort {
|
||||
return true
|
||||
} else if es[i].SrcPort > es[j].SrcPort {
|
||||
return false
|
||||
}
|
||||
|
||||
if es[i].DstPort < es[j].DstPort {
|
||||
return true
|
||||
} else if es[i].DstPort > es[j].DstPort {
|
||||
return false
|
||||
}
|
||||
|
||||
if es[i].Dir != es[j].Dir {
|
||||
return es[i].Dir
|
||||
}
|
||||
|
||||
attrs := es[i].Attrs.Copy()
|
||||
for k, v := range es[j].Attrs {
|
||||
attrs[k] = v
|
||||
}
|
||||
|
||||
for _, k := range attrs.sortedNames() {
|
||||
if es[i].Attrs[k] < es[j].Attrs[k] {
|
||||
return true
|
||||
} else if es[i].Attrs[k] > es[j].Attrs[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
195
vendor/github.com/awalterschulze/gographviz/escape.go
generated
vendored
Normal file
195
vendor/github.com/awalterschulze/gographviz/escape.go
generated
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Escape is just a Graph that escapes some strings when required.
|
||||
type Escape struct {
|
||||
*Graph
|
||||
}
|
||||
|
||||
// NewEscape returns a graph which will try to escape some strings when required
|
||||
func NewEscape() *Escape {
|
||||
return &Escape{NewGraph()}
|
||||
}
|
||||
|
||||
func isHTML(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
ss := strings.TrimSpace(s)
|
||||
if ss[0] != '<' {
|
||||
return false
|
||||
}
|
||||
count := 0
|
||||
for _, c := range ss {
|
||||
if c == '<' {
|
||||
count++
|
||||
}
|
||||
if c == '>' {
|
||||
count--
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isLetter(ch rune) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' ||
|
||||
ch >= 0x80 && unicode.IsLetter(ch) && ch != 'ε'
|
||||
}
|
||||
|
||||
func isID(s string) bool {
|
||||
i := 0
|
||||
pos := false
|
||||
for _, c := range s {
|
||||
if i == 0 {
|
||||
if !isLetter(c) {
|
||||
return false
|
||||
}
|
||||
pos = true
|
||||
}
|
||||
if unicode.IsSpace(c) {
|
||||
return false
|
||||
}
|
||||
if c == '-' {
|
||||
return false
|
||||
}
|
||||
if c == '/' {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
func isDigit(ch rune) bool {
|
||||
return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
|
||||
}
|
||||
|
||||
func isNumber(s string) bool {
|
||||
state := 0
|
||||
for _, c := range s {
|
||||
if state == 0 {
|
||||
if isDigit(c) || c == '.' {
|
||||
state = 2
|
||||
} else if c == '-' {
|
||||
state = 1
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else if state == 1 {
|
||||
if isDigit(c) || c == '.' {
|
||||
state = 2
|
||||
}
|
||||
} else if c != '.' && !isDigit(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return (state == 2)
|
||||
}
|
||||
|
||||
func isStringLit(s string) bool {
|
||||
if !strings.HasPrefix(s, `"`) || !strings.HasSuffix(s, `"`) {
|
||||
return false
|
||||
}
|
||||
var prev rune
|
||||
for _, r := range s[1 : len(s)-1] {
|
||||
if r == '"' && prev != '\\' {
|
||||
return false
|
||||
}
|
||||
prev = r
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func esc(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
if isHTML(s) {
|
||||
return s
|
||||
}
|
||||
ss := strings.TrimSpace(s)
|
||||
if ss[0] == '<' {
|
||||
return fmt.Sprintf("\"%s\"", strings.Replace(s, "\"", "\\\"", -1))
|
||||
}
|
||||
if isID(s) {
|
||||
return s
|
||||
}
|
||||
if isNumber(s) {
|
||||
return s
|
||||
}
|
||||
if isStringLit(s) {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("\"%s\"", template.HTMLEscapeString(s))
|
||||
}
|
||||
|
||||
func escAttrs(attrs map[string]string) map[string]string {
|
||||
newAttrs := make(map[string]string)
|
||||
for k, v := range attrs {
|
||||
newAttrs[esc(k)] = esc(v)
|
||||
}
|
||||
return newAttrs
|
||||
}
|
||||
|
||||
// SetName sets the graph name and escapes it, if needed.
|
||||
func (escape *Escape) SetName(name string) error {
|
||||
return escape.Graph.SetName(esc(name))
|
||||
}
|
||||
|
||||
// AddPortEdge adds an edge with ports and escapes the src, dst and attrs, if needed.
|
||||
func (escape *Escape) AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) error {
|
||||
return escape.Graph.AddPortEdge(esc(src), srcPort, esc(dst), dstPort, directed, escAttrs(attrs))
|
||||
}
|
||||
|
||||
// AddEdge adds an edge and escapes the src, dst and attrs, if needed.
|
||||
func (escape *Escape) AddEdge(src, dst string, directed bool, attrs map[string]string) error {
|
||||
return escape.AddPortEdge(src, "", dst, "", directed, attrs)
|
||||
}
|
||||
|
||||
// AddNode adds a node and escapes the parentGraph, name and attrs, if needed.
|
||||
func (escape *Escape) AddNode(parentGraph string, name string, attrs map[string]string) error {
|
||||
return escape.Graph.AddNode(esc(parentGraph), esc(name), escAttrs(attrs))
|
||||
}
|
||||
|
||||
// AddAttr adds an attribute and escapes the parentGraph, field and value, if needed.
|
||||
func (escape *Escape) AddAttr(parentGraph string, field, value string) error {
|
||||
return escape.Graph.AddAttr(esc(parentGraph), esc(field), esc(value))
|
||||
}
|
||||
|
||||
// AddSubGraph adds a subgraph and escapes the parentGraph, name and attrs, if needed.
|
||||
func (escape *Escape) AddSubGraph(parentGraph string, name string, attrs map[string]string) error {
|
||||
return escape.Graph.AddSubGraph(esc(parentGraph), esc(name), escAttrs(attrs))
|
||||
}
|
||||
|
||||
// IsNode returns whether the, escaped if needed, name is a node in the graph.
|
||||
func (escape *Escape) IsNode(name string) bool {
|
||||
return escape.Graph.IsNode(esc(name))
|
||||
}
|
||||
|
||||
// IsSubGraph returns whether the, escaped if needed, name is a subgraph in the grahp.
|
||||
func (escape *Escape) IsSubGraph(name string) bool {
|
||||
return escape.Graph.IsSubGraph(esc(name))
|
||||
}
|
58
vendor/github.com/awalterschulze/gographviz/gographviz.go
generated
vendored
Normal file
58
vendor/github.com/awalterschulze/gographviz/gographviz.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
//Package gographviz provides parsing for the DOT grammar into
|
||||
//an abstract syntax tree representing a graph,
|
||||
//analysis of the abstract syntax tree into a more usable structure,
|
||||
//and writing back of this structure into the DOT format.
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"github.com/awalterschulze/gographviz/ast"
|
||||
"github.com/awalterschulze/gographviz/internal/parser"
|
||||
)
|
||||
|
||||
var _ Interface = NewGraph()
|
||||
|
||||
//Interface allows you to parse the graph into your own structure.
|
||||
type Interface interface {
|
||||
SetStrict(strict bool) error
|
||||
SetDir(directed bool) error
|
||||
SetName(name string) error
|
||||
AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) error
|
||||
AddEdge(src, dst string, directed bool, attrs map[string]string) error
|
||||
AddNode(parentGraph string, name string, attrs map[string]string) error
|
||||
AddAttr(parentGraph string, field, value string) error
|
||||
AddSubGraph(parentGraph string, name string, attrs map[string]string) error
|
||||
String() string
|
||||
}
|
||||
|
||||
//Parse parses the buffer into a abstract syntax tree representing the graph.
|
||||
func Parse(buf []byte) (*ast.Graph, error) {
|
||||
return parser.ParseBytes(buf)
|
||||
}
|
||||
|
||||
//ParseString parses the buffer into a abstract syntax tree representing the graph.
|
||||
func ParseString(buf string) (*ast.Graph, error) {
|
||||
return parser.ParseBytes([]byte(buf))
|
||||
}
|
||||
|
||||
//Read parses and creates a new Graph from the data.
|
||||
func Read(buf []byte) (*Graph, error) {
|
||||
st, err := Parse(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAnalysedGraph(st)
|
||||
}
|
197
vendor/github.com/awalterschulze/gographviz/graph.go
generated
vendored
Normal file
197
vendor/github.com/awalterschulze/gographviz/graph.go
generated
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Graph is the analysed representation of the Graph parsed from the DOT format.
|
||||
type Graph struct {
|
||||
Attrs Attrs
|
||||
Name string
|
||||
Directed bool
|
||||
Strict bool
|
||||
Nodes *Nodes
|
||||
Edges *Edges
|
||||
SubGraphs *SubGraphs
|
||||
Relations *Relations
|
||||
}
|
||||
|
||||
// NewGraph creates a new empty graph, ready to be populated.
|
||||
func NewGraph() *Graph {
|
||||
return &Graph{
|
||||
Attrs: make(Attrs),
|
||||
Name: "",
|
||||
Directed: false,
|
||||
Strict: false,
|
||||
Nodes: NewNodes(),
|
||||
Edges: NewEdges(),
|
||||
SubGraphs: NewSubGraphs(),
|
||||
Relations: NewRelations(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetStrict sets whether a graph is strict.
|
||||
// If the graph is strict then multiple edges are not allowed between the same pairs of nodes,
|
||||
// see dot man page.
|
||||
func (g *Graph) SetStrict(strict bool) error {
|
||||
g.Strict = strict
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDir sets whether the graph is directed (true) or undirected (false).
|
||||
func (g *Graph) SetDir(dir bool) error {
|
||||
g.Directed = dir
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetName sets the graph name.
|
||||
func (g *Graph) SetName(name string) error {
|
||||
g.Name = name
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPortEdge adds an edge to the graph from node src to node dst.
|
||||
// srcPort and dstPort are the port the node ports, leave as empty strings if it is not required.
|
||||
// This does not imply the adding of missing nodes.
|
||||
func (g *Graph) AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) error {
|
||||
as, err := NewAttrs(attrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Edges.Add(&Edge{src, srcPort, dst, dstPort, directed, as})
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddEdge adds an edge to the graph from node src to node dst.
|
||||
// This does not imply the adding of missing nodes.
|
||||
// If directed is set to true then SetDir(true) must also be called or there will be a syntax error in the output.
|
||||
func (g *Graph) AddEdge(src, dst string, directed bool, attrs map[string]string) error {
|
||||
return g.AddPortEdge(src, "", dst, "", directed, attrs)
|
||||
}
|
||||
|
||||
// AddNode adds a node to a graph/subgraph.
|
||||
// If not subgraph exists use the name of the main graph.
|
||||
// This does not imply the adding of a missing subgraph.
|
||||
func (g *Graph) AddNode(parentGraph string, name string, attrs map[string]string) error {
|
||||
as, err := NewAttrs(attrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Nodes.Add(&Node{name, as})
|
||||
g.Relations.Add(parentGraph, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNode removes a node from the graph
|
||||
func (g *Graph) RemoveNode(parentGraph string, name string) error {
|
||||
err := g.Nodes.Remove(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Relations.Remove(parentGraph, name)
|
||||
|
||||
edges := NewEdges()
|
||||
for _, e := range g.Edges.Edges {
|
||||
if e.Dst == name || e.Src == name {
|
||||
continue
|
||||
}
|
||||
|
||||
edges.Add(e)
|
||||
}
|
||||
|
||||
g.Edges = edges
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graph) getAttrs(graphName string) (Attrs, error) {
|
||||
if g.Name == graphName {
|
||||
return g.Attrs, nil
|
||||
}
|
||||
sub, ok := g.SubGraphs.SubGraphs[graphName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("graph or subgraph %s does not exist", graphName)
|
||||
}
|
||||
return sub.Attrs, nil
|
||||
}
|
||||
|
||||
// AddAttr adds an attribute to a graph/subgraph.
|
||||
func (g *Graph) AddAttr(parentGraph string, field string, value string) error {
|
||||
a, err := g.getAttrs(parentGraph)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.Add(field, value)
|
||||
}
|
||||
|
||||
// AddSubGraph adds a subgraph to a graph/subgraph.
|
||||
func (g *Graph) AddSubGraph(parentGraph string, name string, attrs map[string]string) error {
|
||||
g.Relations.Add(parentGraph, name)
|
||||
g.SubGraphs.Add(name)
|
||||
for key, value := range attrs {
|
||||
if err := g.AddAttr(name, key, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveSubGraph removes the subgraph including nodes
|
||||
func (g *Graph) RemoveSubGraph(parentGraph string, name string) error {
|
||||
for child := range g.Relations.ParentToChildren[name] {
|
||||
err := g.RemoveNode(parentGraph, child)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
g.Relations.Remove(parentGraph, name)
|
||||
g.SubGraphs.Remove(name)
|
||||
|
||||
edges := NewEdges()
|
||||
for _, e := range g.Edges.Edges {
|
||||
if e.Dst == name || e.DstPort == name || e.Src == name || e.SrcPort == name {
|
||||
continue
|
||||
}
|
||||
|
||||
edges.Add(e)
|
||||
}
|
||||
|
||||
g.Edges = edges
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNode returns whether a given node name exists as a node in the graph.
|
||||
func (g *Graph) IsNode(name string) bool {
|
||||
_, ok := g.Nodes.Lookup[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsSubGraph returns whether a given subgraph name exists as a subgraph in the graph.
|
||||
func (g *Graph) IsSubGraph(name string) bool {
|
||||
_, ok := g.SubGraphs.SubGraphs[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (g *Graph) isClusterSubGraph(name string) bool {
|
||||
isSubGraph := g.IsSubGraph(name)
|
||||
isCluster := strings.HasPrefix(name, "cluster")
|
||||
return isSubGraph && isCluster
|
||||
}
|
7
vendor/github.com/awalterschulze/gographviz/install-godeps.sh
generated
vendored
Normal file
7
vendor/github.com/awalterschulze/gographviz/install-godeps.sh
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -xe
|
||||
mkdir -p $GOPATH/src/githbub.com/goccmack
|
||||
git clone https://github.com/goccmack/gocc $GOPATH/src/github.com/goccmack/gocc
|
||||
go get golang.org/x/tools/cmd/goimports
|
||||
go get github.com/kisielk/errcheck
|
||||
go get -u golang.org/x/lint/golint
|
56
vendor/github.com/awalterschulze/gographviz/internal/errors/errors.go
generated
vendored
Normal file
56
vendor/github.com/awalterschulze/gographviz/internal/errors/errors.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/awalterschulze/gographviz/internal/token"
|
||||
)
|
||||
|
||||
type ErrorSymbol interface {
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Err error
|
||||
ErrorToken *token.Token
|
||||
ErrorSymbols []ErrorSymbol
|
||||
ExpectedTokens []string
|
||||
StackTop int
|
||||
}
|
||||
|
||||
func (e *Error) String() string {
|
||||
w := new(bytes.Buffer)
|
||||
fmt.Fprintf(w, "Error")
|
||||
if e.Err != nil {
|
||||
fmt.Fprintf(w, " %s\n", e.Err)
|
||||
} else {
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
fmt.Fprintf(w, "Token: type=%d, lit=%s\n", e.ErrorToken.Type, e.ErrorToken.Lit)
|
||||
fmt.Fprintf(w, "Pos: offset=%d, line=%d, column=%d\n", e.ErrorToken.Pos.Offset, e.ErrorToken.Pos.Line, e.ErrorToken.Pos.Column)
|
||||
fmt.Fprintf(w, "Expected one of: ")
|
||||
for _, sym := range e.ExpectedTokens {
|
||||
fmt.Fprintf(w, "%s ", sym)
|
||||
}
|
||||
fmt.Fprintf(w, "ErrorSymbol:\n")
|
||||
for _, sym := range e.ErrorSymbols {
|
||||
fmt.Fprintf(w, "%v\n", sym)
|
||||
}
|
||||
return w.String()
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
w := new(bytes.Buffer)
|
||||
fmt.Fprintf(w, "Error in S%d: %s, %s", e.StackTop, token.TokMap.TokenString(e.ErrorToken), e.ErrorToken.Pos.String())
|
||||
if e.Err != nil {
|
||||
fmt.Fprintf(w, ": %+v", e.Err)
|
||||
} else {
|
||||
fmt.Fprintf(w, ", expected one of: ")
|
||||
for _, expected := range e.ExpectedTokens {
|
||||
fmt.Fprintf(w, "%s ", expected)
|
||||
}
|
||||
}
|
||||
return w.String()
|
||||
}
|
587
vendor/github.com/awalterschulze/gographviz/internal/lexer/acttab.go
generated
vendored
Normal file
587
vendor/github.com/awalterschulze/gographviz/internal/lexer/acttab.go
generated
vendored
Normal file
@ -0,0 +1,587 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/awalterschulze/gographviz/internal/token"
|
||||
)
|
||||
|
||||
type ActionTable [NumStates]ActionRow
|
||||
|
||||
type ActionRow struct {
|
||||
Accept token.Type
|
||||
Ignore string
|
||||
}
|
||||
|
||||
func (a ActionRow) String() string {
|
||||
return fmt.Sprintf("Accept=%d, Ignore=%s", a.Accept, a.Ignore)
|
||||
}
|
||||
|
||||
var ActTab = ActionTable{
|
||||
ActionRow{ // S0
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S1
|
||||
Accept: -1,
|
||||
Ignore: "!whitespace",
|
||||
},
|
||||
ActionRow{ // S2
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S3
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S4
|
||||
Accept: 13,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S5
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S6
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S7
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S8
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S9
|
||||
Accept: 14,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S10
|
||||
Accept: 7,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S11
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S12
|
||||
Accept: 8,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S13
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S14
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S15
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S16
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S17
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S18
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S19
|
||||
Accept: 11,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S20
|
||||
Accept: 12,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S21
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S22
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S23
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S24
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S25
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S26
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S27
|
||||
Accept: 3,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S28
|
||||
Accept: 4,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S29
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S30
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S31
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S32
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S33
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S34
|
||||
Accept: -1,
|
||||
Ignore: "!comment",
|
||||
},
|
||||
ActionRow{ // S35
|
||||
Accept: 17,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S36
|
||||
Accept: 16,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S37
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S38
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S39
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S40
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S41
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S42
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S43
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S44
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S45
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S46
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S47
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S48
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S49
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S50
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S51
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S52
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S53
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S54
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S55
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S56
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S57
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S58
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S59
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S60
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S61
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S62
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S63
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S64
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S65
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S66
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S67
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S68
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S69
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S70
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S71
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S72
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S73
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S74
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S75
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S76
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S77
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S78
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S79
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S80
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S81
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S82
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S83
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S84
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S85
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S86
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S87
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S88
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S89
|
||||
Accept: -1,
|
||||
Ignore: "!comment",
|
||||
},
|
||||
ActionRow{ // S90
|
||||
Accept: 0,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S91
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S92
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S93
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S94
|
||||
Accept: 10,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S95
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S96
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S97
|
||||
Accept: 9,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S98
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S99
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S100
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S101
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S102
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S103
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S104
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S105
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S106
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S107
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S108
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S109
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S110
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S111
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S112
|
||||
Accept: 2,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S113
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S114
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S115
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S116
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S117
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S118
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S119
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S120
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S121
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S122
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S123
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S124
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S125
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S126
|
||||
Accept: 5,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S127
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S128
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S129
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S130
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S131
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S132
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S133
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S134
|
||||
Accept: 6,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S135
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S136
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S137
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S138
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S139
|
||||
Accept: 18,
|
||||
Ignore: "",
|
||||
},
|
||||
ActionRow{ // S140
|
||||
Accept: 15,
|
||||
Ignore: "",
|
||||
},
|
||||
}
|
300
vendor/github.com/awalterschulze/gographviz/internal/lexer/lexer.go
generated
vendored
Normal file
300
vendor/github.com/awalterschulze/gographviz/internal/lexer/lexer.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/awalterschulze/gographviz/internal/token"
|
||||
)
|
||||
|
||||
const (
|
||||
NoState = -1
|
||||
NumStates = 141
|
||||
NumSymbols = 184
|
||||
)
|
||||
|
||||
type Lexer struct {
|
||||
src []byte
|
||||
pos int
|
||||
line int
|
||||
column int
|
||||
}
|
||||
|
||||
func NewLexer(src []byte) *Lexer {
|
||||
lexer := &Lexer{
|
||||
src: src,
|
||||
pos: 0,
|
||||
line: 1,
|
||||
column: 1,
|
||||
}
|
||||
return lexer
|
||||
}
|
||||
|
||||
func NewLexerFile(fpath string) (*Lexer, error) {
|
||||
src, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewLexer(src), nil
|
||||
}
|
||||
|
||||
func (l *Lexer) Scan() (tok *token.Token) {
|
||||
tok = new(token.Token)
|
||||
if l.pos >= len(l.src) {
|
||||
tok.Type = token.EOF
|
||||
tok.Pos.Offset, tok.Pos.Line, tok.Pos.Column = l.pos, l.line, l.column
|
||||
return
|
||||
}
|
||||
start, startLine, startColumn, end := l.pos, l.line, l.column, 0
|
||||
tok.Type = token.INVALID
|
||||
state, rune1, size := 0, rune(-1), 0
|
||||
for state != -1 {
|
||||
if l.pos >= len(l.src) {
|
||||
rune1 = -1
|
||||
} else {
|
||||
rune1, size = utf8.DecodeRune(l.src[l.pos:])
|
||||
l.pos += size
|
||||
}
|
||||
|
||||
nextState := -1
|
||||
if rune1 != -1 {
|
||||
nextState = TransTab[state](rune1)
|
||||
}
|
||||
state = nextState
|
||||
|
||||
if state != -1 {
|
||||
|
||||
switch rune1 {
|
||||
case '\n':
|
||||
l.line++
|
||||
l.column = 1
|
||||
case '\r':
|
||||
l.column = 1
|
||||
case '\t':
|
||||
l.column += 4
|
||||
default:
|
||||
l.column++
|
||||
}
|
||||
|
||||
switch {
|
||||
case ActTab[state].Accept != -1:
|
||||
tok.Type = ActTab[state].Accept
|
||||
end = l.pos
|
||||
case ActTab[state].Ignore != "":
|
||||
start, startLine, startColumn = l.pos, l.line, l.column
|
||||
state = 0
|
||||
if start >= len(l.src) {
|
||||
tok.Type = token.EOF
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if tok.Type == token.INVALID {
|
||||
end = l.pos
|
||||
}
|
||||
}
|
||||
}
|
||||
if end > start {
|
||||
l.pos = end
|
||||
tok.Lit = l.src[start:end]
|
||||
} else {
|
||||
tok.Lit = []byte{}
|
||||
}
|
||||
tok.Pos.Offset, tok.Pos.Line, tok.Pos.Column = start, startLine, startColumn
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Lexer) Reset() {
|
||||
l.pos = 0
|
||||
}
|
||||
|
||||
/*
|
||||
Lexer symbols:
|
||||
0: 'n'
|
||||
1: 'o'
|
||||
2: 'd'
|
||||
3: 'e'
|
||||
4: 'N'
|
||||
5: 'o'
|
||||
6: 'd'
|
||||
7: 'e'
|
||||
8: 'N'
|
||||
9: 'O'
|
||||
10: 'D'
|
||||
11: 'E'
|
||||
12: 'e'
|
||||
13: 'd'
|
||||
14: 'g'
|
||||
15: 'e'
|
||||
16: 'E'
|
||||
17: 'd'
|
||||
18: 'g'
|
||||
19: 'e'
|
||||
20: 'E'
|
||||
21: 'D'
|
||||
22: 'G'
|
||||
23: 'E'
|
||||
24: 'g'
|
||||
25: 'r'
|
||||
26: 'a'
|
||||
27: 'p'
|
||||
28: 'h'
|
||||
29: 'G'
|
||||
30: 'r'
|
||||
31: 'a'
|
||||
32: 'p'
|
||||
33: 'h'
|
||||
34: 'G'
|
||||
35: 'R'
|
||||
36: 'A'
|
||||
37: 'P'
|
||||
38: 'H'
|
||||
39: 'd'
|
||||
40: 'i'
|
||||
41: 'g'
|
||||
42: 'r'
|
||||
43: 'a'
|
||||
44: 'p'
|
||||
45: 'h'
|
||||
46: 'D'
|
||||
47: 'i'
|
||||
48: 'g'
|
||||
49: 'r'
|
||||
50: 'a'
|
||||
51: 'p'
|
||||
52: 'h'
|
||||
53: 'd'
|
||||
54: 'i'
|
||||
55: 'G'
|
||||
56: 'r'
|
||||
57: 'a'
|
||||
58: 'p'
|
||||
59: 'h'
|
||||
60: 'D'
|
||||
61: 'i'
|
||||
62: 'G'
|
||||
63: 'r'
|
||||
64: 'a'
|
||||
65: 'p'
|
||||
66: 'h'
|
||||
67: 'D'
|
||||
68: 'I'
|
||||
69: 'G'
|
||||
70: 'R'
|
||||
71: 'A'
|
||||
72: 'P'
|
||||
73: 'H'
|
||||
74: 's'
|
||||
75: 'u'
|
||||
76: 'b'
|
||||
77: 'g'
|
||||
78: 'r'
|
||||
79: 'a'
|
||||
80: 'p'
|
||||
81: 'h'
|
||||
82: 'S'
|
||||
83: 'u'
|
||||
84: 'b'
|
||||
85: 'g'
|
||||
86: 'r'
|
||||
87: 'a'
|
||||
88: 'p'
|
||||
89: 'h'
|
||||
90: 's'
|
||||
91: 'u'
|
||||
92: 'b'
|
||||
93: 'G'
|
||||
94: 'r'
|
||||
95: 'a'
|
||||
96: 'p'
|
||||
97: 'h'
|
||||
98: 'S'
|
||||
99: 'u'
|
||||
100: 'b'
|
||||
101: 'G'
|
||||
102: 'r'
|
||||
103: 'a'
|
||||
104: 'p'
|
||||
105: 'h'
|
||||
106: 'S'
|
||||
107: 'U'
|
||||
108: 'B'
|
||||
109: 'G'
|
||||
110: 'R'
|
||||
111: 'A'
|
||||
112: 'P'
|
||||
113: 'H'
|
||||
114: 's'
|
||||
115: 't'
|
||||
116: 'r'
|
||||
117: 'i'
|
||||
118: 'c'
|
||||
119: 't'
|
||||
120: 'S'
|
||||
121: 't'
|
||||
122: 'r'
|
||||
123: 'i'
|
||||
124: 'c'
|
||||
125: 't'
|
||||
126: 'S'
|
||||
127: 'T'
|
||||
128: 'R'
|
||||
129: 'I'
|
||||
130: 'C'
|
||||
131: 'T'
|
||||
132: '{'
|
||||
133: '}'
|
||||
134: ';'
|
||||
135: '='
|
||||
136: '['
|
||||
137: ']'
|
||||
138: ','
|
||||
139: ':'
|
||||
140: '-'
|
||||
141: '>'
|
||||
142: '-'
|
||||
143: '-'
|
||||
144: '_'
|
||||
145: '-'
|
||||
146: '.'
|
||||
147: '-'
|
||||
148: '.'
|
||||
149: '\'
|
||||
150: '"'
|
||||
151: '\'
|
||||
152: '"'
|
||||
153: '"'
|
||||
154: '='
|
||||
155: '<'
|
||||
156: '>'
|
||||
157: '<'
|
||||
158: '>'
|
||||
159: '/'
|
||||
160: '/'
|
||||
161: '\n'
|
||||
162: '#'
|
||||
163: '\n'
|
||||
164: '/'
|
||||
165: '*'
|
||||
166: '*'
|
||||
167: '*'
|
||||
168: '/'
|
||||
169: ' '
|
||||
170: '\t'
|
||||
171: '\r'
|
||||
172: '\n'
|
||||
173: \u0001-'!'
|
||||
174: '#'-'['
|
||||
175: ']'-\u007f
|
||||
176: 'a'-'z'
|
||||
177: 'A'-'Z'
|
||||
178: '0'-'9'
|
||||
179: \u0080-\ufffc
|
||||
180: \ufffe-\U0010ffff
|
||||
181: \u0001-';'
|
||||
182: '?'-\u00ff
|
||||
183: .
|
||||
*/
|
2731
vendor/github.com/awalterschulze/gographviz/internal/lexer/transitiontable.go
generated
vendored
Normal file
2731
vendor/github.com/awalterschulze/gographviz/internal/lexer/transitiontable.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
51
vendor/github.com/awalterschulze/gographviz/internal/parser/action.go
generated
vendored
Normal file
51
vendor/github.com/awalterschulze/gographviz/internal/parser/action.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type action interface {
|
||||
act()
|
||||
String() string
|
||||
}
|
||||
|
||||
type (
|
||||
accept bool
|
||||
shift int // value is next state index
|
||||
reduce int // value is production index
|
||||
)
|
||||
|
||||
func (this accept) act() {}
|
||||
func (this shift) act() {}
|
||||
func (this reduce) act() {}
|
||||
|
||||
func (this accept) Equal(that action) bool {
|
||||
if _, ok := that.(accept); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this reduce) Equal(that action) bool {
|
||||
that1, ok := that.(reduce)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return this == that1
|
||||
}
|
||||
|
||||
func (this shift) Equal(that action) bool {
|
||||
that1, ok := that.(shift)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return this == that1
|
||||
}
|
||||
|
||||
func (this accept) String() string { return "accept(0)" }
|
||||
func (this shift) String() string { return fmt.Sprintf("shift:%d", this) }
|
||||
func (this reduce) String() string {
|
||||
return fmt.Sprintf("reduce:%d(%s)", this, productionsTable[this].String)
|
||||
}
|
152
vendor/github.com/awalterschulze/gographviz/internal/parser/actiontable.go
generated
vendored
Normal file
152
vendor/github.com/awalterschulze/gographviz/internal/parser/actiontable.go
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
type (
|
||||
actionTable [numStates]actionRow
|
||||
actionRow struct {
|
||||
canRecover bool
|
||||
actions [numSymbols]action
|
||||
}
|
||||
)
|
||||
|
||||
var actionTab = actionTable{}
|
||||
|
||||
func init() {
|
||||
tab := []struct {
|
||||
CanRecover bool
|
||||
Actions []struct {
|
||||
Index int
|
||||
Action int
|
||||
Amount int
|
||||
}
|
||||
}{}
|
||||
data := []byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x9c, 0x97, 0x4f, 0x88, 0x5b, 0x55,
|
||||
0x1b, 0xc6, 0xcf, 0x7b, 0xe7, 0x7e, 0xd3, 0xf9, 0x66, 0xa6, 0xa5, 0x0c, 0xf3, 0x85, 0x61, 0x18,
|
||||
0x86, 0x10, 0xc2, 0x10, 0x42, 0x08, 0x21, 0x0c, 0x21, 0xe4, 0x8b, 0x21, 0x8e, 0x65, 0xd0, 0x21,
|
||||
0x84, 0x10, 0x42, 0x08, 0x31, 0x86, 0x9a, 0xa6, 0x21, 0x0d, 0xf1, 0x36, 0x4c, 0xd3, 0xa1, 0xe0,
|
||||
0x1f, 0x6a, 0xad, 0xda, 0x95, 0xb8, 0x70, 0xe1, 0xca, 0xa5, 0x0b, 0x17, 0x22, 0xe2, 0x42, 0x5c,
|
||||
0x88, 0x0b, 0xe9, 0x42, 0x5c, 0xba, 0x10, 0x17, 0x22, 0x2e, 0x5c, 0x89, 0xb8, 0x10, 0x11, 0x17,
|
||||
0xbd, 0x72, 0xcf, 0x2f, 0x3d, 0x99, 0xd4, 0xc9, 0xe4, 0x26, 0x74, 0x71, 0x6e, 0x4e, 0xef, 0xf3,
|
||||
0x7b, 0x9e, 0x73, 0xce, 0x7b, 0xce, 0x3d, 0x73, 0xd1, 0x7d, 0xdb, 0x12, 0xcb, 0x7d, 0xa0, 0xc4,
|
||||
0xbd, 0xa7, 0x54, 0xc4, 0x7d, 0x7d, 0x49, 0x2c, 0xf7, 0x9e, 0x12, 0x4b, 0x56, 0x9f, 0x79, 0xd1,
|
||||
0x29, 0x5f, 0xbf, 0x76, 0xf3, 0xe4, 0xfa, 0xb1, 0x58, 0x4a, 0x2e, 0x3c, 0x7d, 0x6d, 0x78, 0xe3,
|
||||
0xa6, 0x73, 0x4b, 0xdc, 0xb7, 0x94, 0x52, 0x4f, 0xb9, 0x6f, 0x5a, 0x22, 0xf1, 0xe7, 0x5f, 0xb8,
|
||||
0x35, 0x3c, 0xbe, 0x7d, 0x6d, 0x18, 0x7c, 0x39, 0xf8, 0x9c, 0xd3, 0xbe, 0x7e, 0x27, 0x78, 0xc3,
|
||||
0x19, 0xfe, 0x3f, 0xc8, 0x9b, 0xa3, 0xe7, 0x97, 0x6e, 0xde, 0x76, 0x86, 0xde, 0x73, 0xf0, 0x55,
|
||||
0x4f, 0x2a, 0xee, 0x7d, 0xa5, 0x62, 0xee, 0x1b, 0x9e, 0xcd, 0x7d, 0x25, 0x4b, 0xf2, 0x1f, 0x2d,
|
||||
0x14, 0x5b, 0xc9, 0x32, 0x3a, 0x1e, 0xb5, 0x4c, 0x6c, 0xa5, 0xd4, 0xa3, 0xff, 0x3d, 0x74, 0x1f,
|
||||
0x28, 0xf7, 0xae, 0xb5, 0x24, 0xb6, 0xf7, 0x4f, 0xc9, 0xaa, 0xd8, 0xb2, 0xac, 0x64, 0x5d, 0x6c,
|
||||
0x59, 0x51, 0xca, 0x12, 0xb1, 0x94, 0xb2, 0x2c, 0x59, 0x16, 0x5b, 0x56, 0x95, 0x84, 0xc5, 0x96,
|
||||
0x4b, 0xba, 0xc3, 0x7b, 0xfd, 0x32, 0xef, 0x6d, 0x98, 0x37, 0x36, 0xc7, 0x6f, 0x5c, 0xd0, 0x6f,
|
||||
0x6c, 0x29, 0xdd, 0xbf, 0xad, 0x64, 0x45, 0x6c, 0xd9, 0x51, 0xb2, 0x21, 0xb6, 0xc4, 0x95, 0x6c,
|
||||
0x8a, 0x2d, 0x09, 0x25, 0xbb, 0x62, 0xcb, 0x3e, 0x9a, 0x94, 0x36, 0xf3, 0x5e, 0x4e, 0x8f, 0x9e,
|
||||
0x2c, 0x39, 0x31, 0xe0, 0xcc, 0x69, 0x6b, 0xaf, 0x23, 0x77, 0xbe, 0xd3, 0x81, 0x2f, 0xa7, 0x43,
|
||||
0xfd, 0x14, 0x10, 0x5b, 0x8e, 0x94, 0xb2, 0x96, 0x9f, 0xe0, 0xcc, 0x00, 0x58, 0xde, 0x3f, 0xa5,
|
||||
0xac, 0x8b, 0x62, 0x8b, 0x25, 0x57, 0x95, 0x4e, 0x7c, 0xd5, 0xb3, 0xd7, 0xcd, 0x25, 0x9a, 0xcb,
|
||||
0x62, 0x4b, 0xd1, 0x63, 0xe9, 0x5f, 0x9b, 0x34, 0x01, 0x9a, 0x1d, 0xb1, 0xa5, 0xec, 0x91, 0xf5,
|
||||
0xaf, 0x20, 0x4d, 0x88, 0x26, 0xac, 0x9b, 0xb3, 0x07, 0x57, 0x99, 0x91, 0xed, 0x82, 0x4e, 0x14,
|
||||
0x22, 0x51, 0x88, 0x44, 0x21, 0x32, 0x84, 0xc8, 0x10, 0xc2, 0x35, 0x84, 0x4f, 0x48, 0x29, 0x6b,
|
||||
0x45, 0x6b, 0xf6, 0xd0, 0xec, 0xa1, 0xd9, 0xf3, 0x46, 0x61, 0x4b, 0x0d, 0xe9, 0x1e, 0xd2, 0x3d,
|
||||
0xa4, 0x7b, 0x48, 0xf7, 0x8c, 0x34, 0x86, 0x34, 0x86, 0x34, 0xc6, 0x04, 0xc4, 0x90, 0xc6, 0x90,
|
||||
0xc6, 0x90, 0xc6, 0x90, 0xc6, 0x8c, 0x34, 0x8e, 0x34, 0x8e, 0x34, 0x8e, 0x34, 0x8e, 0x34, 0x8e,
|
||||
0x34, 0x8e, 0x34, 0x8e, 0x34, 0x6e, 0xa4, 0x09, 0xa4, 0x09, 0xa4, 0x09, 0xa4, 0x09, 0xa4, 0x09,
|
||||
0xa4, 0x09, 0xa4, 0x09, 0xa4, 0x09, 0xa5, 0xac, 0x55, 0x2d, 0x4d, 0x22, 0x4d, 0x22, 0x4d, 0x22,
|
||||
0x4d, 0x22, 0x4d, 0x22, 0x4d, 0x22, 0x4d, 0x7a, 0x8b, 0x63, 0x4b, 0xd3, 0x5b, 0x1c, 0xfb, 0xf1,
|
||||
0xe2, 0x24, 0x27, 0x6a, 0x67, 0xfc, 0xb4, 0xa6, 0xe1, 0x0d, 0xe0, 0x0d, 0xe0, 0x0d, 0xe0, 0x0d,
|
||||
0xe0, 0x0d, 0xe0, 0x0d, 0x35, 0xd2, 0x68, 0x8f, 0xc6, 0x59, 0x1e, 0x0d, 0x53, 0xee, 0xbd, 0x71,
|
||||
0xb9, 0x53, 0x6f, 0x27, 0x6a, 0xb4, 0x43, 0xb4, 0xc1, 0x09, 0x06, 0x27, 0x5e, 0xbd, 0xe9, 0x66,
|
||||
0x83, 0x66, 0x93, 0x26, 0x40, 0xb3, 0x43, 0xb3, 0x4b, 0x13, 0xa4, 0x09, 0xd1, 0x84, 0x47, 0xdb,
|
||||
0xed, 0xac, 0x7a, 0x73, 0x7c, 0xd4, 0xdb, 0x93, 0x9a, 0x63, 0x5f, 0x1b, 0x70, 0x9a, 0xe3, 0x1d,
|
||||
0x5f, 0xea, 0x57, 0xcc, 0x3e, 0xdc, 0x98, 0xc2, 0x79, 0x6d, 0x81, 0xe4, 0xee, 0xdd, 0x19, 0xa2,
|
||||
0xff, 0xea, 0x05, 0xd8, 0x67, 0x01, 0xf6, 0x59, 0x80, 0x7d, 0x16, 0x60, 0x9f, 0x99, 0xdf, 0x67,
|
||||
0xe6, 0xf7, 0x59, 0x61, 0xef, 0x28, 0xde, 0xe5, 0x67, 0x58, 0x37, 0xde, 0xa2, 0x6e, 0x79, 0xff,
|
||||
0xf1, 0x00, 0xa8, 0xfb, 0xce, 0xb4, 0x28, 0xef, 0xce, 0x9c, 0x07, 0xad, 0x7f, 0xcf, 0xd4, 0x5d,
|
||||
0x8b, 0x54, 0x2d, 0x52, 0xb5, 0x48, 0xd5, 0x22, 0x55, 0x8b, 0x54, 0x2d, 0xea, 0xa1, 0x45, 0xa8,
|
||||
0x16, 0x85, 0xd0, 0xa2, 0x10, 0x5a, 0x44, 0x6c, 0x8d, 0xd1, 0xef, 0x9b, 0x39, 0x5e, 0x35, 0x27,
|
||||
0x4b, 0x18, 0x93, 0x30, 0x26, 0x61, 0xe8, 0x61, 0xe8, 0x61, 0xb0, 0x61, 0x40, 0x61, 0xa3, 0x89,
|
||||
0xa0, 0x89, 0xa0, 0x89, 0xa0, 0x89, 0xa0, 0x89, 0xa0, 0x89, 0xa0, 0x89, 0x98, 0xc1, 0x94, 0xd0,
|
||||
0x94, 0xd0, 0x94, 0x18, 0x4c, 0x09, 0x69, 0x09, 0x69, 0x69, 0x62, 0x13, 0x95, 0xce, 0xda, 0x44,
|
||||
0x25, 0xa5, 0xac, 0x25, 0x3d, 0xab, 0xee, 0x07, 0x4c, 0xa0, 0xfb, 0xa1, 0x1a, 0x0f, 0x6e, 0x49,
|
||||
0x7b, 0x1c, 0x03, 0x38, 0x46, 0x72, 0x6c, 0xfa, 0x87, 0xf4, 0x0f, 0xe9, 0x1f, 0x9a, 0xd5, 0x4f,
|
||||
0x11, 0x2d, 0x45, 0xb4, 0x14, 0xd1, 0x52, 0x44, 0x4b, 0x11, 0x2d, 0x35, 0xb9, 0xfa, 0x29, 0x10,
|
||||
0xe3, 0x02, 0x4a, 0x83, 0x48, 0x83, 0x48, 0x83, 0x48, 0x83, 0x48, 0x83, 0x48, 0x4f, 0x22, 0xd2,
|
||||
0x20, 0xd2, 0x06, 0xd1, 0x04, 0xd1, 0x04, 0xd1, 0x04, 0xd1, 0x04, 0xd1, 0x04, 0xd1, 0x9c, 0x44,
|
||||
0x34, 0x41, 0x34, 0xcd, 0x1c, 0x17, 0x40, 0x14, 0x40, 0x14, 0x40, 0x14, 0x40, 0x14, 0x40, 0x14,
|
||||
0x26, 0xe6, 0xb8, 0x70, 0xd6, 0x1c, 0x17, 0xa6, 0x15, 0xf0, 0x47, 0xbe, 0x36, 0xb2, 0xfb, 0xb1,
|
||||
0xa9, 0xb2, 0xe5, 0x69, 0xa4, 0x4f, 0x7c, 0x7d, 0x90, 0xed, 0x69, 0xf2, 0x4f, 0x17, 0x39, 0x09,
|
||||
0x3e, 0xf3, 0xe5, 0xb9, 0x39, 0x4d, 0xfe, 0xf9, 0x22, 0x9e, 0x5f, 0xf8, 0xf2, 0xdc, 0x36, 0x4f,
|
||||
0x81, 0x69, 0xa0, 0x2f, 0x67, 0x80, 0x46, 0x67, 0xd0, 0x57, 0xa7, 0xce, 0x20, 0x5b, 0x5f, 0x57,
|
||||
0xdc, 0xaf, 0x95, 0x6c, 0x89, 0x25, 0xcf, 0x2a, 0xd9, 0xa6, 0x09, 0xeb, 0xe6, 0x71, 0xd5, 0x65,
|
||||
0x28, 0x99, 0x0c, 0x25, 0x93, 0xa1, 0x64, 0x32, 0x94, 0x4c, 0x86, 0x92, 0xc9, 0x70, 0xc6, 0x64,
|
||||
0x28, 0x99, 0x0c, 0x84, 0x8c, 0xde, 0x55, 0xda, 0xf4, 0xa1, 0x87, 0xb6, 0xc5, 0xfd, 0xe6, 0x94,
|
||||
0xf9, 0x92, 0x36, 0x3d, 0xc0, 0xf4, 0x00, 0xc9, 0xc1, 0x28, 0x94, 0xfe, 0x44, 0x6d, 0xd1, 0x6c,
|
||||
0x4f, 0x7e, 0xb0, 0xf8, 0x98, 0x77, 0xc9, 0xd4, 0x25, 0x53, 0x97, 0x4c, 0x5d, 0x32, 0x75, 0xc9,
|
||||
0xd4, 0x25, 0x4c, 0x97, 0x03, 0xaf, 0xcb, 0x81, 0xd7, 0x05, 0xd4, 0x35, 0x17, 0x8a, 0x28, 0xa0,
|
||||
0x28, 0xa0, 0x28, 0xa0, 0x28, 0xa0, 0x28, 0xa0, 0x28, 0xa0, 0x28, 0xd2, 0xa8, 0x91, 0x4e, 0xfb,
|
||||
0x24, 0x4f, 0x7e, 0x8b, 0x77, 0x27, 0xe3, 0xaf, 0x6b, 0x69, 0x1b, 0x69, 0x1b, 0x69, 0x1b, 0x69,
|
||||
0x1b, 0x69, 0x1b, 0x69, 0x9b, 0x29, 0x6d, 0x73, 0x6d, 0x74, 0xbf, 0x85, 0xd4, 0x66, 0x34, 0x6d,
|
||||
0x46, 0xd3, 0x86, 0xdb, 0x36, 0x5c, 0x7f, 0x91, 0xe6, 0xb8, 0x1e, 0x50, 0x01, 0x65, 0xb0, 0x65,
|
||||
0xb0, 0x65, 0xb0, 0x65, 0xb0, 0x65, 0xb0, 0xe5, 0xc9, 0x73, 0xa7, 0x0c, 0xa2, 0x7c, 0xfe, 0x59,
|
||||
0x3c, 0xd7, 0x0d, 0x7c, 0xfd, 0xdc, 0x7b, 0xf7, 0xc2, 0x17, 0x6e, 0x8e, 0xc5, 0x2a, 0xd8, 0x2a,
|
||||
0xd8, 0x2a, 0xd8, 0x2a, 0xd8, 0x2a, 0xd8, 0x2a, 0xd8, 0x2a, 0xbc, 0x2a, 0xbc, 0x2a, 0xbc, 0x2a,
|
||||
0xbc, 0xaa, 0xe1, 0x55, 0xe0, 0x55, 0xe0, 0x55, 0xe0, 0x55, 0xe0, 0x55, 0xe0, 0x55, 0xe0, 0x55,
|
||||
0xe0, 0x55, 0xe0, 0x55, 0xe0, 0x55, 0xe0, 0x55, 0xcc, 0x7d, 0xd0, 0xfd, 0x6e, 0x7c, 0x21, 0x64,
|
||||
0x4d, 0x8a, 0x38, 0x14, 0x71, 0x28, 0xe2, 0x30, 0xfa, 0xcb, 0xa3, 0x88, 0x43, 0x71, 0x72, 0x4d,
|
||||
0x8a, 0x40, 0x8b, 0x66, 0x13, 0x39, 0x20, 0x1c, 0x10, 0x0e, 0x08, 0x07, 0x84, 0x03, 0xc2, 0x41,
|
||||
0xea, 0x90, 0xce, 0x21, 0x9d, 0x03, 0xc8, 0x99, 0x76, 0x12, 0x7d, 0xbf, 0xc8, 0x39, 0xf8, 0x83,
|
||||
0xaf, 0x73, 0x70, 0xdd, 0x3c, 0x5d, 0x32, 0x4f, 0x2b, 0xd3, 0x90, 0x3f, 0xfa, 0x42, 0xee, 0x9a,
|
||||
0xa7, 0xad, 0x69, 0xa0, 0x9f, 0x7c, 0x81, 0x76, 0xcc, 0xda, 0xe4, 0x98, 0xd8, 0x1c, 0x13, 0x9b,
|
||||
0x63, 0x62, 0x73, 0x4c, 0x6c, 0x8e, 0x89, 0xcd, 0xb1, 0xfa, 0x39, 0xe6, 0x37, 0xc7, 0x8c, 0xe6,
|
||||
0xc6, 0x27, 0xe6, 0xcf, 0x67, 0x9c, 0x98, 0xa3, 0x2b, 0xda, 0x2f, 0xc6, 0x27, 0x8b, 0x4f, 0x16,
|
||||
0x9f, 0x2c, 0x3e, 0x59, 0x7c, 0xb2, 0xf8, 0x64, 0xf1, 0xc9, 0xe2, 0x93, 0xc5, 0x27, 0x6b, 0x8e,
|
||||
0xdf, 0x2b, 0x9c, 0xaf, 0x57, 0xe8, 0xbf, 0x32, 0x36, 0x39, 0xe5, 0xf7, 0xab, 0xa9, 0xea, 0x3a,
|
||||
0x7e, 0x75, 0xfc, 0xea, 0xf8, 0xd5, 0xf1, 0xab, 0xe3, 0x57, 0xc7, 0xaf, 0x8e, 0x5f, 0x9d, 0xba,
|
||||
0xa9, 0x53, 0x37, 0x75, 0x5c, 0xea, 0x86, 0x57, 0x83, 0x57, 0x83, 0x57, 0x83, 0x37, 0xfa, 0xf3,
|
||||
0xb3, 0x06, 0xaf, 0x06, 0xaf, 0x06, 0xaf, 0x06, 0xaf, 0x06, 0xaf, 0x06, 0xaf, 0x36, 0x6d, 0xd9,
|
||||
0x7e, 0x5b, 0xa4, 0x0e, 0x7f, 0xf7, 0x77, 0x83, 0xf9, 0xc3, 0xec, 0xa2, 0x1e, 0x83, 0xe8, 0x31,
|
||||
0x88, 0x1e, 0x83, 0xe8, 0x31, 0x88, 0x1e, 0x83, 0xe8, 0x91, 0xbe, 0x47, 0xfa, 0x1e, 0xe9, 0x7b,
|
||||
0xa4, 0xef, 0x19, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0,
|
||||
0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x60, 0xda, 0x88, 0xfe, 0xf4, 0x55, 0xbd, 0x97, 0xcd, 0x53,
|
||||
0xd0, 0xd4, 0x57, 0x9e, 0x44, 0x79, 0x12, 0xe5, 0x49, 0x94, 0x27, 0x51, 0x9e, 0x44, 0x79, 0xd6,
|
||||
0x27, 0x4f, 0xb0, 0x3c, 0x51, 0xf2, 0xa6, 0xbe, 0x8e, 0xa8, 0xaf, 0x23, 0xfa, 0x8f, 0x4c, 0xff,
|
||||
0xbf, 0xbf, 0xeb, 0xf4, 0x1f, 0xd2, 0x7f, 0x48, 0xff, 0xa1, 0xa9, 0x94, 0x0e, 0x49, 0x3a, 0x24,
|
||||
0xe9, 0x90, 0xa4, 0x43, 0x92, 0x0e, 0x49, 0x3a, 0x24, 0xe9, 0x90, 0xa4, 0xc3, 0x14, 0x75, 0x98,
|
||||
0xa2, 0x0e, 0xbc, 0x8e, 0xe1, 0xcd, 0xf5, 0xa1, 0x3c, 0xf7, 0x0b, 0xb9, 0x36, 0xc7, 0x7d, 0x24,
|
||||
0xe0, 0xe3, 0x5a, 0xb2, 0x36, 0xc7, 0xd1, 0x1c, 0x58, 0xf4, 0x84, 0xfe, 0x6b, 0x91, 0x9d, 0xf1,
|
||||
0xf7, 0x0c, 0x11, 0x55, 0xdc, 0x27, 0x79, 0x9f, 0xe4, 0x7d, 0x92, 0xf7, 0x49, 0xde, 0x27, 0x79,
|
||||
0x9f, 0xc8, 0x7d, 0x22, 0xf7, 0x89, 0xdc, 0x27, 0x72, 0xdf, 0x4c, 0x81, 0xbf, 0x7d, 0x15, 0xf0,
|
||||
0xb1, 0xbd, 0xd6, 0xe6, 0xd8, 0x5e, 0x81, 0x45, 0x77, 0xd9, 0xa3, 0x19, 0xb3, 0xb3, 0x36, 0xc7,
|
||||
0xec, 0x04, 0x66, 0x4f, 0xd2, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x47, 0x5c, 0x26, 0x6b,
|
||||
0x16, 0x00, 0x00,
|
||||
}
|
||||
buf, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dec := gob.NewDecoder(buf)
|
||||
if err := dec.Decode(&tab); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i, row := range tab {
|
||||
actionTab[i].canRecover = row.CanRecover
|
||||
for _, a := range row.Actions {
|
||||
switch a.Action {
|
||||
case 0:
|
||||
actionTab[i].actions[a.Index] = accept(true)
|
||||
case 1:
|
||||
actionTab[i].actions[a.Index] = reduce(a.Amount)
|
||||
case 2:
|
||||
actionTab[i].actions[a.Index] = shift(a.Amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
vendor/github.com/awalterschulze/gographviz/internal/parser/gototable.go
generated
vendored
Normal file
56
vendor/github.com/awalterschulze/gographviz/internal/parser/gototable.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
const numNTSymbols = 17
|
||||
|
||||
type (
|
||||
gotoTable [numStates]gotoRow
|
||||
gotoRow [numNTSymbols]int
|
||||
)
|
||||
|
||||
var gotoTab = gotoTable{}
|
||||
|
||||
func init() {
|
||||
tab := [][]int{}
|
||||
data := []byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xe2, 0xfd, 0xdf, 0xcd, 0xc4, 0xc8,
|
||||
0xf4, 0xbf, 0x87, 0x81, 0xf1, 0x7f, 0x17, 0x03, 0x03, 0xcf, 0xff, 0x4e, 0x10, 0xaf, 0x8b, 0x81,
|
||||
0x91, 0x85, 0x81, 0xe1, 0x1f, 0xa7, 0xc6, 0xff, 0x1e, 0x86, 0xff, 0x0d, 0x82, 0x8c, 0x4c, 0x8c,
|
||||
0xa8, 0x40, 0x90, 0x11, 0x1d, 0x60, 0x88, 0xf0, 0x10, 0xa1, 0x46, 0x4c, 0x90, 0x91, 0x51, 0x41,
|
||||
0x49, 0x45, 0x8b, 0x91, 0x91, 0x51, 0x83, 0x51, 0xcd, 0x88, 0x51, 0x87, 0x51, 0x8e, 0x08, 0x5d,
|
||||
0x98, 0x22, 0x36, 0x18, 0x22, 0x0e, 0x82, 0x8c, 0x8c, 0x2e, 0x44, 0x9a, 0xec, 0x81, 0x22, 0xe2,
|
||||
0x43, 0x9a, 0x7b, 0x02, 0xa0, 0x22, 0x61, 0x94, 0xfb, 0x82, 0x54, 0x91, 0x28, 0x10, 0x11, 0x03,
|
||||
0x13, 0x49, 0xc2, 0x50, 0x93, 0x82, 0x21, 0x92, 0xc6, 0xc8, 0xc8, 0x98, 0x81, 0xa2, 0x0b, 0x01,
|
||||
0x72, 0xb0, 0xda, 0x55, 0x80, 0x11, 0x1a, 0x25, 0x44, 0x84, 0x4f, 0x15, 0x79, 0x71, 0x8a, 0x11,
|
||||
0x86, 0xff, 0x9b, 0x88, 0x35, 0xe8, 0x7f, 0xd7, 0xff, 0x1e, 0x18, 0xb3, 0x8d, 0x88, 0xf8, 0xf8,
|
||||
0x3f, 0x89, 0x08, 0x47, 0xfd, 0x9f, 0x46, 0x51, 0x1c, 0xfd, 0x9f, 0x05, 0x37, 0x68, 0x0e, 0xaa,
|
||||
0xaa, 0xff, 0x4b, 0x18, 0xff, 0x2f, 0x62, 0xfc, 0xbf, 0x80, 0xe6, 0xc9, 0xe4, 0xff, 0x0a, 0x54,
|
||||
0x27, 0xfc, 0x5f, 0x43, 0xa5, 0x98, 0x21, 0x46, 0xcd, 0xff, 0x6d, 0xc4, 0x58, 0x86, 0xa9, 0x6d,
|
||||
0x0f, 0x35, 0xdd, 0xf8, 0xff, 0x10, 0x5a, 0xc2, 0x20, 0x32, 0xe4, 0x18, 0x19, 0xff, 0x9f, 0x20,
|
||||
0x4f, 0x1f, 0xbd, 0x45, 0xfe, 0x5f, 0x60, 0xfc, 0x7f, 0x0e, 0x92, 0x98, 0xfe, 0x5f, 0xc2, 0x17,
|
||||
0x74, 0x01, 0xe4, 0xda, 0xf7, 0xff, 0x1a, 0xf9, 0x39, 0xf8, 0x0e, 0x15, 0x8a, 0x77, 0x0a, 0x0a,
|
||||
0x58, 0x0a, 0x8a, 0x65, 0x7c, 0xf1, 0xff, 0xff, 0x09, 0x69, 0x46, 0xbd, 0xc0, 0x63, 0xd4, 0x2b,
|
||||
0xf2, 0x03, 0xf7, 0xd3, 0xc0, 0x06, 0xee, 0x60, 0x13, 0xc1, 0x0c, 0xa0, 0x3f, 0xf4, 0x0a, 0x20,
|
||||
0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8f, 0xfa, 0xd1, 0x1a, 0x46, 0x09, 0x00, 0x00,
|
||||
}
|
||||
buf, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dec := gob.NewDecoder(buf)
|
||||
if err := dec.Decode(&tab); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i := 0; i < numStates; i++ {
|
||||
for j := 0; j < numNTSymbols; j++ {
|
||||
gotoTab[i][j] = tab[i][j]
|
||||
}
|
||||
}
|
||||
}
|
72
vendor/github.com/awalterschulze/gographviz/internal/parser/main.go
generated
vendored
Normal file
72
vendor/github.com/awalterschulze/gographviz/internal/parser/main.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
//A parser for the DOT grammar.
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/awalterschulze/gographviz/ast"
|
||||
"github.com/awalterschulze/gographviz/internal/lexer"
|
||||
)
|
||||
|
||||
//Parses a DOT string and outputs the
|
||||
//abstract syntax tree representing the graph.
|
||||
func ParseString(dotString string) (*ast.Graph, error) {
|
||||
return ParseBytes([]byte(dotString))
|
||||
}
|
||||
|
||||
//Parses the bytes representing a DOT string
|
||||
//and outputs the abstract syntax tree representing the graph.
|
||||
func ParseBytes(dotBytes []byte) (*ast.Graph, error) {
|
||||
lex := lexer.NewLexer(dotBytes)
|
||||
parser := NewParser()
|
||||
st, err := parser.Parse(lex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g, ok := st.(*ast.Graph)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Parser did not return an *ast.Graph, but rather a %T", st))
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
//Parses a reader which contains a DOT string
|
||||
//and outputs the abstract syntax tree representing the graph.
|
||||
func Parse(r io.Reader) (*ast.Graph, error) {
|
||||
bytes, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseBytes(bytes)
|
||||
}
|
||||
|
||||
//Parses a file which contains a DOT string
|
||||
//and outputs the abstract syntax tree representing the graph.
|
||||
func ParseFile(filename string) (*ast.Graph, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g, err := Parse(f)
|
||||
if err := f.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return g, err
|
||||
}
|
216
vendor/github.com/awalterschulze/gographviz/internal/parser/parser.go
generated
vendored
Normal file
216
vendor/github.com/awalterschulze/gographviz/internal/parser/parser.go
generated
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
parseError "github.com/awalterschulze/gographviz/internal/errors"
|
||||
"github.com/awalterschulze/gographviz/internal/token"
|
||||
)
|
||||
|
||||
const (
|
||||
numProductions = 60
|
||||
numStates = 128
|
||||
numSymbols = 36
|
||||
)
|
||||
|
||||
// Stack
|
||||
|
||||
type stack struct {
|
||||
state []int
|
||||
attrib []Attrib
|
||||
}
|
||||
|
||||
const iNITIAL_STACK_SIZE = 100
|
||||
|
||||
func newStack() *stack {
|
||||
return &stack{
|
||||
state: make([]int, 0, iNITIAL_STACK_SIZE),
|
||||
attrib: make([]Attrib, 0, iNITIAL_STACK_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) reset() {
|
||||
s.state = s.state[:0]
|
||||
s.attrib = s.attrib[:0]
|
||||
}
|
||||
|
||||
func (s *stack) push(state int, a Attrib) {
|
||||
s.state = append(s.state, state)
|
||||
s.attrib = append(s.attrib, a)
|
||||
}
|
||||
|
||||
func (s *stack) top() int {
|
||||
return s.state[len(s.state)-1]
|
||||
}
|
||||
|
||||
func (s *stack) peek(pos int) int {
|
||||
return s.state[pos]
|
||||
}
|
||||
|
||||
func (s *stack) topIndex() int {
|
||||
return len(s.state) - 1
|
||||
}
|
||||
|
||||
func (s *stack) popN(items int) []Attrib {
|
||||
lo, hi := len(s.state)-items, len(s.state)
|
||||
|
||||
attrib := s.attrib[lo:hi]
|
||||
|
||||
s.state = s.state[:lo]
|
||||
s.attrib = s.attrib[:lo]
|
||||
|
||||
return attrib
|
||||
}
|
||||
|
||||
func (s *stack) String() string {
|
||||
w := new(bytes.Buffer)
|
||||
fmt.Fprintf(w, "stack:\n")
|
||||
for i, st := range s.state {
|
||||
fmt.Fprintf(w, "\t%d: %d , ", i, st)
|
||||
if s.attrib[i] == nil {
|
||||
fmt.Fprintf(w, "nil")
|
||||
} else {
|
||||
switch attr := s.attrib[i].(type) {
|
||||
case *token.Token:
|
||||
fmt.Fprintf(w, "%s", attr.Lit)
|
||||
default:
|
||||
fmt.Fprintf(w, "%v", attr)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
return w.String()
|
||||
}
|
||||
|
||||
// Parser
|
||||
|
||||
type Parser struct {
|
||||
stack *stack
|
||||
nextToken *token.Token
|
||||
pos int
|
||||
}
|
||||
|
||||
type Scanner interface {
|
||||
Scan() (tok *token.Token)
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
p := &Parser{stack: newStack()}
|
||||
p.Reset()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) Reset() {
|
||||
p.stack.reset()
|
||||
p.stack.push(0, nil)
|
||||
}
|
||||
|
||||
func (p *Parser) Error(err error, scanner Scanner) (recovered bool, errorAttrib *parseError.Error) {
|
||||
errorAttrib = &parseError.Error{
|
||||
Err: err,
|
||||
ErrorToken: p.nextToken,
|
||||
ErrorSymbols: p.popNonRecoveryStates(),
|
||||
ExpectedTokens: make([]string, 0, 8),
|
||||
}
|
||||
for t, action := range actionTab[p.stack.top()].actions {
|
||||
if action != nil {
|
||||
errorAttrib.ExpectedTokens = append(errorAttrib.ExpectedTokens, token.TokMap.Id(token.Type(t)))
|
||||
}
|
||||
}
|
||||
|
||||
if action := actionTab[p.stack.top()].actions[token.TokMap.Type("error")]; action != nil {
|
||||
p.stack.push(int(action.(shift)), errorAttrib) // action can only be shift
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if action := actionTab[p.stack.top()].actions[p.nextToken.Type]; action != nil {
|
||||
recovered = true
|
||||
}
|
||||
for !recovered && p.nextToken.Type != token.EOF {
|
||||
p.nextToken = scanner.Scan()
|
||||
if action := actionTab[p.stack.top()].actions[p.nextToken.Type]; action != nil {
|
||||
recovered = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Parser) popNonRecoveryStates() (removedAttribs []parseError.ErrorSymbol) {
|
||||
if rs, ok := p.firstRecoveryState(); ok {
|
||||
errorSymbols := p.stack.popN(p.stack.topIndex() - rs)
|
||||
removedAttribs = make([]parseError.ErrorSymbol, len(errorSymbols))
|
||||
for i, e := range errorSymbols {
|
||||
removedAttribs[i] = e
|
||||
}
|
||||
} else {
|
||||
removedAttribs = []parseError.ErrorSymbol{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// recoveryState points to the highest state on the stack, which can recover
|
||||
func (p *Parser) firstRecoveryState() (recoveryState int, canRecover bool) {
|
||||
recoveryState, canRecover = p.stack.topIndex(), actionTab[p.stack.top()].canRecover
|
||||
for recoveryState > 0 && !canRecover {
|
||||
recoveryState--
|
||||
canRecover = actionTab[p.stack.peek(recoveryState)].canRecover
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Parser) newError(err error) error {
|
||||
e := &parseError.Error{
|
||||
Err: err,
|
||||
StackTop: p.stack.top(),
|
||||
ErrorToken: p.nextToken,
|
||||
}
|
||||
actRow := actionTab[p.stack.top()]
|
||||
for i, t := range actRow.actions {
|
||||
if t != nil {
|
||||
e.ExpectedTokens = append(e.ExpectedTokens, token.TokMap.Id(token.Type(i)))
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(scanner Scanner) (res interface{}, err error) {
|
||||
p.Reset()
|
||||
p.nextToken = scanner.Scan()
|
||||
for acc := false; !acc; {
|
||||
action := actionTab[p.stack.top()].actions[p.nextToken.Type]
|
||||
if action == nil {
|
||||
if recovered, errAttrib := p.Error(nil, scanner); !recovered {
|
||||
p.nextToken = errAttrib.ErrorToken
|
||||
return nil, p.newError(nil)
|
||||
}
|
||||
if action = actionTab[p.stack.top()].actions[p.nextToken.Type]; action == nil {
|
||||
panic("Error recovery led to invalid action")
|
||||
}
|
||||
}
|
||||
|
||||
switch act := action.(type) {
|
||||
case accept:
|
||||
res = p.stack.popN(1)[0]
|
||||
acc = true
|
||||
case shift:
|
||||
p.stack.push(int(act), p.nextToken)
|
||||
p.nextToken = scanner.Scan()
|
||||
case reduce:
|
||||
prod := productionsTable[int(act)]
|
||||
attrib, err := prod.ReduceFunc(p.stack.popN(prod.NumSymbols))
|
||||
if err != nil {
|
||||
return nil, p.newError(err)
|
||||
} else {
|
||||
p.stack.push(gotoTab[p.stack.top()][prod.NTType], attrib)
|
||||
}
|
||||
default:
|
||||
panic("unknown action: " + action.String())
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
623
vendor/github.com/awalterschulze/gographviz/internal/parser/productionstable.go
generated
vendored
Normal file
623
vendor/github.com/awalterschulze/gographviz/internal/parser/productionstable.go
generated
vendored
Normal file
@ -0,0 +1,623 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
import "github.com/awalterschulze/gographviz/ast"
|
||||
|
||||
type (
|
||||
//TODO: change type and variable names to be consistent with other tables
|
||||
ProdTab [numProductions]ProdTabEntry
|
||||
ProdTabEntry struct {
|
||||
String string
|
||||
Id string
|
||||
NTType int
|
||||
Index int
|
||||
NumSymbols int
|
||||
ReduceFunc func([]Attrib) (Attrib, error)
|
||||
}
|
||||
Attrib interface {
|
||||
}
|
||||
)
|
||||
|
||||
var productionsTable = ProdTab{
|
||||
ProdTabEntry{
|
||||
String: `S' : DotGraph << >>`,
|
||||
Id: "S'",
|
||||
NTType: 0,
|
||||
Index: 0,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return X[0], nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : graphx "{" "}" << ast.NewGraph(ast.GRAPH, ast.FALSE, nil, nil) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 1,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.GRAPH, ast.FALSE, nil, nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : strict graphx "{" "}" << ast.NewGraph(ast.GRAPH, ast.TRUE, nil, nil) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 2,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.GRAPH, ast.TRUE, nil, nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : graphx Id "{" "}" << ast.NewGraph(ast.GRAPH, ast.FALSE, X[1], nil) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 3,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.GRAPH, ast.FALSE, X[1], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : strict graphx Id "{" "}" << ast.NewGraph(ast.GRAPH, ast.TRUE, X[2], nil) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 4,
|
||||
NumSymbols: 5,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.GRAPH, ast.TRUE, X[2], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : graphx "{" StmtList "}" << ast.NewGraph(ast.GRAPH, ast.FALSE, nil, X[2]) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 5,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.GRAPH, ast.FALSE, nil, X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : graphx Id "{" StmtList "}" << ast.NewGraph(ast.GRAPH, ast.FALSE, X[1], X[3]) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 6,
|
||||
NumSymbols: 5,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.GRAPH, ast.FALSE, X[1], X[3])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : strict graphx "{" StmtList "}" << ast.NewGraph(ast.GRAPH, ast.TRUE, nil, X[3]) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 7,
|
||||
NumSymbols: 5,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.GRAPH, ast.TRUE, nil, X[3])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : strict graphx Id "{" StmtList "}" << ast.NewGraph(ast.GRAPH, ast.TRUE, X[2], X[4]) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 8,
|
||||
NumSymbols: 6,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.GRAPH, ast.TRUE, X[2], X[4])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : digraph "{" "}" << ast.NewGraph(ast.DIGRAPH, ast.FALSE, nil, nil) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 9,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.DIGRAPH, ast.FALSE, nil, nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : strict digraph "{" "}" << ast.NewGraph(ast.DIGRAPH, ast.TRUE, nil, nil) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 10,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.DIGRAPH, ast.TRUE, nil, nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : digraph Id "{" "}" << ast.NewGraph(ast.DIGRAPH, ast.FALSE, X[1], nil) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 11,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.DIGRAPH, ast.FALSE, X[1], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : strict digraph Id "{" "}" << ast.NewGraph(ast.DIGRAPH, ast.TRUE, X[2], nil) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 12,
|
||||
NumSymbols: 5,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.DIGRAPH, ast.TRUE, X[2], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : digraph "{" StmtList "}" << ast.NewGraph(ast.DIGRAPH, ast.FALSE, nil, X[2]) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 13,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.DIGRAPH, ast.FALSE, nil, X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : digraph Id "{" StmtList "}" << ast.NewGraph(ast.DIGRAPH, ast.FALSE, X[1], X[3]) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 14,
|
||||
NumSymbols: 5,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.DIGRAPH, ast.FALSE, X[1], X[3])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : strict digraph "{" StmtList "}" << ast.NewGraph(ast.DIGRAPH, ast.TRUE, nil, X[3]) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 15,
|
||||
NumSymbols: 5,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.DIGRAPH, ast.TRUE, nil, X[3])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `DotGraph : strict digraph Id "{" StmtList "}" << ast.NewGraph(ast.DIGRAPH, ast.TRUE, X[2], X[4]) >>`,
|
||||
Id: "DotGraph",
|
||||
NTType: 1,
|
||||
Index: 16,
|
||||
NumSymbols: 6,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraph(ast.DIGRAPH, ast.TRUE, X[2], X[4])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `StmtList : Stmt1 << ast.NewStmtList(X[0]) >>`,
|
||||
Id: "StmtList",
|
||||
NTType: 2,
|
||||
Index: 17,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewStmtList(X[0])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `StmtList : StmtList Stmt1 << ast.AppendStmtList(X[0], X[1]) >>`,
|
||||
Id: "StmtList",
|
||||
NTType: 2,
|
||||
Index: 18,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.AppendStmtList(X[0], X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Stmt1 : Stmt << X[0], nil >>`,
|
||||
Id: "Stmt1",
|
||||
NTType: 3,
|
||||
Index: 19,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return X[0], nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Stmt1 : Stmt ";" << X[0], nil >>`,
|
||||
Id: "Stmt1",
|
||||
NTType: 3,
|
||||
Index: 20,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return X[0], nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Stmt : Id "=" Id << ast.NewAttr(X[0], X[2]) >>`,
|
||||
Id: "Stmt",
|
||||
NTType: 4,
|
||||
Index: 21,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewAttr(X[0], X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Stmt : NodeStmt << X[0], nil >>`,
|
||||
Id: "Stmt",
|
||||
NTType: 4,
|
||||
Index: 22,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return X[0], nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Stmt : EdgeStmt << X[0], nil >>`,
|
||||
Id: "Stmt",
|
||||
NTType: 4,
|
||||
Index: 23,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return X[0], nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Stmt : AttrStmt << X[0], nil >>`,
|
||||
Id: "Stmt",
|
||||
NTType: 4,
|
||||
Index: 24,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return X[0], nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Stmt : SubGraphStmt << X[0], nil >>`,
|
||||
Id: "Stmt",
|
||||
NTType: 4,
|
||||
Index: 25,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return X[0], nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AttrStmt : graphx AttrList << ast.NewGraphAttrs(X[1]) >>`,
|
||||
Id: "AttrStmt",
|
||||
NTType: 5,
|
||||
Index: 26,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewGraphAttrs(X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AttrStmt : node AttrList << ast.NewNodeAttrs(X[1]) >>`,
|
||||
Id: "AttrStmt",
|
||||
NTType: 5,
|
||||
Index: 27,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewNodeAttrs(X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AttrStmt : edge AttrList << ast.NewEdgeAttrs(X[1]) >>`,
|
||||
Id: "AttrStmt",
|
||||
NTType: 5,
|
||||
Index: 28,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewEdgeAttrs(X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AttrList : "[" "]" << ast.NewAttrList(nil) >>`,
|
||||
Id: "AttrList",
|
||||
NTType: 6,
|
||||
Index: 29,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewAttrList(nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AttrList : "[" AList "]" << ast.NewAttrList(X[1]) >>`,
|
||||
Id: "AttrList",
|
||||
NTType: 6,
|
||||
Index: 30,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewAttrList(X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AttrList : AttrList "[" "]" << ast.AppendAttrList(X[0], nil) >>`,
|
||||
Id: "AttrList",
|
||||
NTType: 6,
|
||||
Index: 31,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.AppendAttrList(X[0], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AttrList : AttrList "[" AList "]" << ast.AppendAttrList(X[0], X[2]) >>`,
|
||||
Id: "AttrList",
|
||||
NTType: 6,
|
||||
Index: 32,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.AppendAttrList(X[0], X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AList : Attr << ast.NewAList(X[0]) >>`,
|
||||
Id: "AList",
|
||||
NTType: 7,
|
||||
Index: 33,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewAList(X[0])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AList : AList Attr << ast.AppendAList(X[0], X[1]) >>`,
|
||||
Id: "AList",
|
||||
NTType: 7,
|
||||
Index: 34,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.AppendAList(X[0], X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `AList : AList "," Attr << ast.AppendAList(X[0], X[2]) >>`,
|
||||
Id: "AList",
|
||||
NTType: 7,
|
||||
Index: 35,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.AppendAList(X[0], X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Attr : Id << ast.NewAttr(X[0], nil) >>`,
|
||||
Id: "Attr",
|
||||
NTType: 8,
|
||||
Index: 36,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewAttr(X[0], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Attr : Id "=" Id << ast.NewAttr(X[0], X[2]) >>`,
|
||||
Id: "Attr",
|
||||
NTType: 8,
|
||||
Index: 37,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewAttr(X[0], X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeStmt : NodeId EdgeRHS << ast.NewEdgeStmt(X[0], X[1], nil) >>`,
|
||||
Id: "EdgeStmt",
|
||||
NTType: 9,
|
||||
Index: 38,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewEdgeStmt(X[0], X[1], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeStmt : NodeId EdgeRHS AttrList << ast.NewEdgeStmt(X[0], X[1], X[2]) >>`,
|
||||
Id: "EdgeStmt",
|
||||
NTType: 9,
|
||||
Index: 39,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewEdgeStmt(X[0], X[1], X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeStmt : SubGraphStmt EdgeRHS << ast.NewEdgeStmt(X[0], X[1], nil) >>`,
|
||||
Id: "EdgeStmt",
|
||||
NTType: 9,
|
||||
Index: 40,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewEdgeStmt(X[0], X[1], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeStmt : SubGraphStmt EdgeRHS AttrList << ast.NewEdgeStmt(X[0], X[1], X[2]) >>`,
|
||||
Id: "EdgeStmt",
|
||||
NTType: 9,
|
||||
Index: 41,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewEdgeStmt(X[0], X[1], X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeRHS : EdgeOp NodeId << ast.NewEdgeRHS(X[0], X[1]) >>`,
|
||||
Id: "EdgeRHS",
|
||||
NTType: 10,
|
||||
Index: 42,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewEdgeRHS(X[0], X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeRHS : EdgeOp SubGraphStmt << ast.NewEdgeRHS(X[0], X[1]) >>`,
|
||||
Id: "EdgeRHS",
|
||||
NTType: 10,
|
||||
Index: 43,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewEdgeRHS(X[0], X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeRHS : EdgeRHS EdgeOp NodeId << ast.AppendEdgeRHS(X[0], X[1], X[2]) >>`,
|
||||
Id: "EdgeRHS",
|
||||
NTType: 10,
|
||||
Index: 44,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.AppendEdgeRHS(X[0], X[1], X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeRHS : EdgeRHS EdgeOp SubGraphStmt << ast.AppendEdgeRHS(X[0], X[1], X[2]) >>`,
|
||||
Id: "EdgeRHS",
|
||||
NTType: 10,
|
||||
Index: 45,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.AppendEdgeRHS(X[0], X[1], X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `NodeStmt : NodeId << ast.NewNodeStmt(X[0], nil) >>`,
|
||||
Id: "NodeStmt",
|
||||
NTType: 11,
|
||||
Index: 46,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewNodeStmt(X[0], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `NodeStmt : NodeId AttrList << ast.NewNodeStmt(X[0], X[1]) >>`,
|
||||
Id: "NodeStmt",
|
||||
NTType: 11,
|
||||
Index: 47,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewNodeStmt(X[0], X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `NodeId : Id << ast.NewNodeID(X[0], nil) >>`,
|
||||
Id: "NodeId",
|
||||
NTType: 12,
|
||||
Index: 48,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewNodeID(X[0], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `NodeId : Id Port << ast.NewNodeID(X[0], X[1]) >>`,
|
||||
Id: "NodeId",
|
||||
NTType: 12,
|
||||
Index: 49,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewNodeID(X[0], X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Port : ":" Id << ast.NewPort(X[1], nil), nil >>`,
|
||||
Id: "Port",
|
||||
NTType: 13,
|
||||
Index: 50,
|
||||
NumSymbols: 2,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewPort(X[1], nil), nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Port : ":" Id ":" Id << ast.NewPort(X[1], X[3]), nil >>`,
|
||||
Id: "Port",
|
||||
NTType: 13,
|
||||
Index: 51,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewPort(X[1], X[3]), nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `SubGraphStmt : "{" StmtList "}" << ast.NewSubGraph(nil, X[1]) >>`,
|
||||
Id: "SubGraphStmt",
|
||||
NTType: 14,
|
||||
Index: 52,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewSubGraph(nil, X[1])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `SubGraphStmt : subgraph "{" StmtList "}" << ast.NewSubGraph(nil, X[2]) >>`,
|
||||
Id: "SubGraphStmt",
|
||||
NTType: 14,
|
||||
Index: 53,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewSubGraph(nil, X[2])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `SubGraphStmt : subgraph Id "{" StmtList "}" << ast.NewSubGraph(X[1], X[3]) >>`,
|
||||
Id: "SubGraphStmt",
|
||||
NTType: 14,
|
||||
Index: 54,
|
||||
NumSymbols: 5,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewSubGraph(X[1], X[3])
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `SubGraphStmt : subgraph "{" "}" << ast.NewSubGraph(nil, nil) >>`,
|
||||
Id: "SubGraphStmt",
|
||||
NTType: 14,
|
||||
Index: 55,
|
||||
NumSymbols: 3,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewSubGraph(nil, nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `SubGraphStmt : subgraph Id "{" "}" << ast.NewSubGraph(X[1], nil) >>`,
|
||||
Id: "SubGraphStmt",
|
||||
NTType: 14,
|
||||
Index: 56,
|
||||
NumSymbols: 4,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewSubGraph(X[1], nil)
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeOp : "->" << ast.DIRECTED, nil >>`,
|
||||
Id: "EdgeOp",
|
||||
NTType: 15,
|
||||
Index: 57,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.DIRECTED, nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `EdgeOp : "--" << ast.UNDIRECTED, nil >>`,
|
||||
Id: "EdgeOp",
|
||||
NTType: 15,
|
||||
Index: 58,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.UNDIRECTED, nil
|
||||
},
|
||||
},
|
||||
ProdTabEntry{
|
||||
String: `Id : id << ast.NewID(X[0]) >>`,
|
||||
Id: "Id",
|
||||
NTType: 16,
|
||||
Index: 59,
|
||||
NumSymbols: 1,
|
||||
ReduceFunc: func(X []Attrib) (Attrib, error) {
|
||||
return ast.NewID(X[0])
|
||||
},
|
||||
},
|
||||
}
|
104
vendor/github.com/awalterschulze/gographviz/internal/token/token.go
generated
vendored
Normal file
104
vendor/github.com/awalterschulze/gographviz/internal/token/token.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
// Code generated by gocc; DO NOT EDIT.
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Type
|
||||
Lit []byte
|
||||
Pos
|
||||
}
|
||||
|
||||
type Type int
|
||||
|
||||
const (
|
||||
INVALID Type = iota
|
||||
EOF
|
||||
)
|
||||
|
||||
type Pos struct {
|
||||
Offset int
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
func (p Pos) String() string {
|
||||
return fmt.Sprintf("Pos(offset=%d, line=%d, column=%d)", p.Offset, p.Line, p.Column)
|
||||
}
|
||||
|
||||
type TokenMap struct {
|
||||
typeMap []string
|
||||
idMap map[string]Type
|
||||
}
|
||||
|
||||
func (m TokenMap) Id(tok Type) string {
|
||||
if int(tok) < len(m.typeMap) {
|
||||
return m.typeMap[tok]
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func (m TokenMap) Type(tok string) Type {
|
||||
if typ, exist := m.idMap[tok]; exist {
|
||||
return typ
|
||||
}
|
||||
return INVALID
|
||||
}
|
||||
|
||||
func (m TokenMap) TokenString(tok *Token) string {
|
||||
//TODO: refactor to print pos & token string properly
|
||||
return fmt.Sprintf("%s(%d,%s)", m.Id(tok.Type), tok.Type, tok.Lit)
|
||||
}
|
||||
|
||||
func (m TokenMap) StringType(typ Type) string {
|
||||
return fmt.Sprintf("%s(%d)", m.Id(typ), typ)
|
||||
}
|
||||
|
||||
var TokMap = TokenMap{
|
||||
typeMap: []string{
|
||||
"INVALID",
|
||||
"$",
|
||||
"graphx",
|
||||
"{",
|
||||
"}",
|
||||
"strict",
|
||||
"digraph",
|
||||
";",
|
||||
"=",
|
||||
"node",
|
||||
"edge",
|
||||
"[",
|
||||
"]",
|
||||
",",
|
||||
":",
|
||||
"subgraph",
|
||||
"->",
|
||||
"--",
|
||||
"id",
|
||||
},
|
||||
|
||||
idMap: map[string]Type{
|
||||
"INVALID": 0,
|
||||
"$": 1,
|
||||
"graphx": 2,
|
||||
"{": 3,
|
||||
"}": 4,
|
||||
"strict": 5,
|
||||
"digraph": 6,
|
||||
";": 7,
|
||||
"=": 8,
|
||||
"node": 9,
|
||||
"edge": 10,
|
||||
"[": 11,
|
||||
"]": 12,
|
||||
",": 13,
|
||||
":": 14,
|
||||
"subgraph": 15,
|
||||
"->": 16,
|
||||
"--": 17,
|
||||
"id": 18,
|
||||
},
|
||||
}
|
78
vendor/github.com/awalterschulze/gographviz/nodes.go
generated
vendored
Normal file
78
vendor/github.com/awalterschulze/gographviz/nodes.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Node represents a Node.
|
||||
type Node struct {
|
||||
Name string
|
||||
Attrs Attrs
|
||||
}
|
||||
|
||||
// Nodes represents a set of Nodes.
|
||||
type Nodes struct {
|
||||
Lookup map[string]*Node
|
||||
Nodes []*Node
|
||||
}
|
||||
|
||||
// NewNodes creates a new set of Nodes.
|
||||
func NewNodes() *Nodes {
|
||||
return &Nodes{make(map[string]*Node), make([]*Node, 0)}
|
||||
}
|
||||
|
||||
// Remove removes a node
|
||||
func (nodes *Nodes) Remove(name string) error {
|
||||
for i := 0; i < len(nodes.Nodes); i++ {
|
||||
if nodes.Nodes[i].Name != name {
|
||||
continue
|
||||
}
|
||||
|
||||
nodes.Nodes = append(nodes.Nodes[:i], nodes.Nodes[i+1:]...)
|
||||
delete(nodes.Lookup, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("node %s not found", name)
|
||||
}
|
||||
|
||||
// Add adds a Node to the set of Nodes, extending the attributes of an already existing node.
|
||||
func (nodes *Nodes) Add(node *Node) {
|
||||
n, ok := nodes.Lookup[node.Name]
|
||||
if ok {
|
||||
n.Attrs.Extend(node.Attrs)
|
||||
return
|
||||
}
|
||||
nodes.Lookup[node.Name] = node
|
||||
nodes.Nodes = append(nodes.Nodes, node)
|
||||
}
|
||||
|
||||
// Sorted returns a sorted list of nodes.
|
||||
func (nodes Nodes) Sorted() []*Node {
|
||||
keys := make([]string, 0, len(nodes.Lookup))
|
||||
for key := range nodes.Lookup {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
nodeList := make([]*Node, len(keys))
|
||||
for i := range keys {
|
||||
nodeList[i] = nodes.Lookup[keys[i]]
|
||||
}
|
||||
return nodeList
|
||||
}
|
64
vendor/github.com/awalterschulze/gographviz/relations.go
generated
vendored
Normal file
64
vendor/github.com/awalterschulze/gographviz/relations.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Relations represents the relations between graphs and nodes.
|
||||
// Each node belongs the main graph or a subgraph.
|
||||
type Relations struct {
|
||||
ParentToChildren map[string]map[string]bool
|
||||
ChildToParents map[string]map[string]bool
|
||||
}
|
||||
|
||||
// NewRelations creates an empty set of relations.
|
||||
func NewRelations() *Relations {
|
||||
return &Relations{make(map[string]map[string]bool), make(map[string]map[string]bool)}
|
||||
}
|
||||
|
||||
// Add adds a node to a parent graph.
|
||||
func (relations *Relations) Add(parent string, child string) {
|
||||
if _, ok := relations.ParentToChildren[parent]; !ok {
|
||||
relations.ParentToChildren[parent] = make(map[string]bool)
|
||||
}
|
||||
relations.ParentToChildren[parent][child] = true
|
||||
if _, ok := relations.ChildToParents[child]; !ok {
|
||||
relations.ChildToParents[child] = make(map[string]bool)
|
||||
}
|
||||
relations.ChildToParents[child][parent] = true
|
||||
}
|
||||
|
||||
// Remove removes relation
|
||||
func (relations *Relations) Remove(parent string, child string) {
|
||||
if _, ok := relations.ParentToChildren[parent]; ok {
|
||||
delete(relations.ParentToChildren[parent], child)
|
||||
}
|
||||
|
||||
if _, ok := relations.ChildToParents[child]; ok {
|
||||
delete(relations.ChildToParents[child], parent)
|
||||
}
|
||||
}
|
||||
|
||||
// SortedChildren returns a list of sorted children of the given parent graph.
|
||||
func (relations *Relations) SortedChildren(parent string) []string {
|
||||
keys := make([]string, 0)
|
||||
for key := range relations.ParentToChildren[parent] {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
69
vendor/github.com/awalterschulze/gographviz/subgraphs.go
generated
vendored
Normal file
69
vendor/github.com/awalterschulze/gographviz/subgraphs.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// SubGraph represents a Subgraph.
|
||||
type SubGraph struct {
|
||||
Attrs Attrs
|
||||
Name string
|
||||
}
|
||||
|
||||
// NewSubGraph creates a new Subgraph.
|
||||
func NewSubGraph(name string) *SubGraph {
|
||||
return &SubGraph{
|
||||
Attrs: make(Attrs),
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// SubGraphs represents a set of SubGraphs.
|
||||
type SubGraphs struct {
|
||||
SubGraphs map[string]*SubGraph
|
||||
}
|
||||
|
||||
// NewSubGraphs creates a new blank set of SubGraphs.
|
||||
func NewSubGraphs() *SubGraphs {
|
||||
return &SubGraphs{make(map[string]*SubGraph)}
|
||||
}
|
||||
|
||||
// Add adds and creates a new Subgraph to the set of SubGraphs.
|
||||
func (subgraphs *SubGraphs) Add(name string) {
|
||||
if _, ok := subgraphs.SubGraphs[name]; !ok {
|
||||
subgraphs.SubGraphs[name] = NewSubGraph(name)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes a subgraph
|
||||
func (subgraphs *SubGraphs) Remove(name string) {
|
||||
delete(subgraphs.SubGraphs, name)
|
||||
}
|
||||
|
||||
// Sorted returns a sorted list of SubGraphs.
|
||||
func (subgraphs *SubGraphs) Sorted() []*SubGraph {
|
||||
keys := make([]string, 0)
|
||||
for key := range subgraphs.SubGraphs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
s := make([]*SubGraph, len(keys))
|
||||
for i, key := range keys {
|
||||
s[i] = subgraphs.SubGraphs[key]
|
||||
}
|
||||
return s
|
||||
}
|
172
vendor/github.com/awalterschulze/gographviz/write.go
generated
vendored
Normal file
172
vendor/github.com/awalterschulze/gographviz/write.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
//Copyright 2013 GoGraphviz Authors
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
package gographviz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/awalterschulze/gographviz/ast"
|
||||
)
|
||||
|
||||
type writer struct {
|
||||
*Graph
|
||||
writtenLocations map[string]bool
|
||||
}
|
||||
|
||||
func newWriter(g *Graph) *writer {
|
||||
return &writer{g, make(map[string]bool)}
|
||||
}
|
||||
|
||||
func appendAttrs(list ast.StmtList, attrs Attrs) ast.StmtList {
|
||||
for _, name := range attrs.sortedNames() {
|
||||
stmt := &ast.Attr{
|
||||
Field: ast.ID(name),
|
||||
Value: ast.ID(attrs[name]),
|
||||
}
|
||||
list = append(list, stmt)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (w *writer) newSubGraph(name string) (*ast.SubGraph, error) {
|
||||
sub := w.SubGraphs.SubGraphs[name]
|
||||
w.writtenLocations[sub.Name] = true
|
||||
s := &ast.SubGraph{}
|
||||
s.ID = ast.ID(sub.Name)
|
||||
s.StmtList = appendAttrs(s.StmtList, sub.Attrs)
|
||||
children := w.Relations.SortedChildren(name)
|
||||
for _, child := range children {
|
||||
if w.IsNode(child) {
|
||||
s.StmtList = append(s.StmtList, w.newNodeStmt(child))
|
||||
} else if w.IsSubGraph(child) {
|
||||
subgraph, err := w.newSubGraph(child)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.StmtList = append(s.StmtList, subgraph)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%v is not a node or a subgraph", child)
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (w *writer) newNodeID(name string, port string) *ast.NodeID {
|
||||
node := w.Nodes.Lookup[name]
|
||||
return ast.MakeNodeID(node.Name, port)
|
||||
}
|
||||
|
||||
func (w *writer) newNodeStmt(name string) *ast.NodeStmt {
|
||||
node := w.Nodes.Lookup[name]
|
||||
id := ast.MakeNodeID(node.Name, "")
|
||||
w.writtenLocations[node.Name] = true
|
||||
return &ast.NodeStmt{
|
||||
NodeID: id,
|
||||
Attrs: ast.PutMap(node.Attrs.toMap()),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) newLocation(name string, port string) (ast.Location, error) {
|
||||
if w.IsNode(name) {
|
||||
return w.newNodeID(name, port), nil
|
||||
} else if w.isClusterSubGraph(name) {
|
||||
if len(port) != 0 {
|
||||
return nil, fmt.Errorf("subgraph cannot have a port: %v", port)
|
||||
}
|
||||
return ast.MakeNodeID(name, port), nil
|
||||
} else if w.IsSubGraph(name) {
|
||||
if len(port) != 0 {
|
||||
return nil, fmt.Errorf("subgraph cannot have a port: %v", port)
|
||||
}
|
||||
return w.newSubGraph(name)
|
||||
}
|
||||
return nil, fmt.Errorf("%v is not a node or a subgraph", name)
|
||||
}
|
||||
|
||||
func (w *writer) newEdgeStmt(edge *Edge) (*ast.EdgeStmt, error) {
|
||||
src, err := w.newLocation(edge.Src, edge.SrcPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dst, err := w.newLocation(edge.Dst, edge.DstPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt := &ast.EdgeStmt{
|
||||
Source: src,
|
||||
EdgeRHS: ast.EdgeRHS{
|
||||
&ast.EdgeRH{
|
||||
Op: ast.EdgeOp(edge.Dir),
|
||||
Destination: dst,
|
||||
},
|
||||
},
|
||||
Attrs: ast.PutMap(edge.Attrs.toMap()),
|
||||
}
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
func (w *writer) Write() (*ast.Graph, error) {
|
||||
t := &ast.Graph{}
|
||||
t.Strict = w.Strict
|
||||
t.Type = ast.GraphType(w.Directed)
|
||||
t.ID = ast.ID(w.Name)
|
||||
|
||||
t.StmtList = appendAttrs(t.StmtList, w.Attrs)
|
||||
|
||||
for _, edge := range w.Edges.Edges {
|
||||
e, err := w.newEdgeStmt(edge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.StmtList = append(t.StmtList, e)
|
||||
}
|
||||
|
||||
subGraphs := w.SubGraphs.Sorted()
|
||||
for _, s := range subGraphs {
|
||||
if _, ok := w.writtenLocations[s.Name]; !ok {
|
||||
if _, ok := w.Relations.ParentToChildren[w.Name][s.Name]; ok {
|
||||
s, err := w.newSubGraph(s.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.StmtList = append(t.StmtList, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes := w.Nodes.Sorted()
|
||||
for _, n := range nodes {
|
||||
if _, ok := w.writtenLocations[n.Name]; !ok {
|
||||
t.StmtList = append(t.StmtList, w.newNodeStmt(n.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// WriteAst creates an Abstract Syntrax Tree from the Graph.
|
||||
func (g *Graph) WriteAst() (*ast.Graph, error) {
|
||||
w := newWriter(g)
|
||||
return w.Write()
|
||||
}
|
||||
|
||||
// String returns a DOT string representing the Graph.
|
||||
func (g *Graph) String() string {
|
||||
w, err := g.WriteAst()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("error: %v", err)
|
||||
}
|
||||
return w.String()
|
||||
}
|
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (C) 2013 Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
@ -0,0 +1,316 @@
|
||||
// Package quantile computes approximate quantiles over an unbounded data
|
||||
// stream within low memory and CPU bounds.
|
||||
//
|
||||
// A small amount of accuracy is traded to achieve the above properties.
|
||||
//
|
||||
// Multiple streams can be merged before calling Query to generate a single set
|
||||
// of results. This is meaningful when the streams represent the same type of
|
||||
// data. See Merge and Samples.
|
||||
//
|
||||
// For more detailed information about the algorithm used, see:
|
||||
//
|
||||
// Effective Computation of Biased Quantiles over Data Streams
|
||||
//
|
||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sample holds an observed value and meta information for compression. JSON
|
||||
// tags have been added for convenience.
|
||||
type Sample struct {
|
||||
Value float64 `json:",string"`
|
||||
Width float64 `json:",string"`
|
||||
Delta float64 `json:",string"`
|
||||
}
|
||||
|
||||
// Samples represents a slice of samples. It implements sort.Interface.
|
||||
type Samples []Sample
|
||||
|
||||
func (a Samples) Len() int { return len(a) }
|
||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type invariant func(s *stream, r float64) float64
|
||||
|
||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the lower ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewLowBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * r
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the higher ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewHighBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * (s.n - r)
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||
// space and computation time. The targets map maps the desired quantiles to
|
||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||
// is guaranteed to be within (Quantile±Epsilon).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||
func NewTargeted(targetMap map[float64]float64) *Stream {
|
||||
// Convert map to slice to avoid slow iterations on a map.
|
||||
// ƒ is called on the hot path, so converting the map to a slice
|
||||
// beforehand results in significant CPU savings.
|
||||
targets := targetMapToSlice(targetMap)
|
||||
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
var m = math.MaxFloat64
|
||||
var f float64
|
||||
for _, t := range targets {
|
||||
if t.quantile*s.n <= r {
|
||||
f = (2 * t.epsilon * r) / t.quantile
|
||||
} else {
|
||||
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
|
||||
}
|
||||
if f < m {
|
||||
m = f
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
type target struct {
|
||||
quantile float64
|
||||
epsilon float64
|
||||
}
|
||||
|
||||
func targetMapToSlice(targetMap map[float64]float64) []target {
|
||||
targets := make([]target, 0, len(targetMap))
|
||||
|
||||
for quantile, epsilon := range targetMap {
|
||||
t := target{
|
||||
quantile: quantile,
|
||||
epsilon: epsilon,
|
||||
}
|
||||
targets = append(targets, t)
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||
// design. Take care when using across multiple goroutines.
|
||||
type Stream struct {
|
||||
*stream
|
||||
b Samples
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newStream(ƒ invariant) *Stream {
|
||||
x := &stream{ƒ: ƒ}
|
||||
return &Stream{x, make(Samples, 0, 500), true}
|
||||
}
|
||||
|
||||
// Insert inserts v into the stream.
|
||||
func (s *Stream) Insert(v float64) {
|
||||
s.insert(Sample{Value: v, Width: 1})
|
||||
}
|
||||
|
||||
func (s *Stream) insert(sample Sample) {
|
||||
s.b = append(s.b, sample)
|
||||
s.sorted = false
|
||||
if len(s.b) == cap(s.b) {
|
||||
s.flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Query returns the computed qth percentiles value. If s was created with
|
||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||
// will return an unspecified result.
|
||||
func (s *Stream) Query(q float64) float64 {
|
||||
if !s.flushed() {
|
||||
// Fast path when there hasn't been enough data for a flush;
|
||||
// this also yields better accuracy for small sets of data.
|
||||
l := len(s.b)
|
||||
if l == 0 {
|
||||
return 0
|
||||
}
|
||||
i := int(math.Ceil(float64(l) * q))
|
||||
if i > 0 {
|
||||
i -= 1
|
||||
}
|
||||
s.maybeSort()
|
||||
return s.b[i].Value
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.query(q)
|
||||
}
|
||||
|
||||
// Merge merges samples into the underlying streams samples. This is handy when
|
||||
// merging multiple streams from separate threads, database shards, etc.
|
||||
//
|
||||
// ATTENTION: This method is broken and does not yield correct results. The
|
||||
// underlying algorithm is not capable of merging streams correctly.
|
||||
func (s *Stream) Merge(samples Samples) {
|
||||
sort.Sort(samples)
|
||||
s.stream.merge(samples)
|
||||
}
|
||||
|
||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||
func (s *Stream) Reset() {
|
||||
s.stream.reset()
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
// Samples returns stream samples held by s.
|
||||
func (s *Stream) Samples() Samples {
|
||||
if !s.flushed() {
|
||||
return s.b
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.samples()
|
||||
}
|
||||
|
||||
// Count returns the total number of samples observed in the stream
|
||||
// since initialization.
|
||||
func (s *Stream) Count() int {
|
||||
return len(s.b) + s.stream.count()
|
||||
}
|
||||
|
||||
func (s *Stream) flush() {
|
||||
s.maybeSort()
|
||||
s.stream.merge(s.b)
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
func (s *Stream) maybeSort() {
|
||||
if !s.sorted {
|
||||
s.sorted = true
|
||||
sort.Sort(s.b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) flushed() bool {
|
||||
return len(s.stream.l) > 0
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
n float64
|
||||
l []Sample
|
||||
ƒ invariant
|
||||
}
|
||||
|
||||
func (s *stream) reset() {
|
||||
s.l = s.l[:0]
|
||||
s.n = 0
|
||||
}
|
||||
|
||||
func (s *stream) insert(v float64) {
|
||||
s.merge(Samples{{v, 1, 0}})
|
||||
}
|
||||
|
||||
func (s *stream) merge(samples Samples) {
|
||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||
// whole summaries. The paper doesn't mention merging summaries at
|
||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||
// do merges properly.
|
||||
var r float64
|
||||
i := 0
|
||||
for _, sample := range samples {
|
||||
for ; i < len(s.l); i++ {
|
||||
c := s.l[i]
|
||||
if c.Value > sample.Value {
|
||||
// Insert at position i.
|
||||
s.l = append(s.l, Sample{})
|
||||
copy(s.l[i+1:], s.l[i:])
|
||||
s.l[i] = Sample{
|
||||
sample.Value,
|
||||
sample.Width,
|
||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||
// TODO(beorn7): How to calculate delta correctly?
|
||||
}
|
||||
i++
|
||||
goto inserted
|
||||
}
|
||||
r += c.Width
|
||||
}
|
||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||
i++
|
||||
inserted:
|
||||
s.n += sample.Width
|
||||
r += sample.Width
|
||||
}
|
||||
s.compress()
|
||||
}
|
||||
|
||||
func (s *stream) count() int {
|
||||
return int(s.n)
|
||||
}
|
||||
|
||||
func (s *stream) query(q float64) float64 {
|
||||
t := math.Ceil(q * s.n)
|
||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||
p := s.l[0]
|
||||
var r float64
|
||||
for _, c := range s.l[1:] {
|
||||
r += p.Width
|
||||
if r+c.Width+c.Delta > t {
|
||||
return p.Value
|
||||
}
|
||||
p = c
|
||||
}
|
||||
return p.Value
|
||||
}
|
||||
|
||||
func (s *stream) compress() {
|
||||
if len(s.l) < 2 {
|
||||
return
|
||||
}
|
||||
x := s.l[len(s.l)-1]
|
||||
xi := len(s.l) - 1
|
||||
r := s.n - 1 - x.Width
|
||||
|
||||
for i := len(s.l) - 2; i >= 0; i-- {
|
||||
c := s.l[i]
|
||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||
x.Width += c.Width
|
||||
s.l[xi] = x
|
||||
// Remove element at i.
|
||||
copy(s.l[i:], s.l[i+1:])
|
||||
s.l = s.l[:len(s.l)-1]
|
||||
xi -= 1
|
||||
} else {
|
||||
x = c
|
||||
xi = i
|
||||
}
|
||||
r -= c.Width
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) samples() Samples {
|
||||
samples := make(Samples, len(s.l))
|
||||
copy(samples, s.l)
|
||||
return samples
|
||||
}
|
191
vendor/github.com/coreos/go-iptables/LICENSE
generated
vendored
Normal file
191
vendor/github.com/coreos/go-iptables/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
5
vendor/github.com/coreos/go-iptables/NOTICE
generated
vendored
Normal file
5
vendor/github.com/coreos/go-iptables/NOTICE
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
CoreOS Project
|
||||
Copyright 2018 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
530
vendor/github.com/coreos/go-iptables/iptables/iptables.go
generated
vendored
Normal file
530
vendor/github.com/coreos/go-iptables/iptables/iptables.go
generated
vendored
Normal file
@ -0,0 +1,530 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Adds the output of stderr to exec.ExitError
|
||||
type Error struct {
|
||||
exec.ExitError
|
||||
cmd exec.Cmd
|
||||
msg string
|
||||
exitStatus *int //for overriding
|
||||
}
|
||||
|
||||
func (e *Error) ExitStatus() int {
|
||||
if e.exitStatus != nil {
|
||||
return *e.exitStatus
|
||||
}
|
||||
return e.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("running %v: exit status %v: %v", e.cmd.Args, e.ExitStatus(), e.msg)
|
||||
}
|
||||
|
||||
// IsNotExist returns true if the error is due to the chain or rule not existing
|
||||
func (e *Error) IsNotExist() bool {
|
||||
return e.ExitStatus() == 1 &&
|
||||
(e.msg == "iptables: Bad rule (does a matching rule exist in that chain?).\n" ||
|
||||
e.msg == "iptables: No chain/target/match by that name.\n")
|
||||
}
|
||||
|
||||
// Protocol to differentiate between IPv4 and IPv6
|
||||
type Protocol byte
|
||||
|
||||
const (
|
||||
ProtocolIPv4 Protocol = iota
|
||||
ProtocolIPv6
|
||||
)
|
||||
|
||||
type IPTables struct {
|
||||
path string
|
||||
proto Protocol
|
||||
hasCheck bool
|
||||
hasWait bool
|
||||
hasRandomFully bool
|
||||
v1 int
|
||||
v2 int
|
||||
v3 int
|
||||
mode string // the underlying iptables operating mode, e.g. nf_tables
|
||||
}
|
||||
|
||||
// New creates a new IPTables.
|
||||
// For backwards compatibility, this always uses IPv4, i.e. "iptables".
|
||||
func New() (*IPTables, error) {
|
||||
return NewWithProtocol(ProtocolIPv4)
|
||||
}
|
||||
|
||||
// New creates a new IPTables for the given proto.
|
||||
// The proto will determine which command is used, either "iptables" or "ip6tables".
|
||||
func NewWithProtocol(proto Protocol) (*IPTables, error) {
|
||||
path, err := exec.LookPath(getIptablesCommand(proto))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vstring, err := getIptablesVersionString(path)
|
||||
v1, v2, v3, mode, err := extractIptablesVersion(vstring)
|
||||
|
||||
checkPresent, waitPresent, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3)
|
||||
|
||||
ipt := IPTables{
|
||||
path: path,
|
||||
proto: proto,
|
||||
hasCheck: checkPresent,
|
||||
hasWait: waitPresent,
|
||||
hasRandomFully: randomFullyPresent,
|
||||
v1: v1,
|
||||
v2: v2,
|
||||
v3: v3,
|
||||
mode: mode,
|
||||
}
|
||||
return &ipt, nil
|
||||
}
|
||||
|
||||
// Proto returns the protocol used by this IPTables.
|
||||
func (ipt *IPTables) Proto() Protocol {
|
||||
return ipt.proto
|
||||
}
|
||||
|
||||
// Exists checks if given rulespec in specified table/chain exists
|
||||
func (ipt *IPTables) Exists(table, chain string, rulespec ...string) (bool, error) {
|
||||
if !ipt.hasCheck {
|
||||
return ipt.existsForOldIptables(table, chain, rulespec)
|
||||
|
||||
}
|
||||
cmd := append([]string{"-t", table, "-C", chain}, rulespec...)
|
||||
err := ipt.run(cmd...)
|
||||
eerr, eok := err.(*Error)
|
||||
switch {
|
||||
case err == nil:
|
||||
return true, nil
|
||||
case eok && eerr.ExitStatus() == 1:
|
||||
return false, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert inserts rulespec to specified table/chain (in specified pos)
|
||||
func (ipt *IPTables) Insert(table, chain string, pos int, rulespec ...string) error {
|
||||
cmd := append([]string{"-t", table, "-I", chain, strconv.Itoa(pos)}, rulespec...)
|
||||
return ipt.run(cmd...)
|
||||
}
|
||||
|
||||
// Append appends rulespec to specified table/chain
|
||||
func (ipt *IPTables) Append(table, chain string, rulespec ...string) error {
|
||||
cmd := append([]string{"-t", table, "-A", chain}, rulespec...)
|
||||
return ipt.run(cmd...)
|
||||
}
|
||||
|
||||
// AppendUnique acts like Append except that it won't add a duplicate
|
||||
func (ipt *IPTables) AppendUnique(table, chain string, rulespec ...string) error {
|
||||
exists, err := ipt.Exists(table, chain, rulespec...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return ipt.Append(table, chain, rulespec...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes rulespec in specified table/chain
|
||||
func (ipt *IPTables) Delete(table, chain string, rulespec ...string) error {
|
||||
cmd := append([]string{"-t", table, "-D", chain}, rulespec...)
|
||||
return ipt.run(cmd...)
|
||||
}
|
||||
|
||||
// List rules in specified table/chain
|
||||
func (ipt *IPTables) List(table, chain string) ([]string, error) {
|
||||
args := []string{"-t", table, "-S", chain}
|
||||
return ipt.executeList(args)
|
||||
}
|
||||
|
||||
// List rules (with counters) in specified table/chain
|
||||
func (ipt *IPTables) ListWithCounters(table, chain string) ([]string, error) {
|
||||
args := []string{"-t", table, "-v", "-S", chain}
|
||||
return ipt.executeList(args)
|
||||
}
|
||||
|
||||
// ListChains returns a slice containing the name of each chain in the specified table.
|
||||
func (ipt *IPTables) ListChains(table string) ([]string, error) {
|
||||
args := []string{"-t", table, "-S"}
|
||||
|
||||
result, err := ipt.executeList(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Iterate over rules to find all default (-P) and user-specified (-N) chains.
|
||||
// Chains definition always come before rules.
|
||||
// Format is the following:
|
||||
// -P OUTPUT ACCEPT
|
||||
// -N Custom
|
||||
var chains []string
|
||||
for _, val := range result {
|
||||
if strings.HasPrefix(val, "-P") || strings.HasPrefix(val, "-N") {
|
||||
chains = append(chains, strings.Fields(val)[1])
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return chains, nil
|
||||
}
|
||||
|
||||
// Stats lists rules including the byte and packet counts
|
||||
func (ipt *IPTables) Stats(table, chain string) ([][]string, error) {
|
||||
args := []string{"-t", table, "-L", chain, "-n", "-v", "-x"}
|
||||
lines, err := ipt.executeList(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appendSubnet := func(addr string) string {
|
||||
if strings.IndexByte(addr, byte('/')) < 0 {
|
||||
if strings.IndexByte(addr, '.') < 0 {
|
||||
return addr + "/128"
|
||||
}
|
||||
return addr + "/32"
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
ipv6 := ipt.proto == ProtocolIPv6
|
||||
|
||||
rows := [][]string{}
|
||||
for i, line := range lines {
|
||||
// Skip over chain name and field header
|
||||
if i < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Fields:
|
||||
// 0=pkts 1=bytes 2=target 3=prot 4=opt 5=in 6=out 7=source 8=destination 9=options
|
||||
line = strings.TrimSpace(line)
|
||||
fields := strings.Fields(line)
|
||||
|
||||
// The ip6tables verbose output cannot be naively split due to the default "opt"
|
||||
// field containing 2 single spaces.
|
||||
if ipv6 {
|
||||
// Check if field 6 is "opt" or "source" address
|
||||
dest := fields[6]
|
||||
ip, _, _ := net.ParseCIDR(dest)
|
||||
if ip == nil {
|
||||
ip = net.ParseIP(dest)
|
||||
}
|
||||
|
||||
// If we detected a CIDR or IP, the "opt" field is empty.. insert it.
|
||||
if ip != nil {
|
||||
f := []string{}
|
||||
f = append(f, fields[:4]...)
|
||||
f = append(f, " ") // Empty "opt" field for ip6tables
|
||||
f = append(f, fields[4:]...)
|
||||
fields = f
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust "source" and "destination" to include netmask, to match regular
|
||||
// List output
|
||||
fields[7] = appendSubnet(fields[7])
|
||||
fields[8] = appendSubnet(fields[8])
|
||||
|
||||
// Combine "options" fields 9... into a single space-delimited field.
|
||||
options := fields[9:]
|
||||
fields = fields[:9]
|
||||
fields = append(fields, strings.Join(options, " "))
|
||||
rows = append(rows, fields)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (ipt *IPTables) executeList(args []string) ([]string, error) {
|
||||
var stdout bytes.Buffer
|
||||
if err := ipt.runWithOutput(args, &stdout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rules := strings.Split(stdout.String(), "\n")
|
||||
|
||||
// strip trailing newline
|
||||
if len(rules) > 0 && rules[len(rules)-1] == "" {
|
||||
rules = rules[:len(rules)-1]
|
||||
}
|
||||
|
||||
// nftables mode doesn't return an error code when listing a non-existent
|
||||
// chain. Patch that up.
|
||||
if len(rules) == 0 && ipt.mode == "nf_tables" {
|
||||
v := 1
|
||||
return nil, &Error{
|
||||
cmd: exec.Cmd{Args: args},
|
||||
msg: "iptables: No chain/target/match by that name.",
|
||||
exitStatus: &v,
|
||||
}
|
||||
}
|
||||
|
||||
for i, rule := range rules {
|
||||
rules[i] = filterRuleOutput(rule)
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// NewChain creates a new chain in the specified table.
|
||||
// If the chain already exists, it will result in an error.
|
||||
func (ipt *IPTables) NewChain(table, chain string) error {
|
||||
return ipt.run("-t", table, "-N", chain)
|
||||
}
|
||||
|
||||
// ClearChain flushed (deletes all rules) in the specified table/chain.
|
||||
// If the chain does not exist, a new one will be created
|
||||
func (ipt *IPTables) ClearChain(table, chain string) error {
|
||||
err := ipt.NewChain(table, chain)
|
||||
|
||||
// the exit code for "this table already exists" is different for
|
||||
// different iptables modes
|
||||
existsErr := 1
|
||||
if ipt.mode == "nf_tables" {
|
||||
existsErr = 4
|
||||
}
|
||||
|
||||
eerr, eok := err.(*Error)
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case eok && eerr.ExitStatus() == existsErr:
|
||||
// chain already exists. Flush (clear) it.
|
||||
return ipt.run("-t", table, "-F", chain)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// RenameChain renames the old chain to the new one.
|
||||
func (ipt *IPTables) RenameChain(table, oldChain, newChain string) error {
|
||||
return ipt.run("-t", table, "-E", oldChain, newChain)
|
||||
}
|
||||
|
||||
// DeleteChain deletes the chain in the specified table.
|
||||
// The chain must be empty
|
||||
func (ipt *IPTables) DeleteChain(table, chain string) error {
|
||||
return ipt.run("-t", table, "-X", chain)
|
||||
}
|
||||
|
||||
// ChangePolicy changes policy on chain to target
|
||||
func (ipt *IPTables) ChangePolicy(table, chain, target string) error {
|
||||
return ipt.run("-t", table, "-P", chain, target)
|
||||
}
|
||||
|
||||
// Check if the underlying iptables command supports the --random-fully flag
|
||||
func (ipt *IPTables) HasRandomFully() bool {
|
||||
return ipt.hasRandomFully
|
||||
}
|
||||
|
||||
// Return version components of the underlying iptables command
|
||||
func (ipt *IPTables) GetIptablesVersion() (int, int, int) {
|
||||
return ipt.v1, ipt.v2, ipt.v3
|
||||
}
|
||||
|
||||
// run runs an iptables command with the given arguments, ignoring
|
||||
// any stdout output
|
||||
func (ipt *IPTables) run(args ...string) error {
|
||||
return ipt.runWithOutput(args, nil)
|
||||
}
|
||||
|
||||
// runWithOutput runs an iptables command with the given arguments,
|
||||
// writing any stdout output to the given writer
|
||||
func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error {
|
||||
args = append([]string{ipt.path}, args...)
|
||||
if ipt.hasWait {
|
||||
args = append(args, "--wait")
|
||||
} else {
|
||||
fmu, err := newXtablesFileLock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ul, err := fmu.tryLock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ul.Unlock()
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Cmd{
|
||||
Path: ipt.path,
|
||||
Args: args,
|
||||
Stdout: stdout,
|
||||
Stderr: &stderr,
|
||||
}
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
switch e := err.(type) {
|
||||
case *exec.ExitError:
|
||||
return &Error{*e, cmd, stderr.String(), nil}
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getIptablesCommand returns the correct command for the given protocol, either "iptables" or "ip6tables".
|
||||
func getIptablesCommand(proto Protocol) string {
|
||||
if proto == ProtocolIPv6 {
|
||||
return "ip6tables"
|
||||
} else {
|
||||
return "iptables"
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if iptables has the "-C" and "--wait" flag
|
||||
func getIptablesCommandSupport(v1 int, v2 int, v3 int) (bool, bool, bool) {
|
||||
return iptablesHasCheckCommand(v1, v2, v3), iptablesHasWaitCommand(v1, v2, v3), iptablesHasRandomFully(v1, v2, v3)
|
||||
}
|
||||
|
||||
// getIptablesVersion returns the first three components of the iptables version
|
||||
// and the operating mode (e.g. nf_tables or legacy)
|
||||
// e.g. "iptables v1.3.66" would return (1, 3, 66, legacy, nil)
|
||||
func extractIptablesVersion(str string) (int, int, int, string, error) {
|
||||
versionMatcher := regexp.MustCompile(`v([0-9]+)\.([0-9]+)\.([0-9]+)(?:\s+\((\w+))?`)
|
||||
result := versionMatcher.FindStringSubmatch(str)
|
||||
if result == nil {
|
||||
return 0, 0, 0, "", fmt.Errorf("no iptables version found in string: %s", str)
|
||||
}
|
||||
|
||||
v1, err := strconv.Atoi(result[1])
|
||||
if err != nil {
|
||||
return 0, 0, 0, "", err
|
||||
}
|
||||
|
||||
v2, err := strconv.Atoi(result[2])
|
||||
if err != nil {
|
||||
return 0, 0, 0, "", err
|
||||
}
|
||||
|
||||
v3, err := strconv.Atoi(result[3])
|
||||
if err != nil {
|
||||
return 0, 0, 0, "", err
|
||||
}
|
||||
|
||||
mode := "legacy"
|
||||
if result[4] != "" {
|
||||
mode = result[4]
|
||||
}
|
||||
return v1, v2, v3, mode, nil
|
||||
}
|
||||
|
||||
// Runs "iptables --version" to get the version string
|
||||
func getIptablesVersionString(path string) (string, error) {
|
||||
cmd := exec.Command(path, "--version")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
// Checks if an iptables version is after 1.4.11, when --check was added
|
||||
func iptablesHasCheckCommand(v1 int, v2 int, v3 int) bool {
|
||||
if v1 > 1 {
|
||||
return true
|
||||
}
|
||||
if v1 == 1 && v2 > 4 {
|
||||
return true
|
||||
}
|
||||
if v1 == 1 && v2 == 4 && v3 >= 11 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if an iptables version is after 1.4.20, when --wait was added
|
||||
func iptablesHasWaitCommand(v1 int, v2 int, v3 int) bool {
|
||||
if v1 > 1 {
|
||||
return true
|
||||
}
|
||||
if v1 == 1 && v2 > 4 {
|
||||
return true
|
||||
}
|
||||
if v1 == 1 && v2 == 4 && v3 >= 20 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if an iptables version is after 1.6.2, when --random-fully was added
|
||||
func iptablesHasRandomFully(v1 int, v2 int, v3 int) bool {
|
||||
if v1 > 1 {
|
||||
return true
|
||||
}
|
||||
if v1 == 1 && v2 > 6 {
|
||||
return true
|
||||
}
|
||||
if v1 == 1 && v2 == 6 && v3 >= 2 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if a rule specification exists for a table
|
||||
func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) {
|
||||
rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ")
|
||||
args := []string{"-t", table, "-S"}
|
||||
var stdout bytes.Buffer
|
||||
err := ipt.runWithOutput(args, &stdout)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.Contains(stdout.String(), rs), nil
|
||||
}
|
||||
|
||||
// counterRegex is the regex used to detect nftables counter format
|
||||
var counterRegex = regexp.MustCompile(`^\[([0-9]+):([0-9]+)\] `)
|
||||
|
||||
// filterRuleOutput works around some inconsistencies in output.
|
||||
// For example, when iptables is in legacy vs. nftables mode, it produces
|
||||
// different results.
|
||||
func filterRuleOutput(rule string) string {
|
||||
out := rule
|
||||
|
||||
// work around an output difference in nftables mode where counters
|
||||
// are output in iptables-save format, rather than iptables -S format
|
||||
// The string begins with "[0:0]"
|
||||
//
|
||||
// Fixes #49
|
||||
if groups := counterRegex.FindStringSubmatch(out); groups != nil {
|
||||
// drop the brackets
|
||||
out = out[len(groups[0]):]
|
||||
out = fmt.Sprintf("%s -c %s %s", out, groups[1], groups[2])
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
84
vendor/github.com/coreos/go-iptables/iptables/lock.go
generated
vendored
Normal file
84
vendor/github.com/coreos/go-iptables/iptables/lock.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
// In earlier versions of iptables, the xtables lock was implemented
|
||||
// via a Unix socket, but now flock is used via this lockfile:
|
||||
// http://git.netfilter.org/iptables/commit/?id=aa562a660d1555b13cffbac1e744033e91f82707
|
||||
// Note the LSB-conforming "/run" directory does not exist on old
|
||||
// distributions, so assume "/var" is symlinked
|
||||
xtablesLockFilePath = "/var/run/xtables.lock"
|
||||
|
||||
defaultFilePerm = 0600
|
||||
)
|
||||
|
||||
type Unlocker interface {
|
||||
Unlock() error
|
||||
}
|
||||
|
||||
type nopUnlocker struct{}
|
||||
|
||||
func (_ nopUnlocker) Unlock() error { return nil }
|
||||
|
||||
type fileLock struct {
|
||||
// mu is used to protect against concurrent invocations from within this process
|
||||
mu sync.Mutex
|
||||
fd int
|
||||
}
|
||||
|
||||
// tryLock takes an exclusive lock on the xtables lock file without blocking.
|
||||
// This is best-effort only: if the exclusive lock would block (i.e. because
|
||||
// another process already holds it), no error is returned. Otherwise, any
|
||||
// error encountered during the locking operation is returned.
|
||||
// The returned Unlocker should be used to release the lock when the caller is
|
||||
// done invoking iptables commands.
|
||||
func (l *fileLock) tryLock() (Unlocker, error) {
|
||||
l.mu.Lock()
|
||||
err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
switch err {
|
||||
case syscall.EWOULDBLOCK:
|
||||
l.mu.Unlock()
|
||||
return nopUnlocker{}, nil
|
||||
case nil:
|
||||
return l, nil
|
||||
default:
|
||||
l.mu.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock closes the underlying file, which implicitly unlocks it as well. It
|
||||
// also unlocks the associated mutex.
|
||||
func (l *fileLock) Unlock() error {
|
||||
defer l.mu.Unlock()
|
||||
return syscall.Close(l.fd)
|
||||
}
|
||||
|
||||
// newXtablesFileLock opens a new lock on the xtables lockfile without
|
||||
// acquiring the lock
|
||||
func newXtablesFileLock() (*fileLock, error) {
|
||||
fd, err := syscall.Open(xtablesLockFilePath, os.O_CREATE, defaultFilePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileLock{fd: fd}, nil
|
||||
}
|
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
type flag uintptr
|
||||
|
||||
var (
|
||||
// flagRO indicates whether the value field of a reflect.Value
|
||||
// is read-only.
|
||||
flagRO flag
|
||||
|
||||
// flagAddr indicates whether the address of the reflect.Value's
|
||||
// value may be taken.
|
||||
flagAddr flag
|
||||
)
|
||||
|
||||
// flagKindMask holds the bits that make up the kind
|
||||
// part of the flags field. In all the supported versions,
|
||||
// it is in the lower 5 bits.
|
||||
const flagKindMask = flag(0x1f)
|
||||
|
||||
// Different versions of Go have used different
|
||||
// bit layouts for the flags type. This table
|
||||
// records the known combinations.
|
||||
var okFlags = []struct {
|
||||
ro, addr flag
|
||||
}{{
|
||||
// From Go 1.4 to 1.5
|
||||
ro: 1 << 5,
|
||||
addr: 1 << 7,
|
||||
}, {
|
||||
// Up to Go tip.
|
||||
ro: 1<<5 | 1<<6,
|
||||
addr: 1 << 8,
|
||||
}}
|
||||
|
||||
var flagValOffset = func() uintptr {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
return field.Offset
|
||||
}()
|
||||
|
||||
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||
func flagField(v *reflect.Value) *flag {
|
||||
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||
return v
|
||||
}
|
||||
flagFieldPtr := flagField(&v)
|
||||
*flagFieldPtr &^= flagRO
|
||||
*flagFieldPtr |= flagAddr
|
||||
return v
|
||||
}
|
||||
|
||||
// Sanity checks against future reflect package changes
|
||||
// to the type or semantics of the Value.flag field.
|
||||
func init() {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||
panic("reflect.Value flag field has changed kind")
|
||||
}
|
||||
type t0 int
|
||||
var t struct {
|
||||
A t0
|
||||
// t0 will have flagEmbedRO set.
|
||||
t0
|
||||
// a will have flagStickyRO set
|
||||
a t0
|
||||
}
|
||||
vA := reflect.ValueOf(t).FieldByName("A")
|
||||
va := reflect.ValueOf(t).FieldByName("a")
|
||||
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||
|
||||
// Infer flagRO from the difference between the flags
|
||||
// for the (otherwise identical) fields in t.
|
||||
flagPublic := *flagField(&vA)
|
||||
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||
flagRO = flagPublic ^ flagWithRO
|
||||
|
||||
// Infer flagAddr from the difference between a value
|
||||
// taken from a pointer and not.
|
||||
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||
flagNoPtr := *flagField(&vA)
|
||||
flagPtr := *flagField(&vPtrA)
|
||||
flagAddr = flagNoPtr ^ flagPtr
|
||||
|
||||
// Check that the inferred flags tally with one of the known versions.
|
||||
for _, f := range okFlags {
|
||||
if flagRO == f.ro && flagAddr == f.addr {
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("reflect.Value read-only flag has changed semantics")
|
||||
}
|
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe !go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
panicBytes = []byte("(PANIC=")
|
||||
plusBytes = []byte("+")
|
||||
iBytes = []byte("i")
|
||||
trueBytes = []byte("true")
|
||||
falseBytes = []byte("false")
|
||||
interfaceBytes = []byte("(interface {})")
|
||||
commaNewlineBytes = []byte(",\n")
|
||||
newlineBytes = []byte("\n")
|
||||
openBraceBytes = []byte("{")
|
||||
openBraceNewlineBytes = []byte("{\n")
|
||||
closeBraceBytes = []byte("}")
|
||||
asteriskBytes = []byte("*")
|
||||
colonBytes = []byte(":")
|
||||
colonSpaceBytes = []byte(": ")
|
||||
openParenBytes = []byte("(")
|
||||
closeParenBytes = []byte(")")
|
||||
spaceBytes = []byte(" ")
|
||||
pointerChainBytes = []byte("->")
|
||||
nilAngleBytes = []byte("<nil>")
|
||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
percentBytes = []byte("%")
|
||||
precisionBytes = []byte(".")
|
||||
openAngleBytes = []byte("<")
|
||||
closeAngleBytes = []byte(">")
|
||||
openMapBytes = []byte("map[")
|
||||
closeMapBytes = []byte("]")
|
||||
lenEqualsBytes = []byte("len=")
|
||||
capEqualsBytes = []byte("cap=")
|
||||
)
|
||||
|
||||
// hexDigits is used to map a decimal value to a hex digit.
|
||||
var hexDigits = "0123456789abcdef"
|
||||
|
||||
// catchPanic handles any panics that might occur during the handleMethods
|
||||
// calls.
|
||||
func catchPanic(w io.Writer, v reflect.Value) {
|
||||
if err := recover(); err != nil {
|
||||
w.Write(panicBytes)
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMethods attempts to call the Error and String methods on the underlying
|
||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||
//
|
||||
// It handles panics in any called methods by catching and displaying the error
|
||||
// as the formatted value.
|
||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
// Choose whether or not to do error and Stringer interface lookups against
|
||||
// the base type or a pointer to the base type depending on settings.
|
||||
// Technically calling one of these methods with a pointer receiver can
|
||||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.Error()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
|
||||
w.Write([]byte(iface.Error()))
|
||||
return true
|
||||
|
||||
case fmt.Stringer:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.String()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
w.Write([]byte(iface.String()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printBool outputs a boolean value as true or false to Writer w.
|
||||
func printBool(w io.Writer, val bool) {
|
||||
if val {
|
||||
w.Write(trueBytes)
|
||||
} else {
|
||||
w.Write(falseBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// printInt outputs a signed integer value to Writer w.
|
||||
func printInt(w io.Writer, val int64, base int) {
|
||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||
}
|
||||
|
||||
// printUint outputs an unsigned integer value to Writer w.
|
||||
func printUint(w io.Writer, val uint64, base int) {
|
||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||
}
|
||||
|
||||
// printFloat outputs a floating point value using the specified precision,
|
||||
// which is expected to be 32 or 64bit, to Writer w.
|
||||
func printFloat(w io.Writer, val float64, precision int) {
|
||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||
}
|
||||
|
||||
// printComplex outputs a complex value using the specified float precision
|
||||
// for the real and imaginary parts to Writer w.
|
||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
r := real(c)
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||
i := imag(c)
|
||||
if i >= 0 {
|
||||
w.Write(plusBytes)
|
||||
}
|
||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||
w.Write(iBytes)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
num := uint64(p)
|
||||
if num == 0 {
|
||||
w.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||
buf := make([]byte, 18)
|
||||
|
||||
// It's simpler to construct the hex string right to left.
|
||||
base := uint64(16)
|
||||
i := len(buf) - 1
|
||||
for num >= base {
|
||||
buf[i] = hexDigits[num%base]
|
||||
num /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[num]
|
||||
|
||||
// Add '0x' prefix.
|
||||
i--
|
||||
buf[i] = 'x'
|
||||
i--
|
||||
buf[i] = '0'
|
||||
|
||||
// Strip unused leading bytes.
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
values []reflect.Value
|
||||
strings []string // either nil or same len and values
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||
// surrogate keys on which the data should be sorted. It uses flags in
|
||||
// ConfigState to decide if and how to populate those surrogate keys.
|
||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||
vs := &valuesSorter{values: values, cs: cs}
|
||||
if canSortSimply(vs.values[0].Kind()) {
|
||||
return vs
|
||||
}
|
||||
if !cs.DisableMethods {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
b := bytes.Buffer{}
|
||||
if !handleMethods(cs, &b, vs.values[i]) {
|
||||
vs.strings = nil
|
||||
break
|
||||
}
|
||||
vs.strings[i] = b.String()
|
||||
}
|
||||
}
|
||||
if vs.strings == nil && cs.SpewKeys {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||
// directly, or whether it should be considered for sorting by surrogate keys
|
||||
// (if the ConfigState allows it).
|
||||
func canSortSimply(kind reflect.Kind) bool {
|
||||
// This switch parallels valueSortLess, except for the default case.
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Uintptr:
|
||||
return true
|
||||
case reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
// Swap swaps the values at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
if s.strings != nil {
|
||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
// valueSortLess returns whether the first value should sort before the second
|
||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||
// implementation.
|
||||
func valueSortLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.String:
|
||||
return a.String() < b.String()
|
||||
case reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Array:
|
||||
// Compare the contents of both arrays.
|
||||
l := a.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
av := a.Index(i)
|
||||
bv := b.Index(i)
|
||||
if av.Interface() == bv.Interface() {
|
||||
continue
|
||||
}
|
||||
return valueSortLess(av, bv)
|
||||
}
|
||||
}
|
||||
return a.String() < b.String()
|
||||
}
|
||||
|
||||
// Less returns whether the value at index i should sort before the
|
||||
// value at index j. It is part of the sort.Interface implementation.
|
||||
func (s *valuesSorter) Less(i, j int) bool {
|
||||
if s.strings == nil {
|
||||
return valueSortLess(s.values[i], s.values[j])
|
||||
}
|
||||
return s.strings[i] < s.strings[j]
|
||||
}
|
||||
|
||||
// sortValues is a sort function that handles both native types and any type that
|
||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||
// their Value.String() value to ensure display stability.
|
||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Sort(newValuesSorter(values, cs))
|
||||
}
|
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
@ -0,0 +1,306 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigState houses the configuration options used by spew to format and
|
||||
// display values. There is a global instance, Config, that is used to control
|
||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||
// provides methods equivalent to the top-level functions.
|
||||
//
|
||||
// The zero value for ConfigState provides no indentation. You would typically
|
||||
// want to set it to a space or a tab.
|
||||
//
|
||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||
// with default settings. See the documentation of NewDefaultConfig for default
|
||||
// values.
|
||||
type ConfigState struct {
|
||||
// Indent specifies the string to use for each indentation level. The
|
||||
// global config instance that all top-level functions use set this to a
|
||||
// single space by default. If you would like more indentation, you might
|
||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||
Indent string
|
||||
|
||||
// MaxDepth controls the maximum number of levels to descend into nested
|
||||
// data structures. The default, 0, means there is no limit.
|
||||
//
|
||||
// NOTE: Circular data structures are properly detected, so it is not
|
||||
// necessary to set this value unless you specifically want to limit deeply
|
||||
// nested data structures.
|
||||
MaxDepth int
|
||||
|
||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||
// invoked for types that implement them.
|
||||
DisableMethods bool
|
||||
|
||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||
// error and Stringer interfaces on types which only accept a pointer
|
||||
// receiver when the current type is not a pointer.
|
||||
//
|
||||
// NOTE: This might be an unsafe action since calling one of these methods
|
||||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "safe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// DisablePointerAddresses specifies whether to disable the printing of
|
||||
// pointer addresses. This is useful when diffing data structures in tests.
|
||||
DisablePointerAddresses bool
|
||||
|
||||
// DisableCapacities specifies whether to disable the printing of capacities
|
||||
// for arrays, slices, maps and channels. This is useful when diffing
|
||||
// data structures in tests.
|
||||
DisableCapacities bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
// a custom error or Stringer interface is invoked. The default, false,
|
||||
// means it will print the results of invoking the custom error or Stringer
|
||||
// interface and return immediately instead of continuing to recurse into
|
||||
// the internals of the data type.
|
||||
//
|
||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||
// via the DisableMethods or DisablePointerMethods options.
|
||||
ContinueOnMethod bool
|
||||
|
||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||
// this to have a more deterministic, diffable output. Note that only
|
||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||
// that support the error or Stringer interfaces (if methods are
|
||||
// enabled) are supported, with other types sorted according to the
|
||||
// reflect.Value.String() output which guarantees display stability.
|
||||
SortKeys bool
|
||||
|
||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
// The configuration can be changed by modifying the contents of spew.Config.
|
||||
var Config = ConfigState{Indent: " "}
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the formatted string as a value that satisfies error. See NewFormatter
|
||||
// for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
c.Printf, c.Println, or c.Printf.
|
||||
*/
|
||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(c, v)
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(c, w, a...)
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by modifying the public members
|
||||
of c. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func (c *ConfigState) Dump(a ...interface{}) {
|
||||
fdump(c, os.Stdout, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(c, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a spew Formatter interface using
|
||||
// the ConfigState associated with s.
|
||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = newFormatter(c, arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||
//
|
||||
// Indent: " "
|
||||
// MaxDepth: 0
|
||||
// DisableMethods: false
|
||||
// DisablePointerMethods: false
|
||||
// ContinueOnMethod: false
|
||||
// SortKeys: false
|
||||
func NewDefaultConfig() *ConfigState {
|
||||
return &ConfigState{Indent: " "}
|
||||
}
|
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging.
|
||||
|
||||
A quick overview of the additional features spew provides over the built-in
|
||||
printing facilities for Go data types are as follows:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output (only when using
|
||||
Dump style)
|
||||
|
||||
There are two different approaches spew allows for dumping Go data structures:
|
||||
|
||||
* Dump style which prints with newlines, customizable indentation,
|
||||
and additional debug information such as types and all pointer addresses
|
||||
used to indirect to the final value
|
||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||
similar to the default %v while providing the additional functionality
|
||||
outlined above and passing unsupported format verbs such as %x and %q
|
||||
along to fmt
|
||||
|
||||
Quick Start
|
||||
|
||||
This section demonstrates how to quickly get started with spew. See the
|
||||
sections below for further details on formatting and configuration options.
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||
%#+v (adds types and pointer addresses):
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available
|
||||
via the spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
The following configuration options are available:
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of
|
||||
capacities for arrays, slices, maps and channels. This is useful when
|
||||
diffing data structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are
|
||||
supported with other types sorted according to the
|
||||
reflect.Value.String() output which guarantees display
|
||||
stability. Natural map order is used by default.
|
||||
|
||||
* SpewKeys
|
||||
Specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only
|
||||
considered if SortKeys is true.
|
||||
|
||||
Dump Usage
|
||||
|
||||
Simply call spew.Dump with a list of variables you want to dump:
|
||||
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
|
||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||
io.Writer. For example, to dump to standard error:
|
||||
|
||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||
|
||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Sample Dump Output
|
||||
|
||||
See the Dump example for details on the setup of the types and variables being
|
||||
shown here.
|
||||
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
(string) (len=3) "one": (bool) true
|
||||
}
|
||||
}
|
||||
|
||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||
command as shown.
|
||||
([]uint8) (len=32 cap=32) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
|
||||
Custom Formatter
|
||||
|
||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||
so that it integrates cleanly with standard fmt package printing functions. The
|
||||
formatter is useful for inline printing of smaller data types similar to the
|
||||
standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Custom Formatter Usage
|
||||
|
||||
The simplest way to make use of the spew custom formatter is to call one of the
|
||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||
functions have syntax you are most likely already familiar with:
|
||||
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Println(myVar, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
See the Index for the full list convenience functions.
|
||||
|
||||
Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
|
||||
See the Printf example for details on the setup of variables being shown
|
||||
here.
|
||||
|
||||
Errors
|
||||
|
||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||
detects them and handles them internally by printing the panic information
|
||||
inline with the output. Since spew is intended to provide deep pretty printing
|
||||
capabilities on structures, it intentionally does not return any errors.
|
||||
*/
|
||||
package spew
|
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||
// convert cgo types to uint8 slices for hexdumping.
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
type dumpState struct {
|
||||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
// option.
|
||||
func (d *dumpState) indent() {
|
||||
if d.ignoreNextIndent {
|
||||
d.ignoreNextIndent = false
|
||||
return
|
||||
}
|
||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range d.pointers {
|
||||
if depth >= d.depth {
|
||||
delete(d.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by dereferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type information.
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
d.w.Write([]byte(ve.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
|
||||
// Display pointer information.
|
||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||
// Determine whether this type should be hex dumped or not. Also,
|
||||
// for types which should be hexdumped, try to use the underlying data
|
||||
// first, then fall back to trying to convert them to a uint8 slice.
|
||||
var buf []uint8
|
||||
doConvert := false
|
||||
doHexDump := false
|
||||
numEntries := v.Len()
|
||||
if numEntries > 0 {
|
||||
vt := v.Index(0).Type()
|
||||
vts := vt.String()
|
||||
switch {
|
||||
// C types that need to be converted.
|
||||
case cCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUnsignedCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUint8tCharRE.MatchString(vts):
|
||||
doConvert = true
|
||||
|
||||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
// be type asserted to a uint8 slice.
|
||||
doConvert = true
|
||||
}
|
||||
|
||||
// Copy and convert the underlying type if needed.
|
||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||
// Convert and copy each element into a uint8 byte
|
||||
// slice.
|
||||
buf = make([]uint8, numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
vv := v.Index(i)
|
||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||
}
|
||||
doHexDump = true
|
||||
}
|
||||
}
|
||||
|
||||
// Hexdump the entire slice as needed.
|
||||
if doHexDump {
|
||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||
str := indent + hex.Dump(buf)
|
||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||
str = strings.TrimRight(str, d.cs.Indent)
|
||||
d.w.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively call dump for each item.
|
||||
for i := 0; i < numEntries; i++ {
|
||||
d.dump(d.unpackValue(v.Index(i)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||
// value to figure out what kind of object we are dealing with and formats it
|
||||
// appropriately. It is a recursive function, however circular data structures
|
||||
// are detected and handled properly.
|
||||
func (d *dumpState) dump(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
d.w.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
d.indent()
|
||||
d.dumpPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !d.ignoreNextType {
|
||||
d.indent()
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write([]byte(v.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.ignoreNextType = false
|
||||
|
||||
// Display length and capacity if the built-in len and cap functions
|
||||
// work with the value's kind and the len/cap itself is non-zero.
|
||||
valueLen, valueCap := 0, 0
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||
valueLen, valueCap = v.Len(), v.Cap()
|
||||
case reflect.Map, reflect.String:
|
||||
valueLen = v.Len()
|
||||
}
|
||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
if valueLen != 0 {
|
||||
d.w.Write(lenEqualsBytes)
|
||||
printInt(d.w, int64(valueLen), 10)
|
||||
}
|
||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||
if valueLen != 0 {
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.w.Write(capEqualsBytes)
|
||||
printInt(d.w, int64(valueCap), 10)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||
// is enabled
|
||||
if !d.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(d.w, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(d.w, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(d.w, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(d.w, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(d.w, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(d.w, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(d.w, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
d.dumpSlice(v)
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.String:
|
||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
keys := v.MapKeys()
|
||||
if d.cs.SortKeys {
|
||||
sortValues(keys, d.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
d.dump(d.unpackValue(key))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
numFields := v.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
d.indent()
|
||||
vtf := vt.Field(i)
|
||||
d.w.Write([]byte(vtf.Name))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.Field(i)))
|
||||
if i < (numFields - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(d.w, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(d.w, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it in case any new
|
||||
// types are added.
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(d.w, "%v", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fdump is a helper function to consolidate the logic from the various public
|
||||
// methods which take varying writers and config states.
|
||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if arg == nil {
|
||||
w.Write(interfaceBytes)
|
||||
w.Write(spaceBytes)
|
||||
w.Write(nilAngleBytes)
|
||||
w.Write(newlineBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(&Config, w, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(&Config, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by an exported package global,
|
||||
spew.Config. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func Dump(a ...interface{}) {
|
||||
fdump(&Config, os.Stdout, a...)
|
||||
}
|
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||
const supportedFlags = "0-+# "
|
||||
|
||||
// formatState implements the fmt.Formatter interface and contains information
|
||||
// about the state of a formatting operation. The NewFormatter function can
|
||||
// be used to get a new Formatter which can be used directly as arguments
|
||||
// in standard fmt package printing calls.
|
||||
type formatState struct {
|
||||
value interface{}
|
||||
fs fmt.State
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// buildDefaultFormat recreates the original format string without precision
|
||||
// and width information to pass in to fmt.Sprintf in the case of an
|
||||
// unrecognized type. Unless new types are added to the language, this
|
||||
// function won't ever be called.
|
||||
func (f *formatState) buildDefaultFormat() (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteRune('v')
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// constructOrigFormat recreates the original format string including precision
|
||||
// and width information to pass along to the standard fmt package. This allows
|
||||
// automatic deferral of all format strings this package doesn't support.
|
||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if width, ok := f.fs.Width(); ok {
|
||||
buf.WriteString(strconv.Itoa(width))
|
||||
}
|
||||
|
||||
if precision, ok := f.fs.Precision(); ok {
|
||||
buf.Write(precisionBytes)
|
||||
buf.WriteString(strconv.Itoa(precision))
|
||||
}
|
||||
|
||||
buf.WriteRune(verb)
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||
// ensures that types for values which have been unpacked from an interface
|
||||
// are displayed when the show types flag is also set.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface {
|
||||
f.ignoreNextType = false
|
||||
if !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (f *formatState) formatPtr(v reflect.Value) {
|
||||
// Display nil if top level pointer is nil.
|
||||
showTypes := f.fs.Flag('#')
|
||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range f.pointers {
|
||||
if depth >= f.depth {
|
||||
delete(f.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to possibly show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by derferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
f.pointers[addr] = f.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type or indirection level depending on flags.
|
||||
if showTypes && !f.ignoreNextType {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
f.fs.Write([]byte(ve.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
} else {
|
||||
if nilFound || cycleFound {
|
||||
indirects += strings.Count(ve.Type().String(), "*")
|
||||
}
|
||||
f.fs.Write(openAngleBytes)
|
||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||
f.fs.Write(closeAngleBytes)
|
||||
}
|
||||
|
||||
// Display pointer information depending on flags.
|
||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||
f.fs.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
f.fs.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(f.fs, addr)
|
||||
}
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
switch {
|
||||
case nilFound:
|
||||
f.fs.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
f.fs.Write(circularShortBytes)
|
||||
|
||||
default:
|
||||
f.ignoreNextType = true
|
||||
f.format(ve)
|
||||
}
|
||||
}
|
||||
|
||||
// format is the main workhorse for providing the Formatter interface. It
|
||||
// uses the passed reflect value to figure out what kind of object we are
|
||||
// dealing with and formats it appropriately. It is a recursive function,
|
||||
// however circular data structures are detected and handled properly.
|
||||
func (f *formatState) format(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
f.fs.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
f.formatPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write([]byte(v.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
f.ignoreNextType = false
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods
|
||||
// flag is enabled.
|
||||
if !f.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(f.fs, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(f.fs, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(f.fs, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(f.fs, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(f.fs, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(f.fs, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(f.fs, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
f.fs.Write(openBracketBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.Index(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBracketBytes)
|
||||
|
||||
case reflect.String:
|
||||
f.fs.Write([]byte(v.String()))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
f.fs.Write(openMapBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
keys := v.MapKeys()
|
||||
if f.cs.SortKeys {
|
||||
sortValues(keys, f.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(key))
|
||||
f.fs.Write(colonBytes)
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.MapIndex(key)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeMapBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
numFields := v.NumField()
|
||||
f.fs.Write(openBraceBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
for i := 0; i < numFields; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
vtf := vt.Field(i)
|
||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||
f.fs.Write([]byte(vtf.Name))
|
||||
f.fs.Write(colonBytes)
|
||||
}
|
||||
f.format(f.unpackValue(v.Field(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(f.fs, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it if any get added.
|
||||
default:
|
||||
format := f.buildDefaultFormat()
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(f.fs, format, v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(f.fs, format, v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||
// details.
|
||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||
f.fs = fs
|
||||
|
||||
// Use standard formatting for verbs that are not v.
|
||||
if verb != 'v' {
|
||||
format := f.constructOrigFormat(verb)
|
||||
fmt.Fprintf(fs, format, f.value)
|
||||
return
|
||||
}
|
||||
|
||||
if f.value == nil {
|
||||
if fs.Flag('#') {
|
||||
fs.Write(interfaceBytes)
|
||||
}
|
||||
fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
f.format(reflect.ValueOf(f.value))
|
||||
}
|
||||
|
||||
// newFormatter is a helper function to consolidate the logic from the various
|
||||
// public methods which take varying config states.
|
||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||
fs := &formatState{value: v, cs: cs}
|
||||
fs.pointers = make(map[uintptr]int)
|
||||
return fs
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
Printf, Println, or Fprintf.
|
||||
*/
|
||||
func NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(&Config, v)
|
||||
}
|
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the formatted string as a value that satisfies error. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a default Formatter interface returned by NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a default spew Formatter interface.
|
||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = NewFormatter(arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
22
vendor/github.com/go-kit/kit/LICENSE
generated
vendored
Normal file
22
vendor/github.com/go-kit/kit/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Peter Bourgon
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
147
vendor/github.com/go-kit/kit/log/README.md
generated
vendored
Normal file
147
vendor/github.com/go-kit/kit/log/README.md
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
# package log
|
||||
|
||||
`package log` provides a minimal interface for structured logging in services.
|
||||
It may be wrapped to encode conventions, enforce type-safety, provide leveled
|
||||
logging, and so on. It can be used for both typical application log events,
|
||||
and log-structured data streams.
|
||||
|
||||
## Structured logging
|
||||
|
||||
Structured logging is, basically, conceding to the reality that logs are
|
||||
_data_, and warrant some level of schematic rigor. Using a stricter,
|
||||
key/value-oriented message format for our logs, containing contextual and
|
||||
semantic information, makes it much easier to get insight into the
|
||||
operational activity of the systems we build. Consequently, `package log` is
|
||||
of the strong belief that "[the benefits of structured logging outweigh the
|
||||
minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
|
||||
|
||||
Migrating from unstructured to structured logging is probably a lot easier
|
||||
than you'd expect.
|
||||
|
||||
```go
|
||||
// Unstructured
|
||||
log.Printf("HTTP server listening on %s", addr)
|
||||
|
||||
// Structured
|
||||
logger.Log("transport", "HTTP", "addr", addr, "msg", "listening")
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Typical application logging
|
||||
|
||||
```go
|
||||
w := log.NewSyncWriter(os.Stderr)
|
||||
logger := log.NewLogfmtLogger(w)
|
||||
logger.Log("question", "what is the meaning of life?", "answer", 42)
|
||||
|
||||
// Output:
|
||||
// question="what is the meaning of life?" answer=42
|
||||
```
|
||||
|
||||
### Contextual Loggers
|
||||
|
||||
```go
|
||||
func main() {
|
||||
var logger log.Logger
|
||||
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
|
||||
logger = log.With(logger, "instance_id", 123)
|
||||
|
||||
logger.Log("msg", "starting")
|
||||
NewWorker(log.With(logger, "component", "worker")).Run()
|
||||
NewSlacker(log.With(logger, "component", "slacker")).Run()
|
||||
}
|
||||
|
||||
// Output:
|
||||
// instance_id=123 msg=starting
|
||||
// instance_id=123 component=worker msg=running
|
||||
// instance_id=123 component=slacker msg=running
|
||||
```
|
||||
|
||||
### Interact with stdlib logger
|
||||
|
||||
Redirect stdlib logger to Go kit logger.
|
||||
|
||||
```go
|
||||
import (
|
||||
"os"
|
||||
stdlog "log"
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout))
|
||||
stdlog.SetOutput(kitlog.NewStdlibAdapter(logger))
|
||||
stdlog.Print("I sure like pie")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
|
||||
```
|
||||
|
||||
Or, if, for legacy reasons, you need to pipe all of your logging through the
|
||||
stdlib log package, you can redirect Go kit logger to the stdlib logger.
|
||||
|
||||
```go
|
||||
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
|
||||
logger.Log("legacy", true, "msg", "at least it's something")
|
||||
|
||||
// Output:
|
||||
// 2016/01/01 12:34:56 legacy=true msg="at least it's something"
|
||||
```
|
||||
|
||||
### Timestamps and callers
|
||||
|
||||
```go
|
||||
var logger log.Logger
|
||||
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
|
||||
logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
|
||||
|
||||
logger.Log("msg", "hello")
|
||||
|
||||
// Output:
|
||||
// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello
|
||||
```
|
||||
|
||||
## Supported output formats
|
||||
|
||||
- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write))
|
||||
- JSON
|
||||
|
||||
## Enhancements
|
||||
|
||||
`package log` is centered on the one-method Logger interface.
|
||||
|
||||
```go
|
||||
type Logger interface {
|
||||
Log(keyvals ...interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
This interface, and its supporting code like is the product of much iteration
|
||||
and evaluation. For more details on the evolution of the Logger interface,
|
||||
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
|
||||
a talk by [Chris Hines](https://github.com/ChrisHines).
|
||||
Also, please see
|
||||
[#63](https://github.com/go-kit/kit/issues/63),
|
||||
[#76](https://github.com/go-kit/kit/pull/76),
|
||||
[#131](https://github.com/go-kit/kit/issues/131),
|
||||
[#157](https://github.com/go-kit/kit/pull/157),
|
||||
[#164](https://github.com/go-kit/kit/issues/164), and
|
||||
[#252](https://github.com/go-kit/kit/pull/252)
|
||||
to review historical conversations about package log and the Logger interface.
|
||||
|
||||
Value-add packages and suggestions,
|
||||
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level),
|
||||
are of course welcome. Good proposals should
|
||||
|
||||
- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With),
|
||||
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and
|
||||
- Be friendly to packages that accept only an unadorned log.Logger.
|
||||
|
||||
## Benchmarks & comparisons
|
||||
|
||||
There are a few Go logging benchmarks and comparisons that include Go kit's package log.
|
||||
|
||||
- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log
|
||||
- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log
|
116
vendor/github.com/go-kit/kit/log/doc.go
generated
vendored
Normal file
116
vendor/github.com/go-kit/kit/log/doc.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
// Package log provides a structured logger.
|
||||
//
|
||||
// Structured logging produces logs easily consumed later by humans or
|
||||
// machines. Humans might be interested in debugging errors, or tracing
|
||||
// specific requests. Machines might be interested in counting interesting
|
||||
// events, or aggregating information for off-line processing. In both cases,
|
||||
// it is important that the log messages are structured and actionable.
|
||||
// Package log is designed to encourage both of these best practices.
|
||||
//
|
||||
// Basic Usage
|
||||
//
|
||||
// The fundamental interface is Logger. Loggers create log events from
|
||||
// key/value data. The Logger interface has a single method, Log, which
|
||||
// accepts a sequence of alternating key/value pairs, which this package names
|
||||
// keyvals.
|
||||
//
|
||||
// type Logger interface {
|
||||
// Log(keyvals ...interface{}) error
|
||||
// }
|
||||
//
|
||||
// Here is an example of a function using a Logger to create log events.
|
||||
//
|
||||
// func RunTask(task Task, logger log.Logger) string {
|
||||
// logger.Log("taskID", task.ID, "event", "starting task")
|
||||
// ...
|
||||
// logger.Log("taskID", task.ID, "event", "task complete")
|
||||
// }
|
||||
//
|
||||
// The keys in the above example are "taskID" and "event". The values are
|
||||
// task.ID, "starting task", and "task complete". Every key is followed
|
||||
// immediately by its value.
|
||||
//
|
||||
// Keys are usually plain strings. Values may be any type that has a sensible
|
||||
// encoding in the chosen log format. With structured logging it is a good
|
||||
// idea to log simple values without formatting them. This practice allows
|
||||
// the chosen logger to encode values in the most appropriate way.
|
||||
//
|
||||
// Contextual Loggers
|
||||
//
|
||||
// A contextual logger stores keyvals that it includes in all log events.
|
||||
// Building appropriate contextual loggers reduces repetition and aids
|
||||
// consistency in the resulting log output. With and WithPrefix add context to
|
||||
// a logger. We can use With to improve the RunTask example.
|
||||
//
|
||||
// func RunTask(task Task, logger log.Logger) string {
|
||||
// logger = log.With(logger, "taskID", task.ID)
|
||||
// logger.Log("event", "starting task")
|
||||
// ...
|
||||
// taskHelper(task.Cmd, logger)
|
||||
// ...
|
||||
// logger.Log("event", "task complete")
|
||||
// }
|
||||
//
|
||||
// The improved version emits the same log events as the original for the
|
||||
// first and last calls to Log. Passing the contextual logger to taskHelper
|
||||
// enables each log event created by taskHelper to include the task.ID even
|
||||
// though taskHelper does not have access to that value. Using contextual
|
||||
// loggers this way simplifies producing log output that enables tracing the
|
||||
// life cycle of individual tasks. (See the Contextual example for the full
|
||||
// code of the above snippet.)
|
||||
//
|
||||
// Dynamic Contextual Values
|
||||
//
|
||||
// A Valuer function stored in a contextual logger generates a new value each
|
||||
// time an event is logged. The Valuer example demonstrates how this feature
|
||||
// works.
|
||||
//
|
||||
// Valuers provide the basis for consistently logging timestamps and source
|
||||
// code location. The log package defines several valuers for that purpose.
|
||||
// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and
|
||||
// DefaultCaller. A common logger initialization sequence that ensures all log
|
||||
// entries contain a timestamp and source location looks like this:
|
||||
//
|
||||
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
|
||||
// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
|
||||
//
|
||||
// Concurrent Safety
|
||||
//
|
||||
// Applications with multiple goroutines want each log event written to the
|
||||
// same logger to remain separate from other log events. Package log provides
|
||||
// two simple solutions for concurrent safe logging.
|
||||
//
|
||||
// NewSyncWriter wraps an io.Writer and serializes each call to its Write
|
||||
// method. Using a SyncWriter has the benefit that the smallest practical
|
||||
// portion of the logging logic is performed within a mutex, but it requires
|
||||
// the formatting Logger to make only one call to Write per log event.
|
||||
//
|
||||
// NewSyncLogger wraps any Logger and serializes each call to its Log method.
|
||||
// Using a SyncLogger has the benefit that it guarantees each log event is
|
||||
// handled atomically within the wrapped logger, but it typically serializes
|
||||
// both the formatting and output logic. Use a SyncLogger if the formatting
|
||||
// logger may perform multiple writes per log event.
|
||||
//
|
||||
// Error Handling
|
||||
//
|
||||
// This package relies on the practice of wrapping or decorating loggers with
|
||||
// other loggers to provide composable pieces of functionality. It also means
|
||||
// that Logger.Log must return an error because some
|
||||
// implementations—especially those that output log data to an io.Writer—may
|
||||
// encounter errors that cannot be handled locally. This in turn means that
|
||||
// Loggers that wrap other loggers should return errors from the wrapped
|
||||
// logger up the stack.
|
||||
//
|
||||
// Fortunately, the decorator pattern also provides a way to avoid the
|
||||
// necessity to check for errors every time an application calls Logger.Log.
|
||||
// An application required to panic whenever its Logger encounters
|
||||
// an error could initialize its logger as follows.
|
||||
//
|
||||
// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
|
||||
// logger := log.LoggerFunc(func(keyvals ...interface{}) error {
|
||||
// if err := fmtlogger.Log(keyvals...); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// return nil
|
||||
// })
|
||||
package log
|
89
vendor/github.com/go-kit/kit/log/json_logger.go
generated
vendored
Normal file
89
vendor/github.com/go-kit/kit/log/json_logger.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type jsonLogger struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a
|
||||
// single JSON object. Each log event produces no more than one call to
|
||||
// w.Write. The passed Writer must be safe for concurrent use by multiple
|
||||
// goroutines if the returned Logger will be used concurrently.
|
||||
func NewJSONLogger(w io.Writer) Logger {
|
||||
return &jsonLogger{w}
|
||||
}
|
||||
|
||||
func (l *jsonLogger) Log(keyvals ...interface{}) error {
|
||||
n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd
|
||||
m := make(map[string]interface{}, n)
|
||||
for i := 0; i < len(keyvals); i += 2 {
|
||||
k := keyvals[i]
|
||||
var v interface{} = ErrMissingValue
|
||||
if i+1 < len(keyvals) {
|
||||
v = keyvals[i+1]
|
||||
}
|
||||
merge(m, k, v)
|
||||
}
|
||||
return json.NewEncoder(l.Writer).Encode(m)
|
||||
}
|
||||
|
||||
func merge(dst map[string]interface{}, k, v interface{}) {
|
||||
var key string
|
||||
switch x := k.(type) {
|
||||
case string:
|
||||
key = x
|
||||
case fmt.Stringer:
|
||||
key = safeString(x)
|
||||
default:
|
||||
key = fmt.Sprint(x)
|
||||
}
|
||||
|
||||
// We want json.Marshaler and encoding.TextMarshaller to take priority over
|
||||
// err.Error() and v.String(). But json.Marshall (called later) does that by
|
||||
// default so we force a no-op if it's one of those 2 case.
|
||||
switch x := v.(type) {
|
||||
case json.Marshaler:
|
||||
case encoding.TextMarshaler:
|
||||
case error:
|
||||
v = safeError(x)
|
||||
case fmt.Stringer:
|
||||
v = safeString(x)
|
||||
}
|
||||
|
||||
dst[key] = v
|
||||
}
|
||||
|
||||
func safeString(str fmt.Stringer) (s string) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
s = "NULL"
|
||||
} else {
|
||||
panic(panicVal)
|
||||
}
|
||||
}
|
||||
}()
|
||||
s = str.String()
|
||||
return
|
||||
}
|
||||
|
||||
func safeError(err error) (s interface{}) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
s = nil
|
||||
} else {
|
||||
panic(panicVal)
|
||||
}
|
||||
}
|
||||
}()
|
||||
s = err.Error()
|
||||
return
|
||||
}
|
22
vendor/github.com/go-kit/kit/log/level/doc.go
generated
vendored
Normal file
22
vendor/github.com/go-kit/kit/log/level/doc.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Package level implements leveled logging on top of Go kit's log package. To
|
||||
// use the level package, create a logger as per normal in your func main, and
|
||||
// wrap it with level.NewFilter.
|
||||
//
|
||||
// var logger log.Logger
|
||||
// logger = log.NewLogfmtLogger(os.Stderr)
|
||||
// logger = level.NewFilter(logger, level.AllowInfo()) // <--
|
||||
// logger = log.With(logger, "ts", log.DefaultTimestampUTC)
|
||||
//
|
||||
// Then, at the callsites, use one of the level.Debug, Info, Warn, or Error
|
||||
// helper methods to emit leveled log events.
|
||||
//
|
||||
// logger.Log("foo", "bar") // as normal, no level
|
||||
// level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get())
|
||||
// if value > 100 {
|
||||
// level.Error(logger).Log("value", value)
|
||||
// }
|
||||
//
|
||||
// NewFilter allows precise control over what happens when a log event is
|
||||
// emitted without a level key, or if a squelched level is used. Check the
|
||||
// Option functions for details.
|
||||
package level
|
205
vendor/github.com/go-kit/kit/log/level/level.go
generated
vendored
Normal file
205
vendor/github.com/go-kit/kit/log/level/level.go
generated
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
package level
|
||||
|
||||
import "github.com/go-kit/kit/log"
|
||||
|
||||
// Error returns a logger that includes a Key/ErrorValue pair.
|
||||
func Error(logger log.Logger) log.Logger {
|
||||
return log.WithPrefix(logger, Key(), ErrorValue())
|
||||
}
|
||||
|
||||
// Warn returns a logger that includes a Key/WarnValue pair.
|
||||
func Warn(logger log.Logger) log.Logger {
|
||||
return log.WithPrefix(logger, Key(), WarnValue())
|
||||
}
|
||||
|
||||
// Info returns a logger that includes a Key/InfoValue pair.
|
||||
func Info(logger log.Logger) log.Logger {
|
||||
return log.WithPrefix(logger, Key(), InfoValue())
|
||||
}
|
||||
|
||||
// Debug returns a logger that includes a Key/DebugValue pair.
|
||||
func Debug(logger log.Logger) log.Logger {
|
||||
return log.WithPrefix(logger, Key(), DebugValue())
|
||||
}
|
||||
|
||||
// NewFilter wraps next and implements level filtering. See the commentary on
|
||||
// the Option functions for a detailed description of how to configure levels.
|
||||
// If no options are provided, all leveled log events created with Debug,
|
||||
// Info, Warn or Error helper methods are squelched and non-leveled log
|
||||
// events are passed to next unmodified.
|
||||
func NewFilter(next log.Logger, options ...Option) log.Logger {
|
||||
l := &logger{
|
||||
next: next,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(l)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
next log.Logger
|
||||
allowed level
|
||||
squelchNoLevel bool
|
||||
errNotAllowed error
|
||||
errNoLevel error
|
||||
}
|
||||
|
||||
func (l *logger) Log(keyvals ...interface{}) error {
|
||||
var hasLevel, levelAllowed bool
|
||||
for i := 1; i < len(keyvals); i += 2 {
|
||||
if v, ok := keyvals[i].(*levelValue); ok {
|
||||
hasLevel = true
|
||||
levelAllowed = l.allowed&v.level != 0
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasLevel && l.squelchNoLevel {
|
||||
return l.errNoLevel
|
||||
}
|
||||
if hasLevel && !levelAllowed {
|
||||
return l.errNotAllowed
|
||||
}
|
||||
return l.next.Log(keyvals...)
|
||||
}
|
||||
|
||||
// Option sets a parameter for the leveled logger.
|
||||
type Option func(*logger)
|
||||
|
||||
// AllowAll is an alias for AllowDebug.
|
||||
func AllowAll() Option {
|
||||
return AllowDebug()
|
||||
}
|
||||
|
||||
// AllowDebug allows error, warn, info and debug level log events to pass.
|
||||
func AllowDebug() Option {
|
||||
return allowed(levelError | levelWarn | levelInfo | levelDebug)
|
||||
}
|
||||
|
||||
// AllowInfo allows error, warn and info level log events to pass.
|
||||
func AllowInfo() Option {
|
||||
return allowed(levelError | levelWarn | levelInfo)
|
||||
}
|
||||
|
||||
// AllowWarn allows error and warn level log events to pass.
|
||||
func AllowWarn() Option {
|
||||
return allowed(levelError | levelWarn)
|
||||
}
|
||||
|
||||
// AllowError allows only error level log events to pass.
|
||||
func AllowError() Option {
|
||||
return allowed(levelError)
|
||||
}
|
||||
|
||||
// AllowNone allows no leveled log events to pass.
|
||||
func AllowNone() Option {
|
||||
return allowed(0)
|
||||
}
|
||||
|
||||
func allowed(allowed level) Option {
|
||||
return func(l *logger) { l.allowed = allowed }
|
||||
}
|
||||
|
||||
// ErrNotAllowed sets the error to return from Log when it squelches a log
|
||||
// event disallowed by the configured Allow[Level] option. By default,
|
||||
// ErrNotAllowed is nil; in this case the log event is squelched with no
|
||||
// error.
|
||||
func ErrNotAllowed(err error) Option {
|
||||
return func(l *logger) { l.errNotAllowed = err }
|
||||
}
|
||||
|
||||
// SquelchNoLevel instructs Log to squelch log events with no level, so that
|
||||
// they don't proceed through to the wrapped logger. If SquelchNoLevel is set
|
||||
// to true and a log event is squelched in this way, the error value
|
||||
// configured with ErrNoLevel is returned to the caller.
|
||||
func SquelchNoLevel(squelch bool) Option {
|
||||
return func(l *logger) { l.squelchNoLevel = squelch }
|
||||
}
|
||||
|
||||
// ErrNoLevel sets the error to return from Log when it squelches a log event
|
||||
// with no level. By default, ErrNoLevel is nil; in this case the log event is
|
||||
// squelched with no error.
|
||||
func ErrNoLevel(err error) Option {
|
||||
return func(l *logger) { l.errNoLevel = err }
|
||||
}
|
||||
|
||||
// NewInjector wraps next and returns a logger that adds a Key/level pair to
|
||||
// the beginning of log events that don't already contain a level. In effect,
|
||||
// this gives a default level to logs without a level.
|
||||
func NewInjector(next log.Logger, level Value) log.Logger {
|
||||
return &injector{
|
||||
next: next,
|
||||
level: level,
|
||||
}
|
||||
}
|
||||
|
||||
type injector struct {
|
||||
next log.Logger
|
||||
level interface{}
|
||||
}
|
||||
|
||||
func (l *injector) Log(keyvals ...interface{}) error {
|
||||
for i := 1; i < len(keyvals); i += 2 {
|
||||
if _, ok := keyvals[i].(*levelValue); ok {
|
||||
return l.next.Log(keyvals...)
|
||||
}
|
||||
}
|
||||
kvs := make([]interface{}, len(keyvals)+2)
|
||||
kvs[0], kvs[1] = key, l.level
|
||||
copy(kvs[2:], keyvals)
|
||||
return l.next.Log(kvs...)
|
||||
}
|
||||
|
||||
// Value is the interface that each of the canonical level values implement.
|
||||
// It contains unexported methods that prevent types from other packages from
|
||||
// implementing it and guaranteeing that NewFilter can distinguish the levels
|
||||
// defined in this package from all other values.
|
||||
type Value interface {
|
||||
String() string
|
||||
levelVal()
|
||||
}
|
||||
|
||||
// Key returns the unique key added to log events by the loggers in this
|
||||
// package.
|
||||
func Key() interface{} { return key }
|
||||
|
||||
// ErrorValue returns the unique value added to log events by Error.
|
||||
func ErrorValue() Value { return errorValue }
|
||||
|
||||
// WarnValue returns the unique value added to log events by Warn.
|
||||
func WarnValue() Value { return warnValue }
|
||||
|
||||
// InfoValue returns the unique value added to log events by Info.
|
||||
func InfoValue() Value { return infoValue }
|
||||
|
||||
// DebugValue returns the unique value added to log events by Warn.
|
||||
func DebugValue() Value { return debugValue }
|
||||
|
||||
var (
|
||||
// key is of type interface{} so that it allocates once during package
|
||||
// initialization and avoids allocating every time the value is added to a
|
||||
// []interface{} later.
|
||||
key interface{} = "level"
|
||||
|
||||
errorValue = &levelValue{level: levelError, name: "error"}
|
||||
warnValue = &levelValue{level: levelWarn, name: "warn"}
|
||||
infoValue = &levelValue{level: levelInfo, name: "info"}
|
||||
debugValue = &levelValue{level: levelDebug, name: "debug"}
|
||||
)
|
||||
|
||||
type level byte
|
||||
|
||||
const (
|
||||
levelDebug level = 1 << iota
|
||||
levelInfo
|
||||
levelWarn
|
||||
levelError
|
||||
)
|
||||
|
||||
type levelValue struct {
|
||||
name string
|
||||
level
|
||||
}
|
||||
|
||||
func (v *levelValue) String() string { return v.name }
|
||||
func (v *levelValue) levelVal() {}
|
135
vendor/github.com/go-kit/kit/log/log.go
generated
vendored
Normal file
135
vendor/github.com/go-kit/kit/log/log.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
package log
|
||||
|
||||
import "errors"
|
||||
|
||||
// Logger is the fundamental interface for all log operations. Log creates a
|
||||
// log event from keyvals, a variadic sequence of alternating keys and values.
|
||||
// Implementations must be safe for concurrent use by multiple goroutines. In
|
||||
// particular, any implementation of Logger that appends to keyvals or
|
||||
// modifies or retains any of its elements must make a copy first.
|
||||
type Logger interface {
|
||||
Log(keyvals ...interface{}) error
|
||||
}
|
||||
|
||||
// ErrMissingValue is appended to keyvals slices with odd length to substitute
|
||||
// the missing value.
|
||||
var ErrMissingValue = errors.New("(MISSING)")
|
||||
|
||||
// With returns a new contextual logger with keyvals prepended to those passed
|
||||
// to calls to Log. If logger is also a contextual logger created by With or
|
||||
// WithPrefix, keyvals is appended to the existing context.
|
||||
//
|
||||
// The returned Logger replaces all value elements (odd indexes) containing a
|
||||
// Valuer with their generated value for each call to its Log method.
|
||||
func With(logger Logger, keyvals ...interface{}) Logger {
|
||||
if len(keyvals) == 0 {
|
||||
return logger
|
||||
}
|
||||
l := newContext(logger)
|
||||
kvs := append(l.keyvals, keyvals...)
|
||||
if len(kvs)%2 != 0 {
|
||||
kvs = append(kvs, ErrMissingValue)
|
||||
}
|
||||
return &context{
|
||||
logger: l.logger,
|
||||
// Limiting the capacity of the stored keyvals ensures that a new
|
||||
// backing array is created if the slice must grow in Log or With.
|
||||
// Using the extra capacity without copying risks a data race that
|
||||
// would violate the Logger interface contract.
|
||||
keyvals: kvs[:len(kvs):len(kvs)],
|
||||
hasValuer: l.hasValuer || containsValuer(keyvals),
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrefix returns a new contextual logger with keyvals prepended to those
|
||||
// passed to calls to Log. If logger is also a contextual logger created by
|
||||
// With or WithPrefix, keyvals is prepended to the existing context.
|
||||
//
|
||||
// The returned Logger replaces all value elements (odd indexes) containing a
|
||||
// Valuer with their generated value for each call to its Log method.
|
||||
func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
|
||||
if len(keyvals) == 0 {
|
||||
return logger
|
||||
}
|
||||
l := newContext(logger)
|
||||
// Limiting the capacity of the stored keyvals ensures that a new
|
||||
// backing array is created if the slice must grow in Log or With.
|
||||
// Using the extra capacity without copying risks a data race that
|
||||
// would violate the Logger interface contract.
|
||||
n := len(l.keyvals) + len(keyvals)
|
||||
if len(keyvals)%2 != 0 {
|
||||
n++
|
||||
}
|
||||
kvs := make([]interface{}, 0, n)
|
||||
kvs = append(kvs, keyvals...)
|
||||
if len(kvs)%2 != 0 {
|
||||
kvs = append(kvs, ErrMissingValue)
|
||||
}
|
||||
kvs = append(kvs, l.keyvals...)
|
||||
return &context{
|
||||
logger: l.logger,
|
||||
keyvals: kvs,
|
||||
hasValuer: l.hasValuer || containsValuer(keyvals),
|
||||
}
|
||||
}
|
||||
|
||||
// context is the Logger implementation returned by With and WithPrefix. It
|
||||
// wraps a Logger and holds keyvals that it includes in all log events. Its
|
||||
// Log method calls bindValues to generate values for each Valuer in the
|
||||
// context keyvals.
|
||||
//
|
||||
// A context must always have the same number of stack frames between calls to
|
||||
// its Log method and the eventual binding of Valuers to their value. This
|
||||
// requirement comes from the functional requirement to allow a context to
|
||||
// resolve application call site information for a Caller stored in the
|
||||
// context. To do this we must be able to predict the number of logging
|
||||
// functions on the stack when bindValues is called.
|
||||
//
|
||||
// Two implementation details provide the needed stack depth consistency.
|
||||
//
|
||||
// 1. newContext avoids introducing an additional layer when asked to
|
||||
// wrap another context.
|
||||
// 2. With and WithPrefix avoid introducing an additional layer by
|
||||
// returning a newly constructed context with a merged keyvals rather
|
||||
// than simply wrapping the existing context.
|
||||
type context struct {
|
||||
logger Logger
|
||||
keyvals []interface{}
|
||||
hasValuer bool
|
||||
}
|
||||
|
||||
func newContext(logger Logger) *context {
|
||||
if c, ok := logger.(*context); ok {
|
||||
return c
|
||||
}
|
||||
return &context{logger: logger}
|
||||
}
|
||||
|
||||
// Log replaces all value elements (odd indexes) containing a Valuer in the
|
||||
// stored context with their generated value, appends keyvals, and passes the
|
||||
// result to the wrapped Logger.
|
||||
func (l *context) Log(keyvals ...interface{}) error {
|
||||
kvs := append(l.keyvals, keyvals...)
|
||||
if len(kvs)%2 != 0 {
|
||||
kvs = append(kvs, ErrMissingValue)
|
||||
}
|
||||
if l.hasValuer {
|
||||
// If no keyvals were appended above then we must copy l.keyvals so
|
||||
// that future log events will reevaluate the stored Valuers.
|
||||
if len(keyvals) == 0 {
|
||||
kvs = append([]interface{}{}, l.keyvals...)
|
||||
}
|
||||
bindValues(kvs[:len(l.keyvals)])
|
||||
}
|
||||
return l.logger.Log(kvs...)
|
||||
}
|
||||
|
||||
// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
|
||||
// f is a function with the appropriate signature, LoggerFunc(f) is a Logger
|
||||
// object that calls f.
|
||||
type LoggerFunc func(...interface{}) error
|
||||
|
||||
// Log implements Logger by calling f(keyvals...).
|
||||
func (f LoggerFunc) Log(keyvals ...interface{}) error {
|
||||
return f(keyvals...)
|
||||
}
|
62
vendor/github.com/go-kit/kit/log/logfmt_logger.go
generated
vendored
Normal file
62
vendor/github.com/go-kit/kit/log/logfmt_logger.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/go-logfmt/logfmt"
|
||||
)
|
||||
|
||||
type logfmtEncoder struct {
|
||||
*logfmt.Encoder
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (l *logfmtEncoder) Reset() {
|
||||
l.Encoder.Reset()
|
||||
l.buf.Reset()
|
||||
}
|
||||
|
||||
var logfmtEncoderPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
var enc logfmtEncoder
|
||||
enc.Encoder = logfmt.NewEncoder(&enc.buf)
|
||||
return &enc
|
||||
},
|
||||
}
|
||||
|
||||
type logfmtLogger struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
|
||||
// logfmt format. Each log event produces no more than one call to w.Write.
|
||||
// The passed Writer must be safe for concurrent use by multiple goroutines if
|
||||
// the returned Logger will be used concurrently.
|
||||
func NewLogfmtLogger(w io.Writer) Logger {
|
||||
return &logfmtLogger{w}
|
||||
}
|
||||
|
||||
func (l logfmtLogger) Log(keyvals ...interface{}) error {
|
||||
enc := logfmtEncoderPool.Get().(*logfmtEncoder)
|
||||
enc.Reset()
|
||||
defer logfmtEncoderPool.Put(enc)
|
||||
|
||||
if err := enc.EncodeKeyvals(keyvals...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add newline to the end of the buffer
|
||||
if err := enc.EndRecord(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The Logger interface requires implementations to be safe for concurrent
|
||||
// use by multiple goroutines. For this implementation that means making
|
||||
// only one call to l.w.Write() for each call to Log.
|
||||
if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
8
vendor/github.com/go-kit/kit/log/nop_logger.go
generated
vendored
Normal file
8
vendor/github.com/go-kit/kit/log/nop_logger.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
package log
|
||||
|
||||
type nopLogger struct{}
|
||||
|
||||
// NewNopLogger returns a logger that doesn't do anything.
|
||||
func NewNopLogger() Logger { return nopLogger{} }
|
||||
|
||||
func (nopLogger) Log(...interface{}) error { return nil }
|
116
vendor/github.com/go-kit/kit/log/stdlib.go
generated
vendored
Normal file
116
vendor/github.com/go-kit/kit/log/stdlib.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's
|
||||
// designed to be passed to a Go kit logger as the writer, for cases where
|
||||
// it's necessary to redirect all Go kit log output to the stdlib logger.
|
||||
//
|
||||
// If you have any choice in the matter, you shouldn't use this. Prefer to
|
||||
// redirect the stdlib log to the Go kit logger via NewStdlibAdapter.
|
||||
type StdlibWriter struct{}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (w StdlibWriter) Write(p []byte) (int, error) {
|
||||
log.Print(strings.TrimSpace(string(p)))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// StdlibAdapter wraps a Logger and allows it to be passed to the stdlib
|
||||
// logger's SetOutput. It will extract date/timestamps, filenames, and
|
||||
// messages, and place them under relevant keys.
|
||||
type StdlibAdapter struct {
|
||||
Logger
|
||||
timestampKey string
|
||||
fileKey string
|
||||
messageKey string
|
||||
}
|
||||
|
||||
// StdlibAdapterOption sets a parameter for the StdlibAdapter.
|
||||
type StdlibAdapterOption func(*StdlibAdapter)
|
||||
|
||||
// TimestampKey sets the key for the timestamp field. By default, it's "ts".
|
||||
func TimestampKey(key string) StdlibAdapterOption {
|
||||
return func(a *StdlibAdapter) { a.timestampKey = key }
|
||||
}
|
||||
|
||||
// FileKey sets the key for the file and line field. By default, it's "caller".
|
||||
func FileKey(key string) StdlibAdapterOption {
|
||||
return func(a *StdlibAdapter) { a.fileKey = key }
|
||||
}
|
||||
|
||||
// MessageKey sets the key for the actual log message. By default, it's "msg".
|
||||
func MessageKey(key string) StdlibAdapterOption {
|
||||
return func(a *StdlibAdapter) { a.messageKey = key }
|
||||
}
|
||||
|
||||
// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed
|
||||
// logger. It's designed to be passed to log.SetOutput.
|
||||
func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
|
||||
a := StdlibAdapter{
|
||||
Logger: logger,
|
||||
timestampKey: "ts",
|
||||
fileKey: "caller",
|
||||
messageKey: "msg",
|
||||
}
|
||||
for _, option := range options {
|
||||
option(&a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a StdlibAdapter) Write(p []byte) (int, error) {
|
||||
result := subexps(p)
|
||||
keyvals := []interface{}{}
|
||||
var timestamp string
|
||||
if date, ok := result["date"]; ok && date != "" {
|
||||
timestamp = date
|
||||
}
|
||||
if time, ok := result["time"]; ok && time != "" {
|
||||
if timestamp != "" {
|
||||
timestamp += " "
|
||||
}
|
||||
timestamp += time
|
||||
}
|
||||
if timestamp != "" {
|
||||
keyvals = append(keyvals, a.timestampKey, timestamp)
|
||||
}
|
||||
if file, ok := result["file"]; ok && file != "" {
|
||||
keyvals = append(keyvals, a.fileKey, file)
|
||||
}
|
||||
if msg, ok := result["msg"]; ok {
|
||||
keyvals = append(keyvals, a.messageKey, msg)
|
||||
}
|
||||
if err := a.Logger.Log(keyvals...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
const (
|
||||
logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?`
|
||||
logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?`
|
||||
logRegexpFile = `(?P<file>.+?:[0-9]+)?`
|
||||
logRegexpMsg = `(: )?(?P<msg>.*)`
|
||||
)
|
||||
|
||||
var (
|
||||
logRegexp = regexp.MustCompile(logRegexpDate + logRegexpTime + logRegexpFile + logRegexpMsg)
|
||||
)
|
||||
|
||||
func subexps(line []byte) map[string]string {
|
||||
m := logRegexp.FindSubmatch(line)
|
||||
if len(m) < len(logRegexp.SubexpNames()) {
|
||||
return map[string]string{}
|
||||
}
|
||||
result := map[string]string{}
|
||||
for i, name := range logRegexp.SubexpNames() {
|
||||
result[name] = string(m[i])
|
||||
}
|
||||
return result
|
||||
}
|
116
vendor/github.com/go-kit/kit/log/sync.go
generated
vendored
Normal file
116
vendor/github.com/go-kit/kit/log/sync.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// SwapLogger wraps another logger that may be safely replaced while other
|
||||
// goroutines use the SwapLogger concurrently. The zero value for a SwapLogger
|
||||
// will discard all log events without error.
|
||||
//
|
||||
// SwapLogger serves well as a package global logger that can be changed by
|
||||
// importers.
|
||||
type SwapLogger struct {
|
||||
logger atomic.Value
|
||||
}
|
||||
|
||||
type loggerStruct struct {
|
||||
Logger
|
||||
}
|
||||
|
||||
// Log implements the Logger interface by forwarding keyvals to the currently
|
||||
// wrapped logger. It does not log anything if the wrapped logger is nil.
|
||||
func (l *SwapLogger) Log(keyvals ...interface{}) error {
|
||||
s, ok := l.logger.Load().(loggerStruct)
|
||||
if !ok || s.Logger == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Log(keyvals...)
|
||||
}
|
||||
|
||||
// Swap replaces the currently wrapped logger with logger. Swap may be called
|
||||
// concurrently with calls to Log from other goroutines.
|
||||
func (l *SwapLogger) Swap(logger Logger) {
|
||||
l.logger.Store(loggerStruct{logger})
|
||||
}
|
||||
|
||||
// NewSyncWriter returns a new writer that is safe for concurrent use by
|
||||
// multiple goroutines. Writes to the returned writer are passed on to w. If
|
||||
// another write is already in progress, the calling goroutine blocks until
|
||||
// the writer is available.
|
||||
//
|
||||
// If w implements the following interface, so does the returned writer.
|
||||
//
|
||||
// interface {
|
||||
// Fd() uintptr
|
||||
// }
|
||||
func NewSyncWriter(w io.Writer) io.Writer {
|
||||
switch w := w.(type) {
|
||||
case fdWriter:
|
||||
return &fdSyncWriter{fdWriter: w}
|
||||
default:
|
||||
return &syncWriter{Writer: w}
|
||||
}
|
||||
}
|
||||
|
||||
// syncWriter synchronizes concurrent writes to an io.Writer.
|
||||
type syncWriter struct {
|
||||
sync.Mutex
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// Write writes p to the underlying io.Writer. If another write is already in
|
||||
// progress, the calling goroutine blocks until the syncWriter is available.
|
||||
func (w *syncWriter) Write(p []byte) (n int, err error) {
|
||||
w.Lock()
|
||||
n, err = w.Writer.Write(p)
|
||||
w.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// fdWriter is an io.Writer that also has an Fd method. The most common
|
||||
// example of an fdWriter is an *os.File.
|
||||
type fdWriter interface {
|
||||
io.Writer
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
// fdSyncWriter synchronizes concurrent writes to an fdWriter.
|
||||
type fdSyncWriter struct {
|
||||
sync.Mutex
|
||||
fdWriter
|
||||
}
|
||||
|
||||
// Write writes p to the underlying io.Writer. If another write is already in
|
||||
// progress, the calling goroutine blocks until the fdSyncWriter is available.
|
||||
func (w *fdSyncWriter) Write(p []byte) (n int, err error) {
|
||||
w.Lock()
|
||||
n, err = w.fdWriter.Write(p)
|
||||
w.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// syncLogger provides concurrent safe logging for another Logger.
|
||||
type syncLogger struct {
|
||||
mu sync.Mutex
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// NewSyncLogger returns a logger that synchronizes concurrent use of the
|
||||
// wrapped logger. When multiple goroutines use the SyncLogger concurrently
|
||||
// only one goroutine will be allowed to log to the wrapped logger at a time.
|
||||
// The other goroutines will block until the logger is available.
|
||||
func NewSyncLogger(logger Logger) Logger {
|
||||
return &syncLogger{logger: logger}
|
||||
}
|
||||
|
||||
// Log logs keyvals to the underlying Logger. If another log is already in
|
||||
// progress, the calling goroutine blocks until the syncLogger is available.
|
||||
func (l *syncLogger) Log(keyvals ...interface{}) error {
|
||||
l.mu.Lock()
|
||||
err := l.logger.Log(keyvals...)
|
||||
l.mu.Unlock()
|
||||
return err
|
||||
}
|
110
vendor/github.com/go-kit/kit/log/value.go
generated
vendored
Normal file
110
vendor/github.com/go-kit/kit/log/value.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Valuer generates a log value. When passed to With or WithPrefix in a
|
||||
// value element (odd indexes), it represents a dynamic value which is re-
|
||||
// evaluated with each log event.
|
||||
type Valuer func() interface{}
|
||||
|
||||
// bindValues replaces all value elements (odd indexes) containing a Valuer
|
||||
// with their generated value.
|
||||
func bindValues(keyvals []interface{}) {
|
||||
for i := 1; i < len(keyvals); i += 2 {
|
||||
if v, ok := keyvals[i].(Valuer); ok {
|
||||
keyvals[i] = v()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// containsValuer returns true if any of the value elements (odd indexes)
|
||||
// contain a Valuer.
|
||||
func containsValuer(keyvals []interface{}) bool {
|
||||
for i := 1; i < len(keyvals); i += 2 {
|
||||
if _, ok := keyvals[i].(Valuer); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Timestamp returns a timestamp Valuer. It invokes the t function to get the
|
||||
// time; unless you are doing something tricky, pass time.Now.
|
||||
//
|
||||
// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
|
||||
// are TimestampFormats that use the RFC3339Nano format.
|
||||
func Timestamp(t func() time.Time) Valuer {
|
||||
return func() interface{} { return t() }
|
||||
}
|
||||
|
||||
// TimestampFormat returns a timestamp Valuer with a custom time format. It
|
||||
// invokes the t function to get the time to format; unless you are doing
|
||||
// something tricky, pass time.Now. The layout string is passed to
|
||||
// Time.Format.
|
||||
//
|
||||
// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
|
||||
// are TimestampFormats that use the RFC3339Nano format.
|
||||
func TimestampFormat(t func() time.Time, layout string) Valuer {
|
||||
return func() interface{} {
|
||||
return timeFormat{
|
||||
time: t(),
|
||||
layout: layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A timeFormat represents an instant in time and a layout used when
|
||||
// marshaling to a text format.
|
||||
type timeFormat struct {
|
||||
time time.Time
|
||||
layout string
|
||||
}
|
||||
|
||||
func (tf timeFormat) String() string {
|
||||
return tf.time.Format(tf.layout)
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaller.
|
||||
func (tf timeFormat) MarshalText() (text []byte, err error) {
|
||||
// The following code adapted from the standard library time.Time.Format
|
||||
// method. Using the same undocumented magic constant to extend the size
|
||||
// of the buffer as seen there.
|
||||
b := make([]byte, 0, len(tf.layout)+10)
|
||||
b = tf.time.AppendFormat(b, tf.layout)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Caller returns a Valuer that returns a file and line from a specified depth
|
||||
// in the callstack. Users will probably want to use DefaultCaller.
|
||||
func Caller(depth int) Valuer {
|
||||
return func() interface{} {
|
||||
_, file, line, _ := runtime.Caller(depth)
|
||||
idx := strings.LastIndexByte(file, '/')
|
||||
// using idx+1 below handles both of following cases:
|
||||
// idx == -1 because no "/" was found, or
|
||||
// idx >= 0 and we want to start at the character after the found "/".
|
||||
return file[idx+1:] + ":" + strconv.Itoa(line)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultTimestamp is a Valuer that returns the current wallclock time,
|
||||
// respecting time zones, when bound.
|
||||
DefaultTimestamp = TimestampFormat(time.Now, time.RFC3339Nano)
|
||||
|
||||
// DefaultTimestampUTC is a Valuer that returns the current time in UTC
|
||||
// when bound.
|
||||
DefaultTimestampUTC = TimestampFormat(
|
||||
func() time.Time { return time.Now().UTC() },
|
||||
time.RFC3339Nano,
|
||||
)
|
||||
|
||||
// DefaultCaller is a Valuer that returns the file and line where the Log
|
||||
// method was invoked. It can only be used with log.With.
|
||||
DefaultCaller = Caller(3)
|
||||
)
|
4
vendor/github.com/go-logfmt/logfmt/.gitignore
generated
vendored
Normal file
4
vendor/github.com/go-logfmt/logfmt/.gitignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
_testdata/
|
||||
_testdata2/
|
||||
logfmt-fuzz.zip
|
||||
logfmt.test.exe
|
16
vendor/github.com/go-logfmt/logfmt/.travis.yml
generated
vendored
Normal file
16
vendor/github.com/go-logfmt/logfmt/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- "1.7.x"
|
||||
- "1.8.x"
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "tip"
|
||||
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
|
||||
script:
|
||||
- goveralls -service=travis-ci
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user