init
This commit is contained in:
		
							
								
								
									
										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. | ||||
|  | ||||
| [](https://travis-ci.org/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 ### | ||||
|  | ||||
| [](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
		Reference in New Issue
	
	Block a user