diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 6998741..0000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -ui/node_modules \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dd84ea7..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 44dabfd..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: Release Drago - -on: - push: - tags: - - "v*" - -jobs: - release: - name: Release - runs-on: ubuntu-latest - - steps: - - name: Checkout branch - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: 1.16 - - - name: Setup Node - uses: actions/setup-node@v2 - with: - node-version: '14' - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 - with: - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index be79ff0..0000000 --- a/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# Created by https://www.gitignore.io/api/go -# Edit at https://www.gitignore.io/?templates=go - -### Go ### -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -bin/ - -### Go Patch ### -vendor/ -Godeps/ - -# End of https://www.gitignore.io/api/go -ui/node_modules/* -ui/.eslintcache -ui/build/* -vendor/* - -!*.keep - -tmp -dist/ diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index 7c414fc..0000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,37 +0,0 @@ -before: - hooks: - - go mod tidy - - go generate ./... -builds: - - env: - - CGO_ENABLED=0 - - GO111MODULE=on - goos: - - linux - goarch: - - amd64 - - arm - - arm64 - ldflags: - - -s -w -extldflags "-static" - - -s -d -X version.Version={{ .Version }} -archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 - name_template: "{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" -checksum: - name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt" -snapshot: - name_template: "{{ .Tag }}-next" -release: - name_template: "v{{ .Version }}" -changelog: - sort: asc - filters: - exclude: - - '^docs:' - - '^test:' diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index e7e9ca0..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Debug Drago Development Mode", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "${workspaceFolder}", - "args": ["agent", "--dev"] - }, - { - "name": "Debug Drago Server", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "${workspaceFolder}", - "args": ["agent", "--config=./dist/server.hcl"] - }, - { - "name": "Debug Drago Client", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "${workspaceFolder}", - "args": ["agent", "--config=./dist/client1.hcl"] - }, - ] -} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 3e8ca7b..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at contact@seashell.sh. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a228b69..0000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - 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 2020 Seashell GmbH - - 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. diff --git a/Makefile b/Makefile deleted file mode 100644 index 95ff2fc..0000000 --- a/Makefile +++ /dev/null @@ -1,155 +0,0 @@ -SHELL = bash -PROJECT_ROOT := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) - -THIS_OS := $(shell uname | cut -d- -f1) -THIS_ARCH := $(shell uname -m) - -GIT_COMMIT := $(shell git rev-parse HEAD) -GIT_DIRTY := $(if $(shell git status --porcelain),+CHANGES) - -GO_LDFLAGS ?= -X=github.com/seashell/drago/version.GitCommit=$(GIT_COMMIT)$(GIT_DIRTY) - -CGO_ENABLED ?= 0 - -ifeq ($(CI),true) - $(info Running in a CI environment, verbose mode is disabled) -else - VERBOSE="true" -endif - -# List of supported OS -SUPPORTED_OSES = Linux - -# User defined flags -OS := $(or $(OS),$(O)) ## Define build target OS, e.g linux (coming soon) -ARCH := $(or $(ARCH),$(A)) ## Define build target architecture, e.g amd64 (coming soon) -STATIC := $(or $(STATIC),$(S)) ## If set to 1, build statically linked binary -DOCKER := $(or $(DOCKER),$(D)) ## If set to 1, run build within a Docker container - -# ---- Handle static builds -ifeq ($(STATIC),1) - GO_LDFLAGS := "${GO_LDFLAGS} -w -extldflags -static" -endif - -# ---- In case of a Dockerized build, check if the builder image is available. -ifeq ($(DOCKER),1) - HOST_USER ?= $(strip $(if $(USER),$(USER),nodummy)) - HOST_UID ?= $(strip $(if $(shell id -u),$(shell id -u),4000)) - DOCKER_BUILDER_IMAGE_AVAILABLE := $(shell docker images --filter LABEL=com.drago.builder=true -q) - BUILD_DOCKER_BUILDER_IMAGE_CMD := (docker build --label com.drago.builder=true --build-arg HOST_UID=${HOST_UID} --build-arg HOST_USER=${HOST_USER} -t drago_builder . -f ./build/Dockerfile.builder) -endif - -# =========== Targets =========== - -ifeq (Linux,$(THIS_OS)) -ALL_TARGETS = linux_amd64 -endif - -default: help - -# ====> Current platform -.PHONY: dev -dev: GOOS=$(shell go env GOOS) -dev: GOARCH=$(shell go env GOARCH) -dev: DEV_TARGET=$(GOOS)_$(GOARCH) -dev: ## Build for the current platform - @rm -rf $(PROJECT_ROOT)/bin - @$(MAKE) --no-print-directory $(DEV_TARGET) - -# ====> Container -.PHONY: container -container: ## Build container with the Drago binary inside - @$(MAKE) ui dev STATIC=1 - @echo "==> Building container image "drago:latest" ..." - @docker build -t drago:latest . -f ./build/Dockerfile.linux_amd64 - -# ====> All -.PHONY: all -all: clean ui $(foreach t,$(ALL_TARGETS),$(t)) ## Build all targets supported by this platform - @echo "==> Results:" - @tree --dirsfirst $(PROJECT_ROOT)/bin - -# ====> Tidy -.PHONY: tidy -tidy: - @echo "--> Tidying up Drago modules" - @go mod tidy - -# ====> Linux AMD 64 -.PHONY: linux_amd64 -linux_amd64: CMD='CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 \ - go build \ - -trimpath \ - -ldflags ""$(GO_LDFLAGS)"" \ - -o "bin/$@/drago"' -linux_amd64: $(SOURCE_FILES) ## Build Drago for linux/amd64 - @echo "==> Building $@..." - @echo "==> COMMAND $(CMD)..." -ifeq ($(DOCKER),1) -ifeq ($(DOCKER_BUILDER_IMAGE_AVAILABLE),) - @echo "==> Building Docker builder image..." - @$(call BUILD_DOCKER_BUILDER_IMAGE_CMD) -endif - docker run --rm -v ${PROJECT_ROOT}:${PROJECT_ROOT} --workdir=${PROJECT_ROOT} drago_builder \ - /bin/sh -c ${CMD} -else - @eval ${CMD} -endif - -# ====> Linux ARM 64 -.PHONY: linux/arm64 -linux/arm64: ## Build Drago for linux/arm64 (coming soon) - @echo "==> Coming soon..." - -# ====> Linux ARM -.PHONY: linux/arm -linux/arm: ## Build drago for linux/arm (coming soon) - @echo "==> Coming soon..." - -# ====> Web UI -.PHONY: ui -ui: CMD="go generate" -ui: ## Build Web UI - @echo "==> Building Web UI..." -ifeq ($(DOCKER),1) -ifeq ($(DOCKER_BUILDER_IMAGE_AVAILABLE),) - @echo "==> Building Docker builder image..." - @$(call BUILD_DOCKER_BUILDER_IMAGE_CMD) -endif - docker run --rm -v ${PROJECT_ROOT}:${PROJECT_ROOT} --workdir=${PROJECT_ROOT} drago_builder \ - /bin/sh -c ${CMD} -else - @eval ${CMD} -endif - -.PHONY: clean -clean: ## Remove build artifacts - @echo "==> Cleaning build artifacts..." - @rm -rf "$(PROJECT_ROOT)/bin/" - @rm -rf "$(PROJECT_ROOT)/ui/build/*" - @rm -rf "$(PROJECT_ROOT)/ui/node_modules/" - -HELP_FORMAT=" \033[36m%-25s\033[0m %s\n" -EG_FORMAT=" \033[36m%s\033[0m %s\n" - -.PHONY: help -help: ## Display usage information - @echo "Valid targets:" - @grep -E '^[^ ]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - sort | \ - awk 'BEGIN {FS = ":.*?## "}; \ - {printf $(HELP_FORMAT), $$1, $$2}' - @echo "" - @echo "This host will build the following targets if 'make release' is invoked:" - @echo $(ALL_TARGETS) | sed 's/^/ /' - @echo "" - @echo "Valid flags:" - @grep -E '^[^ ]+ :=.*?## .*$$' $(MAKEFILE_LIST) | \ - sort | \ - awk 'BEGIN {FS = " :=.*?## "}; \ - {printf $(HELP_FORMAT), $$1, $$2}' - @echo "" - @echo "Examples:" - @printf $(EG_FORMAT) "~${PWD}" "$$ make dev STATIC=1" - @printf $(EG_FORMAT) "~${PWD}" "$$ make ui dev DOCKER=1" - @printf $(EG_FORMAT) "~${PWD}" "$$ make container DOCKER=1" diff --git a/README.md b/README.md deleted file mode 100644 index d6b9d73..0000000 --- a/README.md +++ /dev/null @@ -1,231 +0,0 @@ -


- -
- Drago -

- -
-A flexible configuration manager for WireGuard networks -
- ------------------- - -

- Go report: A+ - GitHub - Gitter -

- -Drago is a flexible configuration manager for WireGuard designed to make it simple to configure secure network overlays spanning heterogeneous nodes distributed across different clouds and physical locations. - -In the media: -- [Linux Unplugged: What's up with WireGuard?](https://linuxunplugged.com/418?t=1791) -- [Reddit thread on `r/selfhosted`](https://www.reddit.com/r/selfhosted/comments/ojp6lc/drago_securely_connect_devices_with_wireguard_and/) - -We are in active development and we welcome contributions from the open-source community. - -

- -

- -## Features - -- Single-binary, lightweight -- Encrypted node-to-node communication -- Support for multiple storage backends -- Support for multiple WireGuard implementations -- Dynamic network configuration -- Automatic key rotation -- Extensible via REST API -- Slick management dashboard -- Automatic IP assignment - -## Use cases -- Securely connect IoT devices -- Build your own cloud with Raspberry Pi's -- Connect services running on multiple cloud providers -- Manage access to sensitive services deployed to private hosts -- Expose development servers for debugging and demonstration purposes -- Secure home automation, SSH access, etc -- Establish secure VPNs for your company - -## Overview - -[WireGuard®](https://www.wireguard.com/) is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec. It also intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. Initially released for the Linux kernel, it is now cross-platform and widely deployable, being regarded as the most secure, easiest to use, and simplest VPN solution in the industry. - -WireGuard presents several advantages over other VPN solutions, but it does not allow for the dynamic configuration of network parameters such as IP addresses and firewall rules. Drago builds on top of WireGuard, allowing users to dynamically manage the configuration of their VPN networks, providing a unified control plane for overlays spanning containers, virtual machines, and IoT devices. - -## How it works - -Drago follows a client-server paradigm, in which a centralized server provides multiple clients running alongside WireGuard with their desired state. The desired state is periodically retrieved from the server and applied to WireGuard running on each node. In other words, the Drago server works as a gateway for accessing network configurations safely stored in a database. - -


- -

- -The Drago server exposes a comprehensive API through which these configurations can be retrieved and modified, implements authentication mechanisms to prevent unauthorized access, and provides a slick web UI to facilitate the process of managing and visualizing the state of the user-defined networks. - -The Drago client, in turn, runs on every node in the network, and is responsible for retrieving the most up-to-date configurations from the server through the API. Thanks to a simple reconciliation process, the Drago client then guarantees that the WireGuard configurations on each node always match the desired state stored in the database. When running in client mode, Drago also takes care of automatically generating key pairs for WireGuard, and sharing the public key so that nodes can always connect to each other. - -The only assumptions made by Drago is (i) that each node running a client has WireGuard available either as a kernel module or userspace application, and (ii) that the Drago server is reachable through the network. - -Drago does not enforce any specific network topology. Its sole responsibility is to distribute the desired configurations, and guarantee that they are correctly applied to WireGuard on every single client node. This means that it is up to the user to define how nodes are connected to each other and how the network should look like. - -Drago is meant to be simple, and provide a solid foundation for higher-level functionality. Need automatic IP assignment, dynamic firewall rules, or some kind of telemetry? Feel free to implement it on top of the already existing API. - -## Usage -``` -Usage: drago [--version] [--help] [options] - -Available commands: - acl Interact with ACL policies and tokens - agent Run a Drago agent - agent-info Display status information about the local agent - connection Interact with connections - interface Interact with interfaces - network Interact with networks - node Interact with nodes - ui Open the Drago web UI - version Print the Drago version -``` - -## Quickstart - -A Docker container is provided for those interested in building and running Drago without having to install anything in their systems. - -In order to perform a containerized build of Drago's Docker image, run: - -``` -$ make container DOCKER=1 -``` - -This will build a minimal Docker image containing the Drago binary. The `DOCKER` flag ensures that the build takes place within a Docker container, thus removing the entry barrier for potential users. - -Once the build process finishes, start the Drago agent in development mode with: - -``` -$ docker run -ti -p 8080:8080 drago agent --dev -``` - -You can now interact with the system through the Web UI, available at `http://localhost:8080/`. Alternatively, you can also interact with Drago through the command-line interface: - -``` -$ docker run --network host drago -``` - -## Development - -In order to develop Drago, your environment should meet the following requirements: -- Golang 1.16+ -- Node 10.17.0+ -- yarn 1.12.3+ - -For the sake of convenience, the Drago agent can be initialized in development mode, meaning that it will execute both the client and the server logic. To start the Drago agent in development mode, run: - -``` -$ go run main.go agent --dev -``` - -While the agent is running, Drago's UI will be accessible at `127.0.0.1:8080`. If you see a message instead of the UI, it means that it hasn't been built properly. You can easily build the UI project with: - -``` -$ go generate -``` - -Note that the `--dev` flag also configures the server to use the in-memory storage backend instead of `etcd`. Therefore, any state will be destroyed whenever the agent is stopped. - -To apply independent customizations to client and server, start them with: - -``` -$ go run main.go agent --client --config= -``` -and -``` -$ go run main.go agent --server --config= -``` - -We also provide the `air.sh` script, which makes use of `comstrek/air` to perform hot-reloading of the Drago agent. When running Drago through the `air.sh` script, the binary will be rebuilt and restarted whenever a change is detected in the codebase. - -#### Web UI - -The Drago web UI, can be launched independently from the binary so that developers can benefit from the covenient features offered by React's development server e.g., hot-reloading. To start Drago's UI in development mode and independently from the binary, `cd` into the `/ui/` directory and run: - -``` -$ yarn && yarn start -``` - -This will download all required dependencies, and launch React's development server. The UI can then be accessed at `http://localhost:3000/ui/`. - -While running the Web UI independently from the Drago binary is possible, this subproject still lacks a more sophisticated data mocking mechanism to allow for its independent testing. For those interested in contributing to the UI, we suggest that they first start the Drago agent, and then run the Web UI according to the instructions above. - - -## Build - -To build the Drago binary, run: -``` -$ go generate -$ go build -``` - -Alternatively, you can build Drago with `make`. To do so, run: -``` -$ make dev -``` - -In order to get a comprehensive list of build options, run: - ``` - $ make help - ``` - -## Contributing - -- Fork the project on GitHub -- Clone your fork: `git clone https://github.com/your_username/drago` -- Create a new branch: `git checkout -b my-new-feature` -- Make changes and stage them: `git add .` -- Commit your changes: `git commit -m 'Add some feature'` -- Push to the branch: `git push origin my-new-feature` -- Create a new pull request - -## Roadmap - -- [ ] Project: - - [x] Website - - [x] Documentation - - [ ] Code coverage - - [ ] E2E testing - -- [ ] Features: - - [x] RPC API - - [ ] Input validation - - [ ] Node pre-registration - - [ ] Drago server clustering - - [x] Fine-grained authorization - - [x] Etcd storage backend - - [x] Inmem storage backend - - [x] Backend API for issuing volatile tokens - - [ ] Integration of a plugin system - - [x] Integration with userspace WireGuard implementations - - [x] `WireGuard/wireguard-go` - - [x] `cloudflare/boringtun` - - [ ] `miragejs` mocks for testing the UI - - [ ] Client-side input validation - -- [ ] Improvements: - - [ ] Repository transactions - - [x] CLI improvements - -- [ ] Plugins: - - [ ] Meshing - - [ ] Leasing - - [ ] Admission - - [ ] Notification - -- [ ] Others: - - [ ] Vault plugin - - [ ] Terraform provider - - [ ] [go-discover](https://github.com/hashicorp/go-discover) provider - - -## License -Drago is released under the Apache 2.0 license. See [LICENSE](https://github.com/seashell/drago/blob/master/LICENSE). diff --git a/agent/adapter/http/acl.go b/agent/adapter/http/acl.go deleted file mode 100644 index 12034ca..0000000 --- a/agent/adapter/http/acl.go +++ /dev/null @@ -1,55 +0,0 @@ -package http - -import ( - "net/http" - - "github.com/seashell/drago/agent/conn" - structs "github.com/seashell/drago/drago/structs" -) - -// ACLHandler : -type ACLHandler struct { - rpcConn conn.RPCConnection -} - -// NewACLHandler : -func NewACLHandler(conn conn.RPCConnection) *ACLHandler { - return &ACLHandler{ - rpcConn: conn, - } -} - -// Handle : -func (h *ACLHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - params := parsePathParams(req) - if len(params) > 1 { - return nil, NewCodedError(404, ErrNotFound) - } - - switch params[0] { - case "bootstrap": - return h.handleBootstrap(rw, req) - default: - return nil, NewCodedError(404, "Not found") - } - -} - -func (h *ACLHandler) handleBootstrap(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - if req.Method != "POST" { - return nil, NewCodedError(405, ErrMethodNotAllowed) - } - - args := structs.ACLBootstrapRequest{ - WriteRequest: parseWriteRequestOptions(req), - } - - var out structs.ACLTokenUpsertResponse - if err := h.rpcConn.Call("ACL.BootstrapACL", &args, &out); err != nil { - return nil, parseError(err) - } - - return out.ACLToken, nil -} diff --git a/agent/adapter/http/acl_policy.go b/agent/adapter/http/acl_policy.go deleted file mode 100644 index 2ea546a..0000000 --- a/agent/adapter/http/acl_policy.go +++ /dev/null @@ -1,120 +0,0 @@ -package http - -import ( - "net/http" - - conn "github.com/seashell/drago/agent/conn" - structs "github.com/seashell/drago/drago/structs" -) - -// ACLPolicyHandler : -type ACLPolicyHandler struct { - rpcConn conn.RPCConnection -} - -// NewACLPolicyHandler : -func NewACLPolicyHandler(conn conn.RPCConnection) *ACLPolicyHandler { - return &ACLPolicyHandler{ - rpcConn: conn, - } -} - -// Handle : -func (h *ACLPolicyHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - params := parsePathParams(req) - if len(params) > 1 { - return nil, NewCodedError(404, ErrNotFound) - } - - policyName := params[0] - - switch req.Method { - case "GET": - return h.handleGet(rw, req, policyName) - case "POST": - return h.handlePost(rw, req, policyName) - case "DELETE": - return h.handleDelete(rw, req, policyName) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (h *ACLPolicyHandler) handleGet(rw http.ResponseWriter, req *http.Request, policyName string) (interface{}, error) { - - if policyName == "" { - return h.handleList(rw, req) - } - - args := structs.ACLPolicySpecificRequest{ - QueryOptions: parseQueryOptions(req), - Name: policyName, - } - - var out structs.SingleACLPolicyResponse - if err := h.rpcConn.Call("ACL.GetPolicy", &args, &out); err != nil { - return nil, parseError(err) - } - - return out.ACLPolicy, nil -} - -func (h *ACLPolicyHandler) handleList(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - args := &structs.ACLPolicyListRequest{ - QueryOptions: parseQueryOptions(req), - } - - var out structs.ACLPolicyListResponse - if err := h.rpcConn.Call("ACL.ListPolicies", &args, &out); err != nil { - return nil, parseError(err) - } - - if out.Items == nil { - out.Items = make([]*structs.ACLPolicyListStub, 0) - } - - return out.Items, nil -} - -func (h *ACLPolicyHandler) handlePost(rw http.ResponseWriter, req *http.Request, policyName string) (interface{}, error) { - - var policy structs.ACLPolicy - err := parseBody(req.Body, &policy) - if err != nil { - return nil, NewCodedError(500, ErrInternal, err) - } - - // Make sure the policy name matches - if policy.Name != policyName { - return nil, NewCodedError(400, "ACL policy name does not match request path") - } - - args := &structs.ACLPolicyUpsertRequest{ - ACLPolicy: &policy, - WriteRequest: parseWriteRequestOptions(req), - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("ACL.UpsertACLPolicy", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} - -func (h *ACLPolicyHandler) handleDelete(rw http.ResponseWriter, req *http.Request, policyName string) (interface{}, error) { - - args := structs.ACLPolicyDeleteRequest{ - WriteRequest: parseWriteRequestOptions(req), - Names: []string{policyName}, - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("ACL.DeleteACLPolicy", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} diff --git a/agent/adapter/http/acl_token.go b/agent/adapter/http/acl_token.go deleted file mode 100644 index d18a9f8..0000000 --- a/agent/adapter/http/acl_token.go +++ /dev/null @@ -1,147 +0,0 @@ -package http - -import ( - "net/http" - - conn "github.com/seashell/drago/agent/conn" - structs "github.com/seashell/drago/drago/structs" -) - -// ACLTokenHandler : -type ACLTokenHandler struct { - rpcConn conn.RPCConnection -} - -// NewACLTokenHandler : -func NewACLTokenHandler(conn conn.RPCConnection) *ACLTokenHandler { - return &ACLTokenHandler{ - rpcConn: conn, - } -} - -// Handle : -func (h *ACLTokenHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - params := parsePathParams(req) - if len(params) > 1 { - return nil, NewCodedError(404, ErrNotFound) - } - - tokenID := params[0] - - switch req.Method { - case "GET": - return h.handleGet(rw, req, tokenID) - case "POST": - return h.handlePost(rw, req, tokenID) - case "DELETE": - return h.handleDelete(rw, req, tokenID) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (h *ACLTokenHandler) handleGet(rw http.ResponseWriter, req *http.Request, tokenID string) (interface{}, error) { - - if tokenID == "" { - return h.handleList(rw, req) - } - - if tokenID == "self" { - return h.handleGetSelf(rw, req) - } - - args := structs.ACLTokenSpecificRequest{ - QueryOptions: parseQueryOptions(req), - ACLTokenID: tokenID, - } - - var out structs.SingleACLTokenResponse - if err := h.rpcConn.Call("ACL.GetToken", &args, &out); err != nil { - return nil, parseError(err) - } - - if out.ACLToken == nil { - return nil, NewCodedError(404, "ACL token not found") - } - - return out.ACLToken, nil -} - -func (h *ACLTokenHandler) handleGetSelf(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - args := structs.ResolveACLTokenRequest{ - QueryOptions: parseQueryOptions(req), - Secret: parseAuthToken(req), - } - - var out structs.SingleACLTokenResponse - if err := h.rpcConn.Call("ACL.ResolveToken", &args, &out); err != nil { - return nil, parseError(err) - } - - if out.ACLToken == nil { - return nil, NewCodedError(404, "ACL token not found") - } - - return out.ACLToken, nil -} - -func (h *ACLTokenHandler) handleList(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - args := &structs.ACLTokenListRequest{ - QueryOptions: parseQueryOptions(req), - } - - var out structs.ACLTokenListResponse - if err := h.rpcConn.Call("ACL.ListTokens", &args, &out); err != nil { - return nil, parseError(err) - } - - if out.Items == nil { - out.Items = make([]*structs.ACLTokenListStub, 0) - } - - return out.Items, nil -} - -func (h *ACLTokenHandler) handlePost(rw http.ResponseWriter, req *http.Request, tokenID string) (interface{}, error) { - - var token structs.ACLToken - err := parseBody(req.Body, &token) - if err != nil { - return nil, NewCodedError(400, ErrBadRequest, err) - } - - // Make sure the token ID matches - if token.ID != tokenID { - return nil, NewCodedError(400, "ACL token ID does not match request path") - } - - args := &structs.ACLTokenUpsertRequest{ - ACLToken: &token, - WriteRequest: parseWriteRequestOptions(req), - } - - var out structs.ACLTokenUpsertResponse - if err := h.rpcConn.Call("ACL.UpsertToken", &args, &out); err != nil { - return nil, parseError(err) - } - - return out.ACLToken, nil -} - -func (h *ACLTokenHandler) handleDelete(rw http.ResponseWriter, req *http.Request, tokenID string) (interface{}, error) { - - args := structs.ACLTokenDeleteRequest{ - WriteRequest: parseWriteRequestOptions(req), - ACLTokenIDs: []string{tokenID}, - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("ACL.DeleteToken", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} diff --git a/agent/adapter/http/agent.go b/agent/adapter/http/agent.go deleted file mode 100644 index f33ed91..0000000 --- a/agent/adapter/http/agent.go +++ /dev/null @@ -1,58 +0,0 @@ -package http - -import ( - "net/http" - - conn "github.com/seashell/drago/agent/conn" - structs "github.com/seashell/drago/drago/structs" -) - -type AgentAdapter interface { - Config() map[string]interface{} - Stats() map[string]map[string]string -} - -// AgentHandler provides an API for interacting with an Agent -// in runtime, getting stats and setting configs. -type AgentHandler struct { - agent AgentAdapter -} - -// NewAgentHandler : -func NewAgentHandler(conn conn.RPCConnection, ag AgentAdapter) *AgentHandler { - return &AgentHandler{ - agent: ag, - } -} - -// Handle : -func (h *AgentHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - params := parsePathParams(req) - if len(params) > 1 { - return nil, NewCodedError(404, ErrNotFound) - } - - id := params[0] - - switch req.Method { - case "GET": - return h.handleGet(rw, req, id) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (h *AgentHandler) handleGet(rw http.ResponseWriter, req *http.Request, id string) (interface{}, error) { - - if id != "self" { - return nil, NewCodedError(404, ErrNotFound) - } - - self := &structs.Agent{ - Config: map[string]interface{}{}, - Stats: h.agent.Stats(), - } - - return self, nil -} diff --git a/agent/adapter/http/connection.go b/agent/adapter/http/connection.go deleted file mode 100644 index ba1034e..0000000 --- a/agent/adapter/http/connection.go +++ /dev/null @@ -1,123 +0,0 @@ -package http - -import ( - "net/http" - - "github.com/seashell/drago/agent/conn" - structs "github.com/seashell/drago/drago/structs" -) - -// ConnectionHandler : -type ConnectionHandler struct { - rpcConn conn.RPCConnection -} - -// NewConnectionHandler : -func NewConnectionHandler(conn conn.RPCConnection) *ConnectionHandler { - return &ConnectionHandler{ - rpcConn: conn, - } -} - -// Handle : -func (h *ConnectionHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - params := parsePathParams(req) - if len(params) > 1 { - return nil, NewCodedError(404, ErrNotFound) - } - - connID := params[0] - - switch req.Method { - case "GET": - return h.handleGet(rw, req, connID) - case "PUT", "POST": - return h.handlePost(rw, req, connID) - case "DELETE": - return h.handleDelete(rw, req, connID) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (h *ConnectionHandler) handleGet(rw http.ResponseWriter, req *http.Request, connID string) (interface{}, error) { - - if connID == "" { - return h.handleList(rw, req) - } - - args := structs.ConnectionSpecificRequest{ - QueryOptions: parseQueryOptions(req), - ConnectionID: connID, - } - - var out structs.SingleConnectionResponse - if err := h.rpcConn.Call("Connection.GetConnection", &args, &out); err != nil { - return nil, parseError(err) - } - - return out.Connection, nil -} - -func (h *ConnectionHandler) handleList(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - args := &structs.ConnectionListRequest{ - QueryOptions: parseQueryOptions(req), - InterfaceID: req.URL.Query().Get("interface"), - NodeID: req.URL.Query().Get("node"), - NetworkID: req.URL.Query().Get("network"), - } - - var out structs.ConnectionListResponse - if err := h.rpcConn.Call("Connection.ListConnections", &args, &out); err != nil { - return nil, parseError(err) - } - - if out.Items == nil { - out.Items = make([]*structs.ConnectionListStub, 0) - } - - return out.Items, nil -} - -func (h *ConnectionHandler) handlePost(rw http.ResponseWriter, req *http.Request, ifaceID string) (interface{}, error) { - - var conn structs.Connection - err := parseBody(req.Body, &conn) - if err != nil { - return nil, NewCodedError(500, ErrInternal, err) - } - - // Make sure the interface ID matches - if conn.ID != ifaceID { - return nil, NewCodedError(400, "Connection ID does not match request path") - } - - args := &structs.ConnectionUpsertRequest{ - Connection: &conn, - WriteRequest: parseWriteRequestOptions(req), - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("Connection.UpsertConnection", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} - -func (h *ConnectionHandler) handleDelete(rw http.ResponseWriter, req *http.Request, connID string) (interface{}, error) { - - args := structs.ConnectionDeleteRequest{ - WriteRequest: parseWriteRequestOptions(req), - ConnectionIDs: []string{connID}, - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("Connection.DeleteConnection", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} diff --git a/agent/adapter/http/errors.go b/agent/adapter/http/errors.go deleted file mode 100644 index 74ae105..0000000 --- a/agent/adapter/http/errors.go +++ /dev/null @@ -1,44 +0,0 @@ -package http - -import ( - "fmt" -) - -const ( - ErrNotFound = "Not found" - ErrInternal = "Internal error" - ErrBadRequest = "Bad request" - ErrMethodNotAllowed = "Method not allowed" -) - -// CodedError represents an error generated by an HTTP handler. -// Besides implementing the standard error interface, it also -// implements the http.Error interface, which includes a method -// for obtaining the HTTP status code associated with the error. -type CodedError struct { - // Code is an HTTP status code associated with the error. - code int - - // Message is a human-readable message that describes the error. - Message string -} - -// NewCodedError : creates a new HTTPError object. -func NewCodedError(code int, s string, extra ...interface{}) CodedError { - msg := s - // TODO: only append extra to msg if DEBUG enabled - for _, v := range extra { - msg = fmt.Sprintf("%s : %v", msg, v) - } - return CodedError{code, msg} -} - -// Error returns a string representation for the Error type. -func (e CodedError) Error() string { - return fmt.Sprintf("%s", e.Message) -} - -// Code returns the HTTP status code associated with the error. -func (e CodedError) Code() int { - return e.code -} diff --git a/agent/adapter/http/fallthrough.go b/agent/adapter/http/fallthrough.go deleted file mode 100644 index 51d5a6f..0000000 --- a/agent/adapter/http/fallthrough.go +++ /dev/null @@ -1,34 +0,0 @@ -package http - -import ( - "net/http" -) - -// FallthroughHandler : -type FallthroughHandler struct { - redirectTo string -} - -// NewFallthroughHandler : -func NewFallthroughHandler(to string) *FallthroughHandler { - return &FallthroughHandler{redirectTo: to} -} - -// Handle : -func (a *FallthroughHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - switch req.Method { - case "GET": - return a.handleGet(rw, req) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (a *FallthroughHandler) handleGet(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - if req.URL.Path == "/" { - http.Redirect(rw, req, a.redirectTo, 307) - } else { - return nil, NewCodedError(404, ErrNotFound) - } - return nil, nil -} diff --git a/agent/adapter/http/interface.go b/agent/adapter/http/interface.go deleted file mode 100644 index 5a37b72..0000000 --- a/agent/adapter/http/interface.go +++ /dev/null @@ -1,122 +0,0 @@ -package http - -import ( - "net/http" - - "github.com/seashell/drago/agent/conn" - structs "github.com/seashell/drago/drago/structs" -) - -// InterfaceHandler : -type InterfaceHandler struct { - rpcConn conn.RPCConnection -} - -// NewInterfaceHandler : -func NewInterfaceHandler(conn conn.RPCConnection) *InterfaceHandler { - return &InterfaceHandler{ - rpcConn: conn, - } -} - -// Handle : -func (h *InterfaceHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - params := parsePathParams(req) - if len(params) > 1 { - return nil, NewCodedError(404, ErrNotFound) - } - - ifaceID := params[0] - - switch req.Method { - case "GET": - return h.handleGet(rw, req, ifaceID) - case "PUT", "POST": - return h.handlePost(rw, req, ifaceID) - case "DELETE": - return h.handleDelete(rw, req, ifaceID) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (h *InterfaceHandler) handleGet(rw http.ResponseWriter, req *http.Request, ifaceID string) (interface{}, error) { - - if ifaceID == "" { - return h.handleList(rw, req) - } - - args := structs.InterfaceSpecificRequest{ - QueryOptions: parseQueryOptions(req), - InterfaceID: ifaceID, - } - - var out structs.SingleInterfaceResponse - if err := h.rpcConn.Call("Interface.GetInterface", &args, &out); err != nil { - return nil, parseError(err) - } - - return out.Interface, nil -} - -func (h *InterfaceHandler) handleList(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - args := &structs.InterfaceListRequest{ - QueryOptions: parseQueryOptions(req), - NodeID: req.URL.Query().Get("node"), - NetworkID: req.URL.Query().Get("network"), - } - - var out structs.InterfaceListResponse - if err := h.rpcConn.Call("Interface.ListInterfaces", &args, &out); err != nil { - return nil, parseError(err) - } - - if out.Items == nil { - out.Items = make([]*structs.InterfaceListStub, 0) - } - - return out.Items, nil -} - -func (h *InterfaceHandler) handlePost(rw http.ResponseWriter, req *http.Request, ifaceID string) (interface{}, error) { - - var iface structs.Interface - err := parseBody(req.Body, &iface) - if err != nil { - return nil, NewCodedError(500, ErrInternal, err) - } - - // Make sure the interface ID matches - if iface.ID != ifaceID { - return nil, NewCodedError(400, "Interface ID does not match request path") - } - - args := &structs.InterfaceUpsertRequest{ - Interface: &iface, - WriteRequest: parseWriteRequestOptions(req), - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("Interface.UpsertInterface", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} - -func (h *InterfaceHandler) handleDelete(rw http.ResponseWriter, req *http.Request, ifaceID string) (interface{}, error) { - - args := structs.InterfaceDeleteRequest{ - WriteRequest: parseWriteRequestOptions(req), - InterfaceIDs: []string{ifaceID}, - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("Interface.DeleteInterface", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} diff --git a/agent/adapter/http/middleware/cors.go b/agent/adapter/http/middleware/cors.go deleted file mode 100644 index f04f756..0000000 --- a/agent/adapter/http/middleware/cors.go +++ /dev/null @@ -1,22 +0,0 @@ -package middleware - -import ( - "net/http" -) - -// CORS : -func CORS() func(http.HandlerFunc) http.HandlerFunc { - m := func(next http.HandlerFunc) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - rw.Header().Set("Access-Control-Allow-Origin", "*") - rw.Header().Set("Access-Control-Allow-Methods", "*") - rw.Header().Set("Access-Control-Allow-Headers", "Origin, Accept, Referer, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") - if req.Method == "OPTIONS" { - rw.WriteHeader(http.StatusOK) - return - } - next(rw, req) - } - } - return m -} diff --git a/agent/adapter/http/middleware/logging.go b/agent/adapter/http/middleware/logging.go deleted file mode 100644 index 80e0b49..0000000 --- a/agent/adapter/http/middleware/logging.go +++ /dev/null @@ -1,64 +0,0 @@ -package middleware - -import ( - "bufio" - "errors" - "net" - "net/http" - - "time" - - log "github.com/seashell/drago/pkg/log" -) - -// LoggingResponseWriter : -type LoggingResponseWriter struct { - http.ResponseWriter - code int -} - -// NewLoggingResponseWriter : -func NewLoggingResponseWriter(rw http.ResponseWriter) *LoggingResponseWriter { - return &LoggingResponseWriter{rw, http.StatusOK} -} - -// WriteHeader : -func (lrw *LoggingResponseWriter) WriteHeader(code int) { - lrw.code = code - lrw.ResponseWriter.WriteHeader(code) -} - -// Hijack : -func (lrw *LoggingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - h, ok := lrw.ResponseWriter.(http.Hijacker) - if !ok { - return nil, nil, errors.New("hijacking not supported") - } - return h.Hijack() -} - -// Logging : -func Logging(logger log.Logger) func(http.HandlerFunc) http.HandlerFunc { - - m := func(next http.HandlerFunc) http.HandlerFunc { - - return func(rw http.ResponseWriter, req *http.Request) { - - start := time.Now() - url := req.RequestURI - - logger.Debugf("Request received (remote=%s, method=%s, path=%s)", req.RemoteAddr, req.Method, url) - - var status int - defer func() { - logger.Debugf("Request completed with status %d (method=%s, path=%s, duration=%s)", status, req.Method, url, time.Now().Sub(start)) - }() - - lrw := NewLoggingResponseWriter(rw) - next(lrw, req) - status = lrw.code - } - } - - return m -} diff --git a/agent/adapter/http/network.go b/agent/adapter/http/network.go deleted file mode 100644 index 578e5c6..0000000 --- a/agent/adapter/http/network.go +++ /dev/null @@ -1,120 +0,0 @@ -package http - -import ( - "net/http" - - "github.com/seashell/drago/agent/conn" - structs "github.com/seashell/drago/drago/structs" -) - -// NetworkHandler : -type NetworkHandler struct { - rpcConn conn.RPCConnection -} - -// NewNetworkHandler : -func NewNetworkHandler(conn conn.RPCConnection) *NetworkHandler { - return &NetworkHandler{ - rpcConn: conn, - } -} - -// Handle : -func (h *NetworkHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - params := parsePathParams(req) - if len(params) > 1 { - return nil, NewCodedError(404, ErrNotFound) - } - - networkID := params[0] - - switch req.Method { - case "GET": - return h.handleGet(rw, req, networkID) - case "PUT", "POST": - return h.handlePost(rw, req, networkID) - case "DELETE": - return h.handleDelete(rw, req, networkID) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (h *NetworkHandler) handleGet(rw http.ResponseWriter, req *http.Request, networkID string) (interface{}, error) { - - if networkID == "" { - return h.handleList(rw, req) - } - - args := structs.NetworkSpecificRequest{ - QueryOptions: parseQueryOptions(req), - NetworkID: networkID, - } - - var out structs.SingleNetworkResponse - if err := h.rpcConn.Call("Network.GetNetwork", &args, &out); err != nil { - return nil, parseError(err) - } - - return out.Network, nil -} - -func (h *NetworkHandler) handleList(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - args := &structs.NetworkListRequest{ - QueryOptions: parseQueryOptions(req), - } - - var out structs.NetworkListResponse - if err := h.rpcConn.Call("Network.ListNetworks", &args, &out); err != nil { - return nil, parseError(err) - } - - if out.Items == nil { - out.Items = make([]*structs.NetworkListStub, 0) - } - - return out.Items, nil -} - -func (h *NetworkHandler) handlePost(rw http.ResponseWriter, req *http.Request, networkID string) (interface{}, error) { - - var network structs.Network - err := parseBody(req.Body, &network) - if err != nil { - return nil, NewCodedError(500, ErrInternal, err) - } - - // Make sure the network ID matches - if network.ID != networkID { - return nil, NewCodedError(400, "Network ID does not match request path") - } - - args := &structs.NetworkUpsertRequest{ - Network: &network, - WriteRequest: parseWriteRequestOptions(req), - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("Network.UpsertNetwork", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} - -func (h *NetworkHandler) handleDelete(rw http.ResponseWriter, req *http.Request, networkID string) (interface{}, error) { - - args := structs.NetworkDeleteRequest{ - WriteRequest: parseWriteRequestOptions(req), - NetworkIDs: []string{networkID}, - } - - var out structs.GenericResponse - if err := h.rpcConn.Call("Network.DeleteNetwork", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} diff --git a/agent/adapter/http/node.go b/agent/adapter/http/node.go deleted file mode 100644 index 1adfb49..0000000 --- a/agent/adapter/http/node.go +++ /dev/null @@ -1,105 +0,0 @@ -package http - -import ( - "net/http" - - "github.com/seashell/drago/agent/conn" - structs "github.com/seashell/drago/drago/structs" -) - -// NodeHandler : -type NodeHandler struct { - rpcConn conn.RPCConnection -} - -// NewNodeHandler : -func NewNodeHandler(conn conn.RPCConnection) *NodeHandler { - return &NodeHandler{ - rpcConn: conn, - } -} - -// Handle : -func (h *NodeHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - pathParams := parsePathParams(req) - if len(pathParams) > 1 { - return nil, NewCodedError(404, ErrNotFound) - } - - switch req.Method { - case "GET": - return h.handleGet(rw, req, pathParams) - case "PUT", "POST": - return h.handlePost(rw, req, pathParams) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (h *NodeHandler) handleGet(rw http.ResponseWriter, req *http.Request, pathParams []string) (interface{}, error) { - - nodeID := pathParams[0] - - if nodeID == "" { - return h.handleList(rw, req) - } - - args := structs.NodeSpecificRequest{ - QueryOptions: parseQueryOptions(req), - NodeID: nodeID, - } - - var out structs.SingleNodeResponse - if err := h.rpcConn.Call("Node.GetNode", &args, &out); err != nil { - return nil, parseError(err) - } - - return out.Node, nil -} - -func (h *NodeHandler) handleList(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - args := &structs.NodeListRequest{ - QueryOptions: parseQueryOptions(req), - } - - var out structs.NodeListResponse - if err := h.rpcConn.Call("Node.ListNodes", &args, &out); err != nil { - return nil, parseError(err) - } - - if out.Items == nil { - out.Items = make([]*structs.NodeListStub, 0) - } - - return out.Items, nil -} - -func (h *NodeHandler) handlePost(rw http.ResponseWriter, req *http.Request, pathParams []string) (interface{}, error) { - - nodeID := pathParams[0] - - var node structs.Node - err := parseBody(req.Body, &node) - if err != nil { - return nil, NewCodedError(500, ErrInternal, err) - } - - // Make sure the node ID matches - if node.ID != nodeID { - return nil, NewCodedError(400, "Node ID does not match request path") - } - - args := &structs.NodePreregisterRequest{ - Node: &node, - WriteRequest: parseWriteRequestOptions(req), - } - - var out structs.NodePreregisterResponse - if err := h.rpcConn.Call("Node.PreregisterNode", &args, &out); err != nil { - return nil, parseError(err) - } - - return nil, nil -} diff --git a/agent/adapter/http/spa.go b/agent/adapter/http/spa.go deleted file mode 100644 index 882c2f6..0000000 --- a/agent/adapter/http/spa.go +++ /dev/null @@ -1,46 +0,0 @@ -package http - -import ( - "net/http" - "strings" -) - -// SinglePageApplicationHandler : -type SinglePageApplicationHandler struct { - fsHandler http.Handler - placeholder string -} - -// NewSinglePageApplicationHandler : Create a new SPA handler that serves static files -// from a http.FileSystem passed as argument. If the latter is nil, serve a placeholder string. -func NewSinglePageApplicationHandler(fs http.FileSystem, s string) *SinglePageApplicationHandler { - - handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.Write([]byte(s)) - }) - - if fs != nil { - handler = http.FileServer(fs).ServeHTTP - } - - return &SinglePageApplicationHandler{ - placeholder: s, - fsHandler: handler, - } -} - -func (h *SinglePageApplicationHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - - path := req.URL.Path - - if path == "" || strings.HasPrefix(path, "/static") { - h.fsHandler.ServeHTTP(rw, req) - } else if path == "/" { - h.fsHandler.ServeHTTP(rw, req) - } else { - req.URL.Path = "/" - h.fsHandler.ServeHTTP(rw, req) - } - - return nil, nil -} diff --git a/agent/adapter/http/status.go b/agent/adapter/http/status.go deleted file mode 100644 index 4849d46..0000000 --- a/agent/adapter/http/status.go +++ /dev/null @@ -1,42 +0,0 @@ -package http - -import ( - "net/http" - - "github.com/seashell/drago/agent/conn" - "github.com/seashell/drago/drago/structs" -) - -// StatusHandler is used to check on server status -type StatusHandler struct { - rpcConn conn.RPCConnection -} - -// NewStatusHandler : -func NewStatusHandler(conn conn.RPCConnection) *StatusHandler { - return &StatusHandler{ - rpcConn: conn, - } -} - -// Handle : -func (h *StatusHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { - switch req.Method { - case "GET": - return h.handleGet(rw, req) - default: - return nil, NewCodedError(405, ErrMethodNotAllowed) - } -} - -func (h *StatusHandler) handleGet(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - - var args structs.GenericRequest - - var out structs.GenericResponse - if err := h.rpcConn.Call("Status.Ping", &args, &out); err != nil { - return nil, err - } - - return out, nil -} diff --git a/agent/adapter/http/util.go b/agent/adapter/http/util.go deleted file mode 100644 index 7187512..0000000 --- a/agent/adapter/http/util.go +++ /dev/null @@ -1,99 +0,0 @@ -package http - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "strconv" - "strings" - - "github.com/seashell/drago/drago/structs" -) - -const ( - paginationPageQueryKey = "page" - paginationPerPageQueryKey = "per_page" - defaultPaginationPage = 1 - defaultPaginationPerPage = 10 -) - -func parsePaginationQueryParams(query url.Values) (int, int) { - - var err error - var page, perPage int - - if page, err = strconv.Atoi(query.Get(paginationPageQueryKey)); err != nil { - page = defaultPaginationPage - } - - if perPage, err = strconv.Atoi(query.Get(paginationPerPageQueryKey)); err != nil { - perPage = defaultPaginationPerPage - } - - return page, perPage -} - -func parseBody(body io.ReadCloser, out interface{}) error { - defer body.Close() - - encoded, err := ioutil.ReadAll(body) - if err != nil { - return fmt.Errorf("error reading request body") - } - - if err := json.Unmarshal(encoded, out); err != nil { - return fmt.Errorf("error decoding request body") - } - - return nil -} - -func parsePathParams(req *http.Request) []string { - path := req.URL.Path - path = strings.TrimPrefix(path, "/") - path = strings.TrimSuffix(path, "/") - params := strings.Split(path, "/") - return params -} - -func parseAuthToken(req *http.Request) string { - return req.Header.Get("X-Drago-Token") -} - -func trimPathPrefix(req *http.Request, prefix string) *http.Request { - s := strings.TrimSuffix(strings.TrimPrefix(req.URL.Path, "/"), "/") - s = strings.TrimPrefix(s, prefix) - req.URL.Path = s - return req -} - -func parseQueryOptions(req *http.Request) structs.QueryOptions { - return structs.QueryOptions{ - AuthToken: parseAuthToken(req), - Filters: parseFilters(req), - } -} - -func parseFilters(req *http.Request) structs.Filters { - return structs.Filters(req.URL.Query()) -} - -func parseWriteRequestOptions(req *http.Request) structs.WriteRequest { - return structs.WriteRequest{ - AuthToken: parseAuthToken(req), - } -} - -func parseError(err error) error { - switch err.Error() { - case structs.ErrPermissionDenied.Error(): - return NewCodedError(403, err.Error()) - case structs.ErrNotFound.Error(): - return NewCodedError(404, err.Error()) - default: - return NewCodedError(500, err.Error()) - } -} diff --git a/agent/agent.go b/agent/agent.go deleted file mode 100644 index 3a05264..0000000 --- a/agent/agent.go +++ /dev/null @@ -1,299 +0,0 @@ -package agent - -import ( - "errors" - "fmt" - stdhttp "net/http" - "sync" - - handler "github.com/seashell/drago/agent/adapter/http" - middleware "github.com/seashell/drago/agent/adapter/http/middleware" - conn "github.com/seashell/drago/agent/conn" - client "github.com/seashell/drago/client" - drago "github.com/seashell/drago/drago" - config "github.com/seashell/drago/drago/structs/config" - http "github.com/seashell/drago/pkg/http" - log "github.com/seashell/drago/pkg/log" -) - -// Agent : -type Agent struct { - config *Config - logger log.Logger - - rpcConn conn.RPCConnection - - server *drago.Server - client *client.Client - - httpServer *http.Server - - shutdown bool - shutdownCh chan struct{} - shutdownLock sync.Mutex -} - -// New creates a new Drago agent from the configuration, -// potentially returning an error -func New(config *Config, logger log.Logger) (*Agent, error) { - - config = DefaultConfig().Merge(config) - - if logger == nil { - return nil, errors.New("missing logger") - } - - a := &Agent{ - config: config, - logger: logger.WithName("agent"), - shutdownCh: make(chan struct{}), - } - - if err := a.setupRPCConnection(); err != nil { - return nil, err - } - - // Setup Drago server - if err := a.setupServer(); err != nil { - return nil, err - } - - // Setup Drago client - if err := a.setupClient(); err != nil { - if a.server != nil { - a.server.Shutdown() - } - return nil, err - } - - // Make sure agent will be running at least as a client or as a server - if a.client == nil && a.server == nil { - return nil, errors.New("must have either client or server mode enabled") - } - - if err := a.setupHTTPServer(); err != nil { - a.Shutdown() - return nil, fmt.Errorf("could not initialize http server: %s", err) - } - - return a, nil -} - -// Stats returns a map containing relevant stats -func (a *Agent) Stats() map[string]map[string]string { - - stats := map[string]map[string]string{} - - if a.server != nil { - subStat := a.server.Stats() - for k, v := range subStat { - stats[k] = v - } - } - if a.client != nil { - subStat := a.client.Stats() - for k, v := range subStat { - stats[k] = v - } - } - - return stats -} - -// Config returns a copy of the agent's Config struct -func (a *Agent) Config() map[string]interface{} { - config := map[string]interface{}{} - // TODO populate config map - return config -} - -// Shutdown is used to terminate the agent. -func (a *Agent) Shutdown() error { - - a.shutdownLock.Lock() - defer a.shutdownLock.Unlock() - - if a.shutdown { - return nil - } - - a.logger.Infof("requesting shutdown") - - if a.client != nil { - if err := a.client.Shutdown(); err != nil { - a.logger.Errorf("client shutdown failed: %s", err.Error()) - } - } - if a.server != nil { - if err := a.server.Shutdown(); err != nil { - a.logger.Errorf("server shutdown failed: %s", err.Error()) - } - } - - a.logger.Infof("agent shutdown complete") - - a.shutdown = true - close(a.shutdownCh) - - return nil -} - -func (a *Agent) setupRPCConnection() error { - - address := "" - - if a.config.Server.Enabled { - address = fmt.Sprintf("%s:%d", a.config.BindAddr, a.config.Ports.RPC) - } else { - address = a.config.Client.Servers[0] - } - - a.rpcConn = conn.NewRPCConnection(address, a.logger) - - return nil -} - -// Setup Drago server, if enabled -func (a *Agent) setupServer() error { - - if !a.config.Server.Enabled { - return nil - } - - config, err := a.serverConfig() - if err != nil { - return fmt.Errorf("server config setup failed: %v", err) - } - - server, err := drago.NewServer(config) - if err != nil { - return fmt.Errorf("server setup failed: %v", err) - } - - a.server = server - - return nil -} - -// serverConfig creates a new drago.Config struct based on an -// agent.Config struct and which can be used to initialize -// a Drago server -func (a *Agent) serverConfig() (*drago.Config, error) { - - c := drago.DefaultConfig() - - c.UI = a.config.UI - c.DevMode = *a.config.DevMode - c.BindAddr = a.config.BindAddr - c.DataDir = a.config.DataDir - - c.Ports = &drago.Ports{ - HTTP: a.config.Ports.HTTP, - RPC: a.config.Ports.RPC, - } - - c.ACL = &config.ACLConfig{ - Enabled: a.config.ACL.Enabled, - } - - c.LogLevel = a.config.LogLevel - c.Logger = a.logger - - return c, nil -} - -// Setup Drago client, if enabled -func (a *Agent) setupClient() error { - - if !a.config.Client.Enabled { - return nil - } - - config, err := a.clientConfig() - if err != nil { - return fmt.Errorf("client config setup failed: %v", err) - } - - client, err := client.New(a.rpcConn, config) - if err != nil { - return fmt.Errorf("client setup failed: %v", err) - } - - a.client = client - - return nil -} - -// clientConfig creates a new client.Config struct based on an -// agent.Config struct and which can be used to initialize -// a Drago client -func (a *Agent) clientConfig() (*client.Config, error) { - c := client.DefaultConfig() - - c.Name = a.config.Name - - c.Servers = a.config.Client.Servers - c.StateDir = a.config.Client.StateDir - - c.AdvertiseAddress = a.config.AdvertiseAddrs.Peer - - c.WireguardPath = a.config.Client.WireguardPath - c.InterfacesPrefix = a.config.Client.InterfacesPrefix - - c.Meta = a.config.Client.Meta - - c.LogLevel = a.config.LogLevel - c.Logger = a.logger - - return c, nil -} - -func (a *Agent) setupHTTPServer() error { - - config := &http.Config{ - Logger: a.logger, - BindAddress: fmt.Sprintf("%s:%d", a.config.BindAddr, a.config.Ports.HTTP), - Handlers: map[string]http.Handler{ - "/api/agent/": handler.NewAgentHandler(a.rpcConn, a), - "/api/nodes/": handler.NewNodeHandler(a.rpcConn), - "/api/interfaces/": handler.NewInterfaceHandler(a.rpcConn), - "/api/connections/": handler.NewConnectionHandler(a.rpcConn), - "/api/networks/": handler.NewNetworkHandler(a.rpcConn), - "/api/acl/": handler.NewACLHandler(a.rpcConn), - "/api/acl/tokens/": handler.NewACLTokenHandler(a.rpcConn), - "/api/acl/policies/": handler.NewACLPolicyHandler(a.rpcConn), - "/status": handler.NewStatusHandler(a.rpcConn), - }, - Middleware: []http.Middleware{ - middleware.CORS(), - middleware.Logging(a.logger), - }, - } - - if a.config.UI { - - spaFS := a.config.StaticFS - if !fsContains(spaFS, "index.html") { - spaFS = nil - } - - config.Handlers["/ui/"] = handler.NewSinglePageApplicationHandler(spaFS, uiStubHTML) - config.Handlers["/"] = handler.NewFallthroughHandler("/ui/") - } - - httpServer, err := http.NewServer(config) - if err != nil { - return err - } - - a.httpServer = httpServer - - return nil -} - -func fsContains(fs stdhttp.FileSystem, s string) bool { - if _, err := fs.Open("index.html"); err != nil { - return false - } - return true -} diff --git a/agent/banner.go b/agent/banner.go deleted file mode 100644 index b77a2cc..0000000 --- a/agent/banner.go +++ /dev/null @@ -1,20 +0,0 @@ -package agent - -import ( - "fmt" - - "github.com/seashell/drago/version" -) - -// Banner is a banner to be displayed when the Drago -// agent is started -var Banner = fmt.Sprintf(` -====|===================> -___ ____ ____ ____ ____ -| \ |__/ |__| | __ | | -|__/ | \ | | |__] |__| - - {{ .AnsiColor.Cyan }}%s{{ .AnsiColor.Default }} -<===================|==== - -`, version.GetVersion().VersionNumber()) diff --git a/agent/config.go b/agent/config.go deleted file mode 100644 index d054cfb..0000000 --- a/agent/config.go +++ /dev/null @@ -1,343 +0,0 @@ -package agent - -import ( - "net/http" - "os" - "time" - - "github.com/hashicorp/hcl/v2/hclsimple" - "github.com/seashell/drago/pkg/util" - "github.com/seashell/drago/version" -) - -// Config contains configurations for the Drago agent -type Config struct { - - // StaticFS contains UI bundle to be server by the Drago agent. - StaticFS http.FileSystem - - // UI defines whether or not Drago's web UI will be served - // by the agent. Defaults to true. - UI bool `hcl:"ui,optional"` - - // Name is used to identify individual agents. - Name string `hcl:"name,optional"` - - // DataDir is the directory to store our state in. - // If not specified, this defaults to /etc/drago. - DataDir string `hcl:"data_dir,optional"` - - // PluginDir is the directory where plugins are loaded from. - // If not specified, this defaults to /plugins. - PluginDir string `hcl:"plugin_dir,optional"` - - // BindAddr is the address on which all of Drago's services will - // be bound. If not specified, this defaults to 127.0.0.1. - BindAddr string `hcl:"bind_addr,optional"` - - // AdvertiseAddrs is a struct containing the addresses advertised - // for each of Drago's network services in host:port format. - // It is optional, and all addresses default to the bind address - // with the default port corresponding to each service. - AdvertiseAddrs *AdvertiseAddrs `hcl:"advertise,block"` - - // LogLevel is the level of the logs to put out - LogLevel string `hcl:"log_level,optional"` - - // Ports is used to control the network ports we bind to. - Ports *Ports `hcl:"ports,block"` - - // Server contains all server-specific configurations - Server *ServerConfig `hcl:"server,block"` - - // Client contains all client-specific configurations - Client *ClientConfig `hcl:"client,block"` - - // ACL contains all ACL configurations - ACL *ACLConfig `hcl:"acl,block"` - - // DevMode is set by the --dev CLI flag. - DevMode *bool - - // Version information (set at compilation time) - Version *version.VersionInfo -} - -// Merge merges two Config structs, returning the result -func (c *Config) Merge(b *Config) *Config { - - if b == nil { - return c - } - - result := *c - - if b.DevMode != nil { - result.DevMode = b.DevMode - } - if b.StaticFS != nil { - result.StaticFS = b.StaticFS - } - if b.UI { - result.UI = true - } - if b.LogLevel != "" { - result.LogLevel = b.LogLevel - } - if b.Name != "" { - result.Name = b.Name - } - if b.DataDir != "" { - result.DataDir = b.DataDir - } - if b.BindAddr != "" { - result.BindAddr = b.BindAddr - } - if b.Version != nil { - result.Version = b.Version - } - - // Apply the ports config - if result.Ports == nil && b.Ports != nil { - ports := *b.Ports - result.Ports = &ports - } else if b.Ports != nil { - result.Ports = result.Ports.Merge(b.Ports) - } - - // Apply the client config - if result.Client == nil && b.Client != nil { - client := *b.Client - result.Client = &client - } else if b.Client != nil { - result.Client = result.Client.Merge(b.Client) - } - - // Apply the server config - if result.Server == nil && b.Server != nil { - server := *b.Server - result.Server = &server - } else if b.Server != nil { - result.Server = result.Server.Merge(b.Server) - } - - // Apply the ACL config - if result.ACL == nil && b.ACL != nil { - acl := *b.ACL - result.ACL = &acl - } else if b.ACL != nil { - result.ACL = result.ACL.Merge(b.ACL) - } - - // Apply the advertise addrs config - if result.AdvertiseAddrs == nil && b.AdvertiseAddrs != nil { - advertise := *b.AdvertiseAddrs - result.AdvertiseAddrs = &advertise - } else if b.AdvertiseAddrs != nil { - result.AdvertiseAddrs = result.AdvertiseAddrs.Merge(b.AdvertiseAddrs) - } - - return &result -} - -// ServerConfig contains configurations for the Drago server -type ServerConfig struct { - // Enabled controls if the agent is a server - Enabled bool `hcl:"enabled,optional"` -} - -// Merge merges two ServerConfig structs, returning the result -func (c *ServerConfig) Merge(b *ServerConfig) *ServerConfig { - result := *c - - if b.Enabled { - result.Enabled = true - } - return &result -} - -// ClientConfig contains configurations for the Drago client -type ClientConfig struct { - // Enabled controls if the agent is a client - Enabled bool `hcl:"enabled,optional"` - - // Server is the address of a known Drago server in "host:port" format - Servers []string `hcl:"servers,optional"` - - // StateDir is the directory where the client state will be kept - StateDir string `hcl:"state_dir,optional"` - - // InterfacesPrefix is the prefix that will be added to all WireGuard - // interfaces managed by Drago - InterfacesPrefix string `hcl:"interfaces_prefix,optional"` - - // WireguardPath is the path to a userspace WireGuard binary, if available - WireguardPath string `hcl:"wireguard_path,optional"` - - // Meta contains metadata about the client node - Meta map[string]string `hcl:"meta,optional"` - - // SyncInterval controls how frequently the client synchronizes its state - SyncInterval time.Duration `hcl:"sync_interval,optional"` -} - -// Merge merges two ClientConfig structs, returning the result -func (c *ClientConfig) Merge(b *ClientConfig) *ClientConfig { - result := *c - - if b.Enabled { - result.Enabled = true - } - if b.Servers != nil { - result.Servers = b.Servers - } - if b.StateDir != "" { - result.StateDir = b.StateDir - } - if b.WireguardPath != "" { - result.WireguardPath = b.WireguardPath - } - if b.InterfacesPrefix != "" { - result.InterfacesPrefix = b.InterfacesPrefix - } - if b.SyncInterval != 0 { - result.SyncInterval = b.SyncInterval - } - if b.Meta != nil { - result.Meta = b.Meta - } - - return &result -} - -// ACLConfig contains configuration for Drago's ACL -type ACLConfig struct { - // Enabled controls if the ACLs are managed and enforced - Enabled bool `hcl:"enabled,optional"` -} - -// Merge merges two ACLConfig structs, returning the result -func (c *ACLConfig) Merge(b *ACLConfig) *ACLConfig { - result := *c - if b.Enabled { - result.Enabled = true - } - return &result -} - -// Ports encapsulates the various ports we bind to for network services. If any -// are not specified then the defaults are used instead. -type Ports struct { - HTTP int `hcl:"http,optional"` - RPC int `hcl:"rpc,optional"` -} - -// Merge merges two Ports structs, returning the result -func (c *Ports) Merge(b *Ports) *Ports { - result := *c - - if b.HTTP != 0 { - result.HTTP = b.HTTP - } - if b.RPC != 0 { - result.RPC = b.RPC - } - - return &result -} - -// AdvertiseAddrs is used to control the addresses a Drago node advertises. -// All are optional and default to BindAddr and their default Port. -// Expected format is address:port. -type AdvertiseAddrs struct { - // Peer is the address advertised for the purpose - // of letting other nodes connect to this node. It - // will be used as the WireGuard's endpoint configuration. - Peer string `hcl:"peer,optional"` - - // Server contains the address to be advertised for - // the purpose of clustering with other servers. - Server string `hcl:"server,optional"` -} - -// Merge merges two AdvertiseAddrs structs, returning the result -func (c *AdvertiseAddrs) Merge(b *AdvertiseAddrs) *AdvertiseAddrs { - result := *c - - if b.Peer != "" { - result.Peer = b.Peer - } - if b.Server != "" { - result.Server = b.Server - } - - return &result -} - -// DefaultConfig returns a Config struct populated with sane defaults -func DefaultConfig() *Config { - return &Config{ - LogLevel: "DEBUG", - UI: true, - DevMode: util.BoolToPtr(false), - Name: "", - DataDir: "/tmp/drago", - BindAddr: "0.0.0.0", - Ports: &Ports{ - HTTP: 8080, - RPC: 8081, - }, - AdvertiseAddrs: &AdvertiseAddrs{}, - Server: &ServerConfig{ - Enabled: false, - }, - Client: &ClientConfig{ - Enabled: false, - Servers: []string{"127.0.0.1:8081"}, - Meta: map[string]string{}, - InterfacesPrefix: "drago", - WireguardPath: "", - SyncInterval: 5, - }, - ACL: &ACLConfig{ - Enabled: false, - }, - Version: version.GetVersion(), - } -} - -// EmptyConfig returns an empty Config struct with all nested structs -// also initialized to a non-nil empty value. -func EmptyConfig() *Config { - return &Config{ - Ports: &Ports{}, - AdvertiseAddrs: &AdvertiseAddrs{}, - Server: &ServerConfig{}, - Client: &ClientConfig{}, - ACL: &ACLConfig{}, - } -} - -// Validate returns an error in case a Config struct is invalid. -func (c *Config) Validate() error { - // TODO: implement validation - return nil -} - -// LoadFromFile loads the configuration from a given path -func (c *Config) LoadFromFile(path string) (*Config, error) { - - _, err := os.Stat(path) - if err != nil { - return nil, err - } - - config := &Config{} - - err = hclsimple.DecodeFile(path, nil, config) - if err != nil { - return nil, err - } - - return config, nil -} diff --git a/agent/conn/conn.go b/agent/conn/conn.go deleted file mode 100644 index d0674a8..0000000 --- a/agent/conn/conn.go +++ /dev/null @@ -1,78 +0,0 @@ -package conn - -import ( - "errors" - "sync" - "time" - - log "github.com/seashell/drago/pkg/log" - rpc "github.com/seashell/drago/pkg/rpc" -) - -const ( - errNoServers = "no servers" -) - -var ( - ErrNoServers = errors.New(errNoServers) -) - -type RPCConnection interface { - Call(method string, args interface{}, reply interface{}) error -} - -type rpcConnection struct { - logger log.Logger - client *rpc.Client - address string - sync.Mutex -} - -func NewRPCConnection(address string, logger log.Logger) RPCConnection { - return &rpcConnection{ - address: address, - logger: logger, - } -} - -// Call calls a RPC method on a remote server using the clients RPC client, establishing -// the connection if it's being used for the first time, of if it has been disconnected. -func (c *rpcConnection) Call(method string, args interface{}, reply interface{}) error { - - // Get a cached client or create a new one - client, err := c.getRPCClient() - if err != nil { - return ErrNoServers - } - - if err := client.Call(method, args, reply); err != nil { - c.client = nil - return err - } - - return nil -} - -func (c *rpcConnection) getRPCClient() (*rpc.Client, error) { - - c.Lock() - defer c.Unlock() - - if c.client != nil { - return c.client, nil - } - - client, err := rpc.NewClient(&rpc.ClientConfig{ - Logger: c.logger, - Address: c.address, - DialTimeout: 3 * time.Second, - }) - - if err != nil { - return nil, err - } - - c.client = client - - return c.client, nil -} diff --git a/agent/conn/conn_test.go b/agent/conn/conn_test.go deleted file mode 100644 index bc90eaa..0000000 --- a/agent/conn/conn_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package conn - -import ( - "fmt" - "testing" -) - -func TestCreateConn(t *testing.T) { - - conn := NewRPCConnection("127.0.0.1:8081", nil) - - if err := conn.Call("TestMethod", struct{}{}, nil); err != nil { - fmt.Println(err) - } - - t.Log("success!") - -} diff --git a/agent/ui.go b/agent/ui.go deleted file mode 100644 index 78fdb8f..0000000 --- a/agent/ui.go +++ /dev/null @@ -1,18 +0,0 @@ -package agent - -const uiStubHTML = ` - -
-====|===================>
-___  ____ ____ ____ ____ 
-|  \ |__/ |__| | __ |  | 
-|__/ |  \ |  | |__] |__| 
-		   
-<===================|====
-
- -

If you're seeing this message, it means that Drago's web UI was not built properly.

-

In order to build it, run go generate from the project root directory.

- - -` diff --git a/air.conf b/air.conf deleted file mode 100644 index 5c1003a..0000000 --- a/air.conf +++ /dev/null @@ -1,47 +0,0 @@ -# Config file for [Air](https://github.com/cosmtrek/air) in TOML format - -# Working directory -# . or absolute path, please note that the directories following must be under root. -root = "." -tmp_dir = "tmp" - -[build] -# Just plain old shell command. You could use `make` as well. -cmd = "go build -o ./tmp/air/drago ." -# Binary file yields from `cmd`. -bin = "tmp/air/drago agent" -# Customize binary. -full_bin = "APP_ENV=dev APP_USER=air ./tmp/air/drago agent --dev" -# Watch these filename extensions. -include_ext = ["go", "tpl", "tmpl", "html"] -# Ignore these filename extensions or directories. -exclude_dir = ["assets", "tmp", "vendor", "ui", "data"] -# Watch these directories if you specified. -include_dir = [] -# Exclude files. -exclude_file = [] -# This log file places in your tmp_dir. -log = "./tmp/air.log" -# It's not necessary to trigger build each time file changes if it's too frequent. -delay = 500 # ms -# Stop running old binary when build errors occur. -stop_on_error = true -# Send Interrupt signal before killing process (windows does not support this feature) -send_interrupt = false -# Delay after sending Interrupt signal -kill_delay = 500 # ms - -[log] -# Show log time -time = false - -[color] -# Customize each part's color. If no color found, use the raw app log. -main = "magenta" -watcher = "cyan" -build = "yellow" -runner = "green" - -[misc] -# Delete tmp directory on exit -clean_on_exit = true \ No newline at end of file diff --git a/air.sh b/air.sh deleted file mode 100755 index 1b7017e..0000000 --- a/air.sh +++ /dev/null @@ -1 +0,0 @@ -go run github.com/cosmtrek/air -c "./air.conf" \ No newline at end of file diff --git a/api/README.md b/api/README.md deleted file mode 100644 index bf42483..0000000 --- a/api/README.md +++ /dev/null @@ -1,49 +0,0 @@ -## Drago API Client - -This directory contains the `api` package which aims at providing programmatic access to Drago's HTTP API. - -### Documentation - -... - -### Usage - -```go -package main - -import "github.com/seashell/drago/api" - -func main() { - // Get a new client - client, err := api.NewClient(api.DefaultConfig()) - if err != nil { - panic(err) - } - - // Get a handle to the networks API - networks := client.Networks() - - // Create a new network - n := &api.Network{ - Name: "my-new-network", - IPAddressRange: "10.1.1.0/24" - } - - id, err := networks.Create(context.Background(), n) - if err != nil { - panic(err) - } - - ... -} -``` - -To run this example, start a Drago server: - -``` -drago agent --server -``` - -Copy the code above into a file such as `main.go`, and run it. - -After running the code, you can also view the values in the Drago UI on your local machine at http://localhost:8080/ui/ diff --git a/api/acl.go b/api/acl.go deleted file mode 100644 index f0f1ef8..0000000 --- a/api/acl.go +++ /dev/null @@ -1,33 +0,0 @@ -package api - -import ( - "path" - - "github.com/seashell/drago/drago/structs" -) - -const ( - aclPath = "/api/acl" -) - -// ACL is a handle to the ACL API -type ACL struct { - client *Client -} - -// Networks returns a handle on the networks endpoints. -func (c *Client) ACL() *ACL { - return &ACL{client: c} -} - -// Boostrap : -func (a *ACL) Bootstrap() (*structs.ACLToken, error) { - - var token structs.ACLToken - err := a.client.createResource(path.Join(aclPath, "bootstrap"), nil, &token) - if err != nil { - return nil, err - } - - return &token, nil -} diff --git a/api/acl_policy.go b/api/acl_policy.go deleted file mode 100644 index 831ade9..0000000 --- a/api/acl_policy.go +++ /dev/null @@ -1,68 +0,0 @@ -package api - -import ( - "path" - - "github.com/seashell/drago/drago/structs" -) - -const ( - aclPoliciesPath = "/api/acl/policies" -) - -// ACLPolicies is a handle to the ACL policies API -type ACLPolicies struct { - client *Client -} - -// ACLPolicies returns a handle on the ACL policies endpoints. -func (c *Client) ACLPolicies() *ACLPolicies { - return &ACLPolicies{client: c} -} - -// Create : -func (p *ACLPolicies) Upsert(policy *structs.ACLPolicy) (*structs.ACLPolicy, error) { - - var rcvPolicy *structs.ACLPolicy - err := p.client.createResource(aclPoliciesPath, policy, &rcvPolicy) - if err != nil { - return nil, err - } - - return policy, nil -} - -// Delete : -func (p *ACLPolicies) Delete(name string) error { - - err := p.client.deleteResource(name, aclPoliciesPath, nil) - if err != nil { - return err - } - - return nil -} - -// Get : -func (p *ACLPolicies) Get(name string) (*structs.ACLPolicy, error) { - - out := &structs.ACLPolicy{} - err := p.client.getResource(aclPoliciesPath, name, out) - if err != nil { - return nil, err - } - - return out, nil -} - -// List : -func (p *ACLPolicies) List() ([]*structs.ACLPolicyListStub, error) { - - var items []*structs.ACLPolicyListStub - err := p.client.listResources(path.Join(aclPoliciesPath, "/"), nil, &items) - if err != nil { - return nil, err - } - - return items, nil -} diff --git a/api/acl_token.go b/api/acl_token.go deleted file mode 100644 index 70d395a..0000000 --- a/api/acl_token.go +++ /dev/null @@ -1,94 +0,0 @@ -package api - -import ( - "path" - - "github.com/seashell/drago/drago/structs" -) - -const ( - aclTokensPath = "/api/acl/tokens" -) - -// ACLTokens is a handle to the ACL tokens API -type ACLTokens struct { - client *Client -} - -// ACLTokens returns a handle on the ACL tokens endpoints. -func (c *Client) ACLTokens() *ACLTokens { - return &ACLTokens{client: c} -} - -// Create : -func (t *ACLTokens) Create(token *structs.ACLToken) (*structs.ACLToken, error) { - - out := &structs.ACLToken{} - - err := t.client.createResource(aclTokensPath, token, out) - if err != nil { - return nil, err - } - - return out, nil -} - -// Delete : -func (t *ACLTokens) Delete(id string) error { - - err := t.client.deleteResource(id, aclTokensPath, nil) - if err != nil { - return err - } - - return nil -} - -// Update : -func (t *ACLTokens) Update(token *structs.ACLToken) (*structs.ACLToken, error) { - - out := structs.ACLToken{} - - err := t.client.updateResource(token.ID, aclTokensPath, token, &out) - if err != nil { - return nil, err - } - - return &out, nil -} - -// Get : -func (t *ACLTokens) Get(id string) (*structs.ACLToken, error) { - - var token *structs.ACLToken - err := t.client.getResource(aclTokensPath, id, &token) - if err != nil { - return nil, err - } - - return token, nil -} - -// List : -func (t *ACLTokens) List() ([]*structs.ACLTokenListStub, error) { - - var items []*structs.ACLTokenListStub - err := t.client.listResources(path.Join(aclTokensPath, "/"), nil, &items) - if err != nil { - return nil, err - } - - return items, nil -} - -// Self : -func (t *ACLTokens) Self() (*structs.ACLToken, error) { - - var token *structs.ACLToken - err := t.client.getResource(aclTokensPath, "self", &token) - if err != nil { - return nil, err - } - - return token, nil -} diff --git a/api/agent.go b/api/agent.go deleted file mode 100644 index 7216bde..0000000 --- a/api/agent.go +++ /dev/null @@ -1,33 +0,0 @@ -package api - -import ( - "path" - - "github.com/seashell/drago/drago/structs" -) - -const ( - agentPath = "/api/agent" -) - -// Agent is a handle to the agent API -type Agent struct { - client *Client -} - -// Agent returns a handle on the agent endpoints. -func (c *Client) Agent() *Agent { - return &Agent{client: c} -} - -// Self : -func (t *Agent) Self() (*structs.Agent, error) { - - var agent *structs.Agent - err := t.client.getResource(path.Join(agentPath, "self"), "", &agent) - if err != nil { - return nil, err - } - - return agent, nil -} diff --git a/api/api.go b/api/api.go deleted file mode 100644 index 8f662c3..0000000 --- a/api/api.go +++ /dev/null @@ -1,185 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "path" - - "github.com/hashicorp/go-cleanhttp" -) - -// Client provides a client to the Drago API -type Client struct { - config *Config - httpClient *http.Client -} - -// NewClient returns a new Drago API client -func NewClient(config *Config) (*Client, error) { - - config = DefaultConfig().Merge(config) - - if err := config.Validate(); err != nil { - return nil, err - } - - client := &Client{ - config: config, - httpClient: cleanhttp.DefaultClient(), - } - - return client, nil -} - -func (c *Client) getResource(p string, id string, receiver interface{}) error { - - u, err := url.Parse(c.config.Address) - if err != nil { - return err - } - - u.Path += path.Join(p, id) - - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return err - } - - c.addHeaders(req) - - return c.doRequest(req, receiver) -} - -func (c *Client) createResource(p string, sender interface{}, receiver interface{}) error { - - u, err := url.Parse(c.config.Address) - if err != nil { - return err - } - - u.Path += p + "/" - - b := &bytes.Buffer{} - json.NewEncoder(b).Encode(sender) - - req, err := http.NewRequest("POST", u.String(), b) - if err != nil { - return err - } - - c.addHeaders(req) - - return c.doRequest(req, receiver) -} - -func (c *Client) updateResource(id, p string, sender interface{}, receiver interface{}) error { - - u, err := url.Parse(c.config.Address) - if err != nil { - return err - } - - u.Path += path.Join(p, id) - - b := &bytes.Buffer{} - json.NewEncoder(b).Encode(sender) - - req, err := http.NewRequest("PATCH", u.String(), b) - if err != nil { - return err - } - - c.addHeaders(req) - return c.doRequest(req, receiver) -} - -func (c *Client) deleteResource(id, p string, receiver interface{}) error { - - u, err := url.Parse(c.config.Address) - if err != nil { - return err - } - - u.Path += path.Join(p, id) - - req, err := http.NewRequest("DELETE", u.String(), nil) - if err != nil { - return err - } - - c.addHeaders(req) - - return c.doRequest(req, receiver) -} - -func (c *Client) listResources(p string, filters map[string][]string, receiver interface{}) error { - - u, err := url.Parse(c.config.Address) - if err != nil { - return err - } - - u.Path += p - - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return err - } - - c.addQuery(filters, req) - c.addHeaders(req) - - return c.doRequest(req, receiver) -} - -func (c *Client) addHeaders(req *http.Request) { - req.Header.Set("Content-Type", "application/json") - req.Header.Add("X-Drago-Token", c.config.Token) -} - -func (c *Client) addQuery(filters map[string][]string, req *http.Request) { - - q := req.URL.Query() - - for k, values := range filters { - for _, v := range values { - q.Add(k, v) - } - } - - req.URL.RawQuery = q.Encode() -} - -func (c *Client) doRequest(req *http.Request, receiver interface{}) error { - - res, err := c.httpClient.Do(req) - if err != nil { - return err - } - - defer res.Body.Close() - - if ok := res.StatusCode >= 200 && res.StatusCode < 300; !ok { - - err := CodedError{ - Code: res.StatusCode, - } - - if err := json.NewDecoder(res.Body).Decode(&err); err != nil { - resBody, _ := ioutil.ReadAll(res.Body) - return fmt.Errorf("%v (%v)", res.Status, string(resBody)) - } - - return err - } - - if receiver != nil { - return json.NewDecoder(res.Body).Decode(receiver) - } - - return nil -} diff --git a/api/config.go b/api/config.go deleted file mode 100644 index 23eadbe..0000000 --- a/api/config.go +++ /dev/null @@ -1,64 +0,0 @@ -package api - -import ( - "net/url" - "time" -) - -const ( - // DefaultAddress is the default Drago server address. - DefaultAddress = "http://127.0.0.1:8080" - - // DefaultTimeout is the default request timeout. - DefaultTimeout = 2 * time.Second -) - -// Config contains configurations for Drago's API client. -type Config struct { - // URL of the Drago server (e.g. http://127.0.0.1:8080). - Address string - - // Token to be used for authentication. - Token string - - // Request timeout. - Timeout time.Duration -} - -// DefaultConfig returns a default configuration for Drago's API client. -func DefaultConfig() *Config { - config := &Config{ - Address: DefaultAddress, - } - return config -} - -// Validate validates the configurations contained wihin the Config struct. -func (c *Config) Validate() error { - if _, err := url.Parse(c.Address); err != nil { - return err - } - return nil -} - -// Merge merges two API client configurations. -func (c *Config) Merge(b *Config) *Config { - - if b == nil { - return c - } - - result := *c - - if b.Address != "" { - result.Address = b.Address - } - if b.Token != "" { - result.Token = b.Token - } - if b.Timeout != 0 { - result.Timeout = b.Timeout - } - - return &result -} diff --git a/api/connection.go b/api/connection.go deleted file mode 100644 index b47f09d..0000000 --- a/api/connection.go +++ /dev/null @@ -1,76 +0,0 @@ -package api - -import ( - "path" - - "github.com/seashell/drago/drago/structs" -) - -const ( - connectionsPath = "/api/connections" -) - -// Connections is a handle to the connection API -type Connections struct { - client *Client -} - -// Connections returns a handle on the connections endpoints. -func (c *Client) Connections() *Connections { - return &Connections{client: c} -} - -// Get : -func (n *Connections) Get(id string) (*structs.Connection, error) { - - var conn *structs.Connection - err := n.client.getResource(connectionsPath, id, &conn) - if err != nil { - return nil, err - } - - return conn, nil -} - -// List : -func (n *Connections) List() ([]*structs.ConnectionListStub, error) { - - var items []*structs.ConnectionListStub - err := n.client.listResources(path.Join(connectionsPath, "/"), nil, &items) - if err != nil { - return nil, err - } - - return items, nil -} - -func (n *Connections) Create(connection *structs.Connection) error { - - err := n.client.createResource(connectionsPath, connection, nil) - if err != nil { - return err - } - - return nil -} - -func (n *Connections) Update(conn *structs.Connection) (*structs.Connection, error) { - - out := &structs.Connection{} - err := n.client.createResource(connectionsPath, conn, out) - if err != nil { - return nil, err - } - - return out, nil -} - -func (n *Connections) Delete(id string) error { - - err := n.client.deleteResource(id, connectionsPath, nil) - if err != nil { - return err - } - - return nil -} diff --git a/api/error.go b/api/error.go deleted file mode 100644 index 14ebe97..0000000 --- a/api/error.go +++ /dev/null @@ -1,12 +0,0 @@ -package api - -import "fmt" - -type CodedError struct { - Message string - Code int -} - -func (e CodedError) Error() string { - return fmt.Sprintf("%d (%s)", e.Code, e.Message) -} diff --git a/api/interface.go b/api/interface.go deleted file mode 100644 index 0dfaeb4..0000000 --- a/api/interface.go +++ /dev/null @@ -1,85 +0,0 @@ -package api - -import ( - "path" - - "github.com/seashell/drago/drago/structs" -) - -const ( - interfacesPath = "/api/interfaces" -) - -// Interfaces is a handle to the interfaces API -type Interfaces struct { - client *Client -} - -// Interfaces returns a handle on the interfaces endpoints. -func (c *Client) Interfaces() *Interfaces { - return &Interfaces{client: c} -} - -// Get : -func (n *Interfaces) Get(id string) (*structs.Interface, error) { - - var iface *structs.Interface - err := n.client.getResource(interfacesPath, id, &iface) - if err != nil { - return nil, err - } - - return iface, nil -} - -// List : -func (n *Interfaces) List(filters map[string][]string) ([]*structs.InterfaceListStub, error) { - - var items []*structs.InterfaceListStub - err := n.client.listResources(path.Join(interfacesPath, "/"), filters, &items) - if err != nil { - return nil, err - } - - return items, nil -} - -// Create : -func (n *Interfaces) Create(nodeID, networkID string) (*structs.Interface, error) { - - iface := &structs.Interface{ - NodeID: nodeID, - NetworkID: networkID, - } - - out := &structs.Interface{} - err := n.client.createResource(interfacesPath, iface, out) - if err != nil { - return nil, err - } - - return out, nil -} - -// Update : -func (n *Interfaces) Update(iface *structs.Interface) (*structs.Interface, error) { - - out := &structs.Interface{} - err := n.client.createResource(interfacesPath, iface, out) - if err != nil { - return nil, err - } - - return out, err -} - -// Delete : -func (n *Interfaces) Delete(id string) error { - - err := n.client.deleteResource(id, interfacesPath, nil) - if err != nil { - return err - } - - return nil -} diff --git a/api/network.go b/api/network.go deleted file mode 100644 index 3ac6775..0000000 --- a/api/network.go +++ /dev/null @@ -1,67 +0,0 @@ -package api - -import ( - "path" - - "github.com/seashell/drago/drago/structs" -) - -const ( - networksPath = "/api/networks" -) - -// Networks is a handle to the nodes API -type Networks struct { - client *Client -} - -// Networks returns a handle on the networks endpoints. -func (c *Client) Networks() *Networks { - return &Networks{client: c} -} - -// Create : -func (n *Networks) Create(network *structs.Network) error { - - err := n.client.createResource(networksPath, network, nil) - if err != nil { - return err - } - - return err -} - -// Delete : -func (n *Networks) Delete(id string) error { - - err := n.client.deleteResource(id, networksPath, nil) - if err != nil { - return err - } - - return nil -} - -// Get : -func (n *Networks) Get(id string) (*structs.Network, error) { - - var network *structs.Network - err := n.client.getResource(networksPath, id, &network) - if err != nil { - return nil, err - } - - return network, nil -} - -// List : -func (n *Networks) List() ([]*structs.NetworkListStub, error) { - - var items []*structs.NetworkListStub - err := n.client.listResources(path.Join(networksPath, "/"), nil, &items) - if err != nil { - return nil, err - } - - return items, nil -} diff --git a/api/node.go b/api/node.go deleted file mode 100644 index 0ce22f5..0000000 --- a/api/node.go +++ /dev/null @@ -1,45 +0,0 @@ -package api - -import ( - "path" - - "github.com/seashell/drago/drago/structs" -) - -const ( - nodesPath = "/api/nodes" -) - -// Nodes is a handle to the nodes API -type Nodes struct { - client *Client -} - -// Nodes returns a handle on the nodes endpoints. -func (c *Client) Nodes() *Nodes { - return &Nodes{client: c} -} - -// Get : -func (t *Nodes) Get(id string) (*structs.Node, error) { - - var node *structs.Node - err := t.client.getResource(nodesPath, id, &node) - if err != nil { - return nil, err - } - - return node, nil -} - -// List : -func (t *Nodes) List(filters map[string][]string) ([]*structs.NodeListStub, error) { - - var items []*structs.NodeListStub - err := t.client.listResources(path.Join(nodesPath, "/"), filters, &items) - if err != nil { - return nil, err - } - - return items, nil -} diff --git a/architecture.png b/architecture.png new file mode 100644 index 0000000..ad27cc6 Binary files /dev/null and b/architecture.png differ diff --git a/architecture.svg b/architecture.svg new file mode 100644 index 0000000..54e2766 --- /dev/null +++ b/architecture.svg @@ -0,0 +1,687 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Drago server + + + + + + + + + + + + + + + + Drago client + Wireguard + + + + + + + + + + + + + + + + + + + + Drago client + Wireguard + + + + + + + + + + + + + + + + + + Drago client + Wireguard + + + + Config storage- In-memory- Filesystem- Sqlite- Postgres- BoltDB ... + + Auth + + diff --git a/build/Dockerfile.builder b/build/Dockerfile.builder deleted file mode 100644 index 205f1cb..0000000 --- a/build/Dockerfile.builder +++ /dev/null @@ -1,22 +0,0 @@ -FROM golang:1.16.2-stretch as drago-builder - -ARG HOST_UID=${HOST_UID} -ARG HOST_USER=${HOST_USER} - -RUN curl -sS http://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ - echo "deb http://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ - curl -sL http://deb.nodesource.com/setup_15.x | bash - && \ - apt-get install -y nodejs && \ - apt-get update && \ - apt-get remove cmdtest && \ - apt-get install -y yarn - -RUN apt-get install -y gcc-arm-linux-gnueabihf libc6-dev-armhf-cross \ - gcc-aarch64-linux-gnu libc6-dev-arm64-cross - -RUN if [ "${HOST_USER}" != "root" ]; then \ - (adduser -q --gecos "" --home /home/${HOST_USER} --disabled-password -u ${HOST_UID} ${HOST_USER} \ - && chown -R "${HOST_UID}:${HOST_UID}" /home/${HOST_USER}); \ - fi - -USER ${HOST_USER} \ No newline at end of file diff --git a/build/Dockerfile.linux_amd64 b/build/Dockerfile.linux_amd64 deleted file mode 100644 index 502c423..0000000 --- a/build/Dockerfile.linux_amd64 +++ /dev/null @@ -1,9 +0,0 @@ -FROM alpine:3.13 - -WORKDIR /home - -RUN apk add -U wireguard-tools - -COPY ./bin/linux_amd64/drago ./drago - -ENTRYPOINT [ "./drago" ] \ No newline at end of file diff --git a/client/client.go b/client/client.go deleted file mode 100644 index 2d67c50..0000000 --- a/client/client.go +++ /dev/null @@ -1,532 +0,0 @@ -package client - -import ( - "fmt" - "io/ioutil" - "math/rand" - "os" - "path" - "path/filepath" - "sync" - "time" - - "github.com/seashell/drago/agent/conn" - nic "github.com/seashell/drago/client/nic" - state "github.com/seashell/drago/client/state" - boltdb "github.com/seashell/drago/client/state/boltdb" - structs "github.com/seashell/drago/drago/structs" - log "github.com/seashell/drago/pkg/log" - uuid "github.com/seashell/drago/pkg/uuid" -) - -var ( - defaultRegistrationRetryInterval = 5 * time.Second - defaultReconciliationRetryInterval = 5 * time.Second - defaultReconciliationInterval = 2 * time.Second - defaultFirstHeartbeatDelay = 1 * time.Second - defaultHeartbeatInterval = 5 * time.Second -) - -// Client is the Drago client -type Client struct { - config *Config - - logger log.Logger - - rpc conn.RPCConnection - - state state.Repository - - niController nic.NetworkInterfaceController - niControllerLock sync.Mutex - - node *structs.Node - nodeLock sync.Mutex - - shutdown bool - shutdownCh chan struct{} - shutdownLock sync.Mutex -} - -// New is used to create a new Drago client from the -// configuration, potentially returning an error -func New(conn conn.RPCConnection, config *Config) (*Client, error) { - - rand.Seed(time.Now().Unix()) - - config = DefaultConfig().Merge(config) - - c := &Client{ - config: config, - rpc: conn, - logger: config.Logger.WithName("client"), - shutdownCh: make(chan struct{}), - } - - if err := c.setupState(); err != nil { - return nil, fmt.Errorf("error setting up client state: %v", err) - } - - if err := c.setupNode(); err != nil { - return nil, fmt.Errorf("error setting up node: %v", err) - } - - if err := c.setupNetworkController(); err != nil { - return nil, fmt.Errorf("error setting up network controller: %v", err) - } - - if err := c.setupInterfaces(); err != nil { - return nil, fmt.Errorf("error setting up interfaces: %v", err) - } - - go c.registerAndHeartbeat() - - go c.run() - - c.logger.Infof("started client node %s", c.NodeID()) - - return c, nil -} - -func (c *Client) Node() *structs.Node { - return c.node -} - -// NodeID returns the node ID for the given client -func (c *Client) NodeID() string { - return c.node.ID -} - -// NodeSecretID returns the node secret ID for the given client -func (c *Client) NodeSecretID() string { - return c.node.ID -} - -// Stats is used to return statistics for the server -func (c *Client) Stats() map[string]map[string]string { - - stats := map[string]map[string]string{ - "client": { - "node_id": c.NodeID(), - }, - } - - return stats -} - -func (c *Client) setupNode() error { - - if c.node == nil { - c.node = &structs.Node{} - } - - id, err := c.readOrGenerateNodeID() - if err != nil { - return fmt.Errorf("could not retrieve node ID: %v", err) - } - - secret, err := c.readOrGenerateNodeSecretID() - if err != nil { - return fmt.Errorf("could not retrieve node secret ID: %v", err) - } - - c.node.ID = id - c.node.SecretID = secret - - c.node.Name = c.config.Name - c.node.Meta = c.config.Meta - - c.node.AdvertiseAddress = c.config.AdvertiseAddress - c.node.Status = structs.NodeStatusInit - - if c.node.Name == "" { - if hostname, _ := os.Hostname(); hostname != "" { - c.node.Name = hostname - } else { - c.node.Name = c.node.ID - } - } - if c.node.Meta == nil { - c.node.Meta = make(map[string]string) - } - - return nil -} - -func (c *Client) setupState() error { - - // Ensure the state dir exists. If it was not was specified, - // create a temporary directory to store the client state. - if c.config.StateDir != "" { - if err := os.MkdirAll(c.config.StateDir, 0700); err != nil { - return fmt.Errorf("failed to create state dir: %s", err) - } - } else { - tmp, err := c.createTempDir("DragoClient") - if err != nil { - return fmt.Errorf("failed to create tmp dir for storing state: %s", err) - } - c.config.StateDir = tmp - } - - c.logger.Infof("using state directory %s", c.config.StateDir) - - repo, err := boltdb.NewStateRepository(path.Join(c.config.StateDir, "client.state")) - if err != nil { - return fmt.Errorf("failed to open state database: %v", err) - } - - c.state = repo - - return nil -} - -func (c *Client) setupNetworkController() error { - - nc, err := nic.NewController(&nic.Config{ - InterfacesPrefix: c.config.InterfacesPrefix, - WireguardPath: c.config.WireguardPath, - KeyStore: c.state, // TODO: improve how we store private keys (do we really need to store them?) - }) - if err != nil { - return err - } - - c.niController = nc - - return nil -} - -func (c *Client) setupInterfaces() error { - - current, err := c.niController.Interfaces() - if err != nil { - return fmt.Errorf("could not retrieve network interfaces from network controller: %v", err) - } - - desired, err := c.state.Interfaces() - if err != nil { - return fmt.Errorf("could not retrieve network interfaces from state: %v", err) - } - - c.reconcileInterfaces(current, desired) - - return nil -} - -func (c *Client) registerAndHeartbeat() { - - c.tryToRegisterUntilSuccessful() - - heartbeatCh := time.After(defaultFirstHeartbeatDelay) - - for { - select { - case <-heartbeatCh: - case <-c.shutdownCh: - return - } - - c.logger.Debugf("heartbeating (client -> server)") - - if err := c.updateNodeStatus(); err != nil { - c.logger.Debugf("error updating node status: %v", err) - c.tryToRegisterUntilSuccessful() - heartbeatCh = time.After(defaultHeartbeatInterval) - } else { - heartbeatCh = time.After(defaultHeartbeatInterval) - } - - } -} - -func (c *Client) run() { - - c.logger.Debugf("running node") - - interfacesUpdateCh := make(chan []*structs.Interface) - go c.watchInterfaces(interfacesUpdateCh) - - go c.synchronizeInterfaces() - - for { - select { - case desired := <-interfacesUpdateCh: - c.shutdownLock.Lock() - if c.shutdown { - c.shutdownLock.Unlock() - return - } - - current, err := c.state.Interfaces() - if err != nil { - c.logger.Errorf("could not read interfaces from state repository: %v", err) - } - - c.reconcileInterfaces(current, desired) - - c.shutdownLock.Unlock() - case <-c.shutdownCh: - return - } - } -} - -func (c *Client) reconcileInterfaces(current, desired []*structs.Interface) { - - currentMap := map[string]*structs.Interface{} - for _, i := range current { - currentMap[i.ID] = i - } - - desiredMap := map[string]*structs.Interface{} - for _, i := range desired { - desiredMap[i.ID] = i - } - - diff := interfacesDiff(currentMap, desiredMap) - - c.logger.Debugf("interface updates: (created: %d, deleted: %d, updated: %d, unchanged: %d)", - len(diff.created), len(diff.deleted), len(diff.updated), len(diff.unchanged)) - - c.niControllerLock.Lock() - defer c.niControllerLock.Unlock() - - // Delete old interfaces - for _, id := range diff.deleted { - if err := c.state.DeleteInterfaces([]string{id}); err != nil { - c.logger.Warnf("could not persist interface deletion to the state: %v", err) - } - if err := c.niController.DeleteInterfaceByAlias(id); err != nil { - c.logger.Warnf("could not delete interface: %v", err) - } - } - - // Create a new interface from scratch - for _, id := range diff.created { - - iface := desiredMap[id] - - err := c.state.UpsertInterface(iface) - if err != nil { - c.logger.Warnf("could not persist interface: %v", err) - continue - } - - err = c.niController.CreateInterface(iface) - if err != nil { - c.logger.Warnf("could not create wireguard interface: %v", err) - } - } - - // Update an existing interface - for _, id := range diff.updated { - - iface := desiredMap[id] - - err := c.state.UpsertInterface(iface) - if err != nil { - c.logger.Warnf("could not persist interface: %v", err) - continue - } - - if err := c.niController.UpdateInterface(iface); err != nil { - c.logger.Warnf("could not update wireguard interface: %v", err) - } - } - -} - -func (c *Client) watchInterfaces(ch chan []*structs.Interface) { - - req := &structs.NodeSpecificRequest{ - NodeID: c.NodeID(), - } - - for { - - c.logger.Debugf("updating interface configuration (server -> client)") - - var resp structs.NodeInterfacesResponse - err := c.RPC("Node.GetInterfaces", req, &resp) - if err != nil { - c.logger.Debugf("error fetching interfaces: %v", err) - retryCh := time.After(defaultReconciliationRetryInterval) - select { - case <-retryCh: - case <-c.shutdownCh: - return - } - } else { - ch <- resp.Items - } - - retryCh := time.After(c.config.ReconcileInterval) - select { - case <-c.shutdownCh: - return - case <-retryCh: - } - } -} - -func (c *Client) synchronizeInterfaces() { - - for { - - c.logger.Debugf("updating interface status (client -> server)") - - interfaces, err := c.niController.Interfaces() - if err != nil { - c.logger.Warnf("could not retrieve interfaces with public key from controller") - } - - if interfaces == nil { - interfaces = []*structs.Interface{} - } - - req := &structs.NodeInterfaceUpdateRequest{ - NodeID: c.NodeID(), - Interfaces: interfaces, - } - - var resp structs.GenericResponse - err = c.RPC("Node.UpdateInterfaces", req, &resp) - if err != nil { - c.logger.Debugf("error updating interfaces: %v", err) - retryCh := time.After(randomDuration(defaultReconciliationInterval, 0*time.Second)) - select { - case <-retryCh: - case <-c.shutdownCh: - return - } - } - retryCh := time.After(randomDuration(defaultReconciliationInterval, 0*time.Second)) - select { - case <-c.shutdownCh: - return - case <-retryCh: - } - } -} - -func (c *Client) tryToRegisterUntilSuccessful() { - - for { - - c.logger.Debugf("registering node (client -> server)") - - req := &structs.NodeRegisterRequest{ - Node: c.Node(), - } - - var err error - var resp structs.NodeUpdateResponse - if err = c.RPC("Node.Register", req, &resp); err == nil { - - c.nodeLock.Lock() - c.node.Status = structs.NodeStatusReady - c.nodeLock.Unlock() - - return - } - - c.logger.Debugf("error registering node: %v", err) - - retryCh := time.After(randomDuration(defaultRegistrationRetryInterval, 0*time.Second)) - - select { - case <-retryCh: - case <-c.shutdownCh: - return - } - } -} - -func (c *Client) updateNodeStatus() error { - - c.logger.Debugf("updating node status (client -> server)") - - req := &structs.NodeUpdateStatusRequest{ - NodeID: c.NodeID(), - Status: structs.NodeStatusReady, - AdvertiseAddress: c.Node().AdvertiseAddress, - Meta: c.node.Meta, - } - - var err error - var resp structs.NodeUpdateResponse - if err = c.RPC("Node.UpdateStatus", req, &resp); err != nil { - return err - } - - return nil -} - -func (c *Client) RPC(method string, args interface{}, reply interface{}) error { - return c.rpc.Call(method, args, reply) -} - -// Shutdown is used to tear down the client -func (c *Client) Shutdown() error { - c.shutdownLock.Lock() - defer c.shutdownLock.Unlock() - - if c.shutdown { - c.logger.Infof("client already shutdown") - return nil - } - c.logger.Infof("shutting down") - - c.shutdown = true - close(c.shutdownCh) - - return nil -} - -func (c *Client) createTempDir(pattern string) (string, error) { - p, err := ioutil.TempDir("", pattern) - if err != nil { - return "", fmt.Errorf("could not create temporary directory: %v", err) - } - p, err = filepath.EvalSymlinks(p) - if err != nil { - return "", fmt.Errorf("could not retrieve path to StateDir: %v", err) - } - return p, nil -} - -func (c *Client) readOrGenerateNodeID() (string, error) { - - id := uuid.Generate() - if c.config.DevMode { - return id, nil - } - - path := filepath.Join(c.config.StateDir, "client-id") - - id, err := c.readFileLazy(path, id) - if err != nil { - return "", err - } - - return id, nil -} - -func (c *Client) readOrGenerateNodeSecretID() (string, error) { - - path := filepath.Join(c.config.StateDir, "secret-id") - - secret, err := c.readFileLazy(path, uuid.Generate()) - if err != nil { - return "", err - } - - return secret, nil -} - -// Generates a random duration in the interval [mean-delta, mean+delta] -func randomDuration(mean time.Duration, delta time.Duration) time.Duration { - t := mean.Milliseconds() + int64((rand.Float32()-0.5)*float32(delta.Milliseconds())) - return time.Duration(t * int64(time.Millisecond)) -} diff --git a/client/config.go b/client/config.go deleted file mode 100644 index bf7c876..0000000 --- a/client/config.go +++ /dev/null @@ -1,112 +0,0 @@ -package client - -import ( - "time" - - log "github.com/seashell/drago/pkg/log" - version "github.com/seashell/drago/version" -) - -const ( - defaultLogLevel = "DEBUG" - defaultStateDir = "/tmp/drago" - defaultWireguardPath = "" - defaultInterfacesPrefix = "drago-" -) - -// Config : Drago client configuration -type Config struct { - - // DevMode indicates whether the client is running in development mode. - DevMode bool - - // Name is used to specify the name of the client node. - Name string - - // Version is the version of the Drago client. - Version *version.VersionInfo - - // LogLevel is the level at which the client should output logs. - LogLevel string - - //Logger is the logger the client will use to log. - Logger log.Logger - - // Servers is a list of known server addresses. These are as "host:port". - Servers []string - - // Token contains the auth token used by the client. - Token string - - // StateDir is the directory to store our state in. - StateDir string - - // AdvertiseAddress is the public address advertised to other nodes. - AdvertiseAddress string - - // InterfacesPrefix is the string prepended to the name of all WireGuard - // interfaces created by Drago. - InterfacesPrefix string - - // ReconcileInterval is the interval between two reconciliation cycles. - ReconcileInterval time.Duration - - // WireguardPath is path to the WireGuard binary. - WireguardPath string - - // Meta contains client metadata - Meta map[string]string -} - -// DefaultConfig returns the default configuration. -func DefaultConfig() *Config { - return &Config{ - Name: "", - Version: version.GetVersion(), - LogLevel: defaultLogLevel, - Servers: []string{"localhost:8081"}, - StateDir: defaultStateDir, - InterfacesPrefix: defaultInterfacesPrefix, - ReconcileInterval: 5 * time.Second, - WireguardPath: defaultWireguardPath, - Meta: map[string]string{}, - } -} - -// Merge combines two config structs, returning the result. -func (c *Config) Merge(b *Config) *Config { - result := *c - - if b.Name != "" { - result.Name = b.Name - } - if b.Logger != nil { - result.Logger = b.Logger - } - if b.LogLevel != "" { - result.LogLevel = b.LogLevel - } - if b.Servers != nil { - result.Servers = b.Servers - } - if b.StateDir != "" { - result.StateDir = b.StateDir - } - if b.AdvertiseAddress != "" { - result.AdvertiseAddress = b.AdvertiseAddress - } - if b.InterfacesPrefix != "" { - result.InterfacesPrefix = b.InterfacesPrefix - } - if b.ReconcileInterval != 0 { - result.ReconcileInterval = b.ReconcileInterval - } - if b.WireguardPath != "" { - result.WireguardPath = b.WireguardPath - } - if b.Meta != nil { - result.Meta = b.Meta - } - - return &result -} diff --git a/client/nic/controller.go b/client/nic/controller.go deleted file mode 100644 index fe730f9..0000000 --- a/client/nic/controller.go +++ /dev/null @@ -1,343 +0,0 @@ -package nic - -import ( - "bytes" - "crypto/rand" - "encoding/hex" - "fmt" - "net" - "os/exec" - "strings" - "time" - - structs "github.com/seashell/drago/drago/structs" - util "github.com/seashell/drago/pkg/util" - netlink "github.com/vishvananda/netlink" - wgctrl "golang.zx2c4.com/wireguard/wgctrl" - wgtypes "golang.zx2c4.com/wireguard/wgctrl/wgtypes" -) - -const ( - linkTypeWireguard = "wireguard" -) - -// Config contains configurations for a network controller. -type Config struct { - // InterfacesPrefix defines the string prepended to each interface name. - // Example: if InterfacePrefix is "abc", interfaces will be named as - // "abc-xxxxxx", where "xxxxxx" is a random string. - InterfacesPrefix string - - // WireguardPath is the path to a userspace Wireguard binary. In case - // it is not defined, Drago will try to use the kernel module. - WireguardPath string - - // KeyStore is an implementation of the KeyStore interface, used by the - // Controller to cache private keys for each interface. - KeyStore PrivateKeyStore -} - -// Controller : network interface controller. -type Controller struct { - config *Config - wg *wgctrl.Client -} - -// NewController : -func NewController(config *Config) (*Controller, error) { - - wg, err := wgctrl.New() - if err != nil { - return nil, err - } - - if config.KeyStore == nil { - panic("must provide a key store") - } - - c := &Controller{ - config: config, - wg: wg, - } - - return c, nil -} - -// Interfaces returns a slice of all network interfaces managed by -// the controller. -func (c *Controller) Interfaces() ([]*structs.Interface, error) { - - out := []*structs.Interface{} - - links, err := linksByPrefix(c.config.InterfacesPrefix) - if err != nil { - return nil, err - } - - for _, l := range links { - - dev, err := c.wg.Device(l.Attrs().Name) - if err != nil { - return nil, err - } - - out = append(out, &structs.Interface{ - ID: l.Attrs().Alias, - Name: util.StrToPtr(l.Attrs().Name), - ListenPort: &dev.ListenPort, - PublicKey: util.StrToPtr(dev.PrivateKey.PublicKey().String()), - // TODO: capture other information that might be useful e.g. for diagnosis (see l.Attrs().Statistics) - }) - } - - return out, nil -} - -// DeleteInterfaceByName deletes a network interface and all associated routes by name. -func (c *Controller) DeleteInterfaceByName(s string) error { - err := deleteLinkAndRoutesByName(s) - if err != nil { - return err - } - return nil -} - -// DeleteInterfaceByAlias deletes a network interface and all associated routes by alias. -func (c *Controller) DeleteInterfaceByAlias(s string) error { - err := deleteLinksAndRoutesByAlias(s) - if err != nil { - return err - } - return nil -} - -// DeleteAllInterfaces deletes all network interfaces and routes. -func (c *Controller) DeleteAllInterfaces() error { - err := deleteLinksAndRoutesByPrefix(c.config.InterfacesPrefix) - if err != nil { - return err - } - return nil -} - -// CreateInterface ... -func (c *Controller) CreateInterface(iface *structs.Interface) error { - - linkName, linkAlias := c.randomInterfaceName(), iface.ID - - err := c.createLink(linkName, linkAlias) - if err != nil { - return err - } - - return c.UpdateInterface(iface) - -} - -// UpdateInterface : -func (c *Controller) UpdateInterface(iface *structs.Interface) error { - return c.configureLink(iface) -} - -func (c *Controller) createLink(name string, alias string) error { - - attrs := netlink.NewLinkAttrs() - attrs.Name, attrs.Alias = name, alias - - // Create a new WireGuard interface. If a path to a valid userspace WireGuard binary - // was specified in the configurations, use if. Otherwise, create the interface manually. - if c.config.WireguardPath != "" { - - wgt, err := wgImplementationType(c.config.WireguardPath) - if err != nil { - return err - } - - var cmd *exec.Cmd - - switch wgt { - case "wireguard-go": - cmd = exec.Command(c.config.WireguardPath, name) - case "boringtun": - cmd = exec.Command(c.config.WireguardPath, name, "--disable-drop-privileges", "root") - } - - if err := cmd.Run(); err != nil { - return fmt.Errorf("can't create network interface with specified wireguard binary: %s", err.Error()) - } - - } else { - if err := netlink.LinkAdd(&netlink.Wireguard{LinkAttrs: attrs}); err != nil { - return fmt.Errorf("can't create network interface : %s", err.Error()) - } - } - - link, err := netlink.LinkByName(name) - if err != nil { - return err - } - - err = netlink.LinkSetAlias(link, alias) - if err != nil { - return err - } - - return nil -} - -func (c *Controller) configureLink(iface *structs.Interface) error { - - link, err := netlink.LinkByAlias(iface.ID) - if err != nil { - return err - } - - err = netlink.LinkSetDown(link) - if err != nil { - return err - } - - // Create a new private key for this interface. - // If a private key has already been created, use it. - // TODO: implement an expiration/rotation strategy. - key, err := c.config.KeyStore.KeyByID(iface.ID) - if err != nil { - wgKey, err := wgtypes.GeneratePrivateKey() - if err != nil { - return fmt.Errorf("could not generate private key: %v", err) - } - key = &PrivateKey{ - ID: iface.ID, - Key: wgKey.String(), - CreatedAt: time.Now().Unix(), - } - err = c.config.KeyStore.UpsertKey(key) - if err != nil { - return fmt.Errorf("could not persist private key: %v", err) - } - } - - wgKey, err := wgtypes.ParseKey(key.Key) - if err != nil { - return fmt.Errorf("could not parse private key: %v", err) - } - - config := wgtypes.Config{ - PrivateKey: &wgKey, - ListenPort: iface.ListenPort, - Peers: []wgtypes.PeerConfig{}, - ReplacePeers: true, - } - - for _, peer := range iface.Peers { - peerConfig, err := c.newPeerConfig(peer) - if err != nil { - return err - } - config.Peers = append(config.Peers, *peerConfig) - } - - err = c.wg.ConfigureDevice(link.Attrs().Name, config) - if err != nil { - return err - } - - // Assign IP address in CIDR format to the newly created link - err = setLinkAddress(link, iface.Address) - if err != nil { - return err - } - - err = netlink.LinkSetUp(link) - if err != nil { - return err - } - - for _, peerConfig := range config.Peers { - for _, ip := range peerConfig.AllowedIPs { - if err = netlink.RouteReplace(&netlink.Route{LinkIndex: link.Attrs().Index, Dst: &ip}); err != nil { - return err - } - } - } - - return nil -} - -func (c *Controller) newPeerConfig(peer *structs.Peer) (*wgtypes.PeerConfig, error) { - - var err error - - config := &wgtypes.PeerConfig{ - Remove: false, - UpdateOnly: false, - ReplaceAllowedIPs: true, - PresharedKey: nil, - PublicKey: wgtypes.Key{}, - AllowedIPs: []net.IPNet{}, - Endpoint: nil, - PersistentKeepaliveInterval: nil, - } - - if peer.PublicKey != nil { - var key wgtypes.Key - if key, err = wgtypes.ParseKey(*peer.PublicKey); err != nil { - return nil, err - } - config.PublicKey = key - } - - for _, ip := range peer.AllowedIPs { - _, parsed, err := net.ParseCIDR(ip) - if err != nil { - return nil, err - } - config.AllowedIPs = append(config.AllowedIPs, *parsed) - } - - if peer.PersistentKeepalive != nil { - pk := time.Duration(*peer.PersistentKeepalive) * time.Second - config.PersistentKeepaliveInterval = &pk - } - - if peer.Address != nil { - port := 51820 // TODO: should we use this or the zero value by default? - if peer.Port != nil { - port = *peer.Port - } - config.Endpoint = &net.UDPAddr{ - IP: net.ParseIP(*peer.Address), - Port: port, - } - } - - return config, nil -} - -func (c *Controller) randomInterfaceName() string { - buf := make([]byte, 3) - if _, err := rand.Read(buf); err != nil { - panic(fmt.Errorf("failed to read random bytes: %v", err)) - } - return c.config.InterfacesPrefix + "-" + hex.EncodeToString(buf) -} - -func wgImplementationType(path string) (string, error) { - - var out bytes.Buffer - - cmd := exec.Command(path, "--version") - cmd.Stdout = &out - - if err := cmd.Run(); err != nil { - return "", err - } - - if strings.Contains(out.String(), "wireguard-go") { - return "wireguard-go", nil - } else if strings.Contains(out.String(), "boringtun") { - return "boringtun", nil - } - - return "", fmt.Errorf("unknown wireguard implementation") -} diff --git a/client/nic/controller_test.go b/client/nic/controller_test.go deleted file mode 100644 index 160c98f..0000000 --- a/client/nic/controller_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package nic - -import ( - "fmt" - "testing" - - structs "github.com/seashell/drago/drago/structs" -) - -func TestCreateInterface(t *testing.T) { - config := &Config{ - InterfacesPrefix: "drago", - WireguardPath: "./wireguard", - } - - c, err := NewController(config) - if err != nil { - t.Fatal(err) - } - - key, err := c.GenerateKey() - if err != nil { - t.Error() - } - - err = c.CreateInterfaceWithKey(&structs.Interface{ - Name: "1234567890abcd", - Address: "192.168.2.1/24", - Peers: []*structs.Peer{}, - }, key) - if err != nil { - t.Error(err) - } -} - -func TestListInterfaces(t *testing.T) { - - config := &Config{ - InterfacesPrefix: "drago", - WireguardPath: "./wireguard", - } - - c, err := NewController(config) - if err != nil { - t.Fatal(err) - } - - ifaces, err := c.Interfaces() - if err != nil { - t.Fatal(err) - } - - for _, i := range ifaces { - fmt.Printf("%s (%s)", i.Name, i.Address) - } -} - -func TestDeleteAllInterfaces(t *testing.T) { - return - config := &Config{ - InterfacesPrefix: "drago", - WireguardPath: "./wireguard", - } - - c, err := NewController(config) - if err != nil { - t.Fatal(err) - } - - err = c.DeleteAllInterfaces() - if err != nil { - t.Fatal(err) - } -} diff --git a/client/nic/nic.go b/client/nic/nic.go deleted file mode 100644 index 331d35d..0000000 --- a/client/nic/nic.go +++ /dev/null @@ -1,27 +0,0 @@ -package nic - -import ( - structs "github.com/seashell/drago/drago/structs" -) - -// NetworkInterfaceController provides network configuration capabilities. -type NetworkInterfaceController interface { - Interfaces() ([]*structs.Interface, error) - CreateInterface(iface *structs.Interface) error - UpdateInterface(iface *structs.Interface) error - DeleteInterfaceByAlias(s string) error - DeleteInterfaceByName(s string) error - DeleteAllInterfaces() error -} - -type PrivateKeyStore interface { - KeyByID(id string) (*PrivateKey, error) - UpsertKey(key *PrivateKey) error - DeleteKey(id string) error -} - -type PrivateKey struct { - ID string - Key string - CreatedAt int64 -} diff --git a/client/nic/util.go b/client/nic/util.go deleted file mode 100644 index 36667e5..0000000 --- a/client/nic/util.go +++ /dev/null @@ -1,146 +0,0 @@ -package nic - -import ( - "strings" - - netlink "github.com/vishvananda/netlink" -) - -func linkNameByAlias(s string) (string, error) { - link, err := netlink.LinkByAlias(s) - if err != nil { - return "", err - } - return link.Attrs().Name, nil - -} - -func deleteLinkAndRoutesByName(s string) error { - - link, err := netlink.LinkByName(s) - if err != nil { - return err - } - - deleteLinkAndRoutes(link) - - return nil -} - -func deleteLinksAndRoutesByAlias(s string) error { - - links, err := linksByAlias(s) - if err != nil { - return err - } - - for _, l := range links { - err := deleteLinkAndRoutes(l) - if err != nil { - return err - } - } - - return nil -} - -func deleteLinksAndRoutesByPrefix(s string) error { - - links, err := linksByPrefix(s) - if err != nil { - return err - } - - for _, l := range links { - err := deleteLinkAndRoutes(l) - if err != nil { - return err - } - } - - return nil -} - -func deleteLinkAndRoutes(link netlink.Link) error { - - // List routes - routes, err := netlink.RouteList(&netlink.Wireguard{LinkAttrs: *link.Attrs()}, 0) - if err != nil { - return err - } - - // Delete routes - for _, r := range routes { - if err = netlink.RouteDel(&r); err != nil { - return err - } - } - - // Delete link - if err := netlink.LinkDel(&netlink.Wireguard{LinkAttrs: *link.Attrs()}); err != nil { - return err - } - - return nil -} - -func linksByPrefix(p string) ([]netlink.Link, error) { - - out := []netlink.Link{} - - links, _ := netlink.LinkList() - for _, l := range links { - if strings.HasPrefix(l.Attrs().Name, p) { - out = append(out, l) - } - } - - return out, nil -} - -func linksByAlias(s string) ([]netlink.Link, error) { - - out := []netlink.Link{} - - links, _ := netlink.LinkList() - for _, l := range links { - if l.Attrs().Alias == s { - out = append(out, l) - } - } - - return out, nil -} - -func setLinkAddress(link netlink.Link, cidr *string) error { - - var err error - var addr *netlink.Addr - - if cidr != nil && *cidr != "" { - addr, err = netlink.ParseAddr(*cidr) - if err != nil { - return err - } - } - - if addr != nil { - err = netlink.AddrReplace(link, addr) - if err != nil { - return err - } - } else { - addrs, err := netlink.AddrList(link, 0) - if err != nil { - return err - } - for _, addr := range addrs { - err = netlink.AddrDel(link, &addr) - if err != nil { - return err - } - } - } - - return err -} diff --git a/client/state/boltdb/boltdb.go b/client/state/boltdb/boltdb.go deleted file mode 100644 index d9dc0aa..0000000 --- a/client/state/boltdb/boltdb.go +++ /dev/null @@ -1,154 +0,0 @@ -package boltdb - -import ( - "encoding/json" - "fmt" - - "github.com/seashell/drago/client/nic" - "github.com/seashell/drago/drago/structs" - "go.etcd.io/bbolt" - bolt "go.etcd.io/bbolt" -) - -var ( - interfacesBucketName = []byte("interfaces") - privateKeysBucketName = []byte("keys") -) - -// StateRepository ... -type StateRepository struct { - db *bolt.DB -} - -// NewStateRepository creates a new BoltDB state repository -func NewStateRepository(path string) (*StateRepository, error) { - db, err := bolt.Open(path, 0666, nil) - if err != nil { - return nil, err - } - - err = db.Update(func(tx *bbolt.Tx) error { - - if _, err := tx.CreateBucketIfNotExists(interfacesBucketName); err != nil { - return err - } - - if _, err := tx.CreateBucketIfNotExists(privateKeysBucketName); err != nil { - return err - } - - return nil - }) - - if err != nil { - panic(err) - } - - return &StateRepository{db}, nil - -} - -// Name : -func (r *StateRepository) Name() string { - return "boltdb" -} - -// Interfaces : -func (r *StateRepository) Interfaces() ([]*structs.Interface, error) { - - ifaces := []*structs.Interface{} - - err := r.db.View(func(tx *bbolt.Tx) error { - b := tx.Bucket(interfacesBucketName) - err := b.ForEach(func(k []byte, v []byte) error { - - iface := &structs.Interface{} - - err := decode(v, iface) - if err != nil { - return err - } - - ifaces = append(ifaces, iface) - - return nil - }) - return err - }) - - return ifaces, err - -} - -// UpsertInterface : -func (r *StateRepository) UpsertInterface(iface *structs.Interface) error { - - err := r.db.Update(func(tx *bbolt.Tx) error { - b := tx.Bucket(interfacesBucketName) - return b.Put([]byte(iface.ID), encode(iface)) - }) - - return err -} - -// DeleteInterfaces : -func (r *StateRepository) DeleteInterfaces(ids []string) error { - - err := r.db.Update(func(tx *bbolt.Tx) error { - b := tx.Bucket(interfacesBucketName) - for _, id := range ids { - if err := b.Delete([]byte(id)); err != nil { - return err - } - } - return nil - }) - - return err -} - -func (r *StateRepository) KeyByID(id string) (*nic.PrivateKey, error) { - - var key *nic.PrivateKey - - err := r.db.View(func(tx *bbolt.Tx) error { - v := tx.Bucket(privateKeysBucketName).Get([]byte(id)) - if v != nil { - if err := decode(v, &key); err != nil { - return err - } - return nil - } - return fmt.Errorf("not found") - }) - - return key, err -} - -func (r *StateRepository) UpsertKey(key *nic.PrivateKey) error { - err := r.db.Update(func(tx *bbolt.Tx) error { - b := tx.Bucket(privateKeysBucketName) - return b.Put([]byte(key.ID), encode(key)) - }) - return err -} - -func (r *StateRepository) DeleteKey(id string) error { - err := r.db.View(func(tx *bbolt.Tx) error { - b := tx.Bucket(privateKeysBucketName) - return b.Delete([]byte(id)) - }) - return err -} - -func encode(in interface{}) []byte { - out, err := json.Marshal(in) - if err != nil { - panic(err) - } - return out -} - -func decode(encoded []byte, out interface{}) error { - return json.Unmarshal(encoded, out) -} diff --git a/client/state/inmem/inmem.go b/client/state/inmem/inmem.go deleted file mode 100644 index f04cf2d..0000000 --- a/client/state/inmem/inmem.go +++ /dev/null @@ -1,58 +0,0 @@ -package state - -import ( - "log" - "sync" - - "github.com/seashell/drago/drago/structs" -) - -type Repository struct { - logger log.Logger - - // interface_id -> value - interfaces map[string]*structs.Interface - - mu sync.RWMutex -} - -func NewRepository(logger log.Logger) *Repository { - return &Repository{ - interfaces: make(map[string]*structs.Interface), - logger: logger, - } -} - -func (r *Repository) Name() string { - return "inmem" -} - -func (r *Repository) Interfaces() ([]*structs.Interface, error) { - r.mu.RLock() - defer r.mu.RUnlock() - - ifaces := make([]*structs.Interface, 0, len(r.interfaces)) - for _, v := range r.interfaces { - ifaces = append(ifaces, v) - } - - return ifaces, nil -} - -func (r *Repository) UpsertInterface(iface *structs.Interface) error { - r.mu.Lock() - defer r.mu.Unlock() - r.interfaces[iface.ID] = iface - return nil -} - -func (r *Repository) DeleteInterfaces(ids []string) error { - r.mu.Lock() - defer r.mu.Unlock() - - for _, id := range ids { - delete(r.interfaces, id) - } - - return nil -} diff --git a/client/state/state.go b/client/state/state.go deleted file mode 100644 index 4ea0a39..0000000 --- a/client/state/state.go +++ /dev/null @@ -1,21 +0,0 @@ -package state - -import ( - "github.com/seashell/drago/client/nic" - "github.com/seashell/drago/drago/structs" -) - -// Repository : -type Repository interface { - Name() string - - // Client state - Interfaces() ([]*structs.Interface, error) - UpsertInterface(*structs.Interface) error - DeleteInterfaces(id []string) error - - // Key store - KeyByID(id string) (*nic.PrivateKey, error) - UpsertKey(key *nic.PrivateKey) error - DeleteKey(id string) error -} diff --git a/client/util.go b/client/util.go deleted file mode 100644 index c635d5d..0000000 --- a/client/util.go +++ /dev/null @@ -1,64 +0,0 @@ -package client - -import ( - "io/ioutil" - "os" - "reflect" - - "github.com/seashell/drago/drago/structs" -) - -type Diff struct { - created []string - deleted []string - updated []string - unchanged []string -} - -// Read file contents if they exist, or persist and return a default value otherwise. -func (c *Client) readFileLazy(path string, s string) (string, error) { - - var out string - - buf, err := ioutil.ReadFile(path) - if err != nil && !os.IsNotExist(err) { - return "", err - } - - if len(buf) != 0 { - out = string(buf) - } else { - out = s - if err := ioutil.WriteFile(path, []byte(s), 0700); err != nil { - return "", err - } - } - - return out, nil -} - -func interfacesDiff(old, new map[string]*structs.Interface) Diff { - - diff := Diff{} - - for _, vold := range old { - if _, ok := new[vold.ID]; !ok { - diff.deleted = append(diff.deleted, vold.ID) - } - } - - for _, vnew := range new { - if vold, ok := old[vnew.ID]; !ok { - diff.created = append(diff.created, vnew.ID) - } else { - vnew = vold.Merge(vnew) - if !reflect.DeepEqual(vold, vnew) { - diff.updated = append(diff.updated, vnew.ID) - } else { - diff.unchanged = append(diff.unchanged, vold.ID) - } - } - } - - return diff -} diff --git a/command/acl.go b/command/acl.go deleted file mode 100644 index 0b35643..0000000 --- a/command/acl.go +++ /dev/null @@ -1,46 +0,0 @@ -package command - -import ( - "context" - "strings" - - cli "github.com/seashell/drago/pkg/cli" -) - -// ACLCommand : -type ACLCommand struct { - UI cli.UI -} - -// Name : -func (c *ACLCommand) Name() string { - return "acl" -} - -// Synopsis : -func (c *ACLCommand) Synopsis() string { - return "Interact with ACL policies and tokens" -} - -// Run : -func (c *ACLCommand) Run(ctx context.Context, args []string) int { - return cli.CommandReturnCodeHelp -} - -// Help : -func (c *ACLCommand) Help() string { - h := ` -Usage: drago acl [options] [args] - - This command groups subcommands for interacting with ACL policies and tokens. - It can be used to bootstrap Drago's ACL system, create policies that restrict - access, and generate tokens from those policies. - - Bootstrap ACLs: - - $ drago acl bootstrap - - Please see the individual subcommand help for detailed usage information. -` - return strings.TrimSpace(h) -} diff --git a/command/acl_bootstrap.go b/command/acl_bootstrap.go deleted file mode 100644 index 560d547..0000000 --- a/command/acl_bootstrap.go +++ /dev/null @@ -1,77 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" -) - -// ACLBootstrapCommand : -type ACLBootstrapCommand struct { - UI cli.UI - - Command -} - -// Name : -func (c *ACLBootstrapCommand) Name() string { - return "acl bootstrap" -} - -// Synopsis : -func (c *ACLBootstrapCommand) Synopsis() string { - return "Bootstrap the ACL system" -} - -// Run : -func (c *ACLBootstrapCommand) Run(ctx context.Context, args []string) int { - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - token, err := api.ACL().Bootstrap() - if err != nil { - c.UI.Error(fmt.Sprintf("Error bootstrapping ACL system: %s", err)) - return 1 - } - - c.UI.Output(c.formatACLToken(token)) - - return 0 -} - -// Help : -func (c *ACLBootstrapCommand) Help() string { - h := ` -Usage: drago acl bootstrap [options] - - Bootstrap is used to bootstrap the ACL system and get an initial token. - -General Options: -` + GlobalOptions() + ` -` - return strings.TrimSpace(h) -} - -func (c *ACLBootstrapCommand) formatACLToken(t *structs.ACLToken) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - if err := enc.Encode(t); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - return b.String() -} diff --git a/command/acl_policy.go b/command/acl_policy.go deleted file mode 100644 index f945039..0000000 --- a/command/acl_policy.go +++ /dev/null @@ -1,40 +0,0 @@ -package command - -import ( - "context" - "strings" - - cli "github.com/seashell/drago/pkg/cli" -) - -// ACLPolicyCommand : -type ACLPolicyCommand struct { - UI cli.UI -} - -// Name : -func (c *ACLPolicyCommand) Name() string { - return "acl policy" -} - -// Synopsis : -func (c *ACLPolicyCommand) Synopsis() string { - return "Interact with ACL policies" -} - -// Run : -func (c *ACLPolicyCommand) Run(ctx context.Context, args []string) int { - return cli.CommandReturnCodeHelp -} - -// Help : -func (c *ACLPolicyCommand) Help() string { - h := ` -Usage: drago acl policy [options] [args] - - This command groups subcommands for interacting with ACL policies. - - Please see the individual subcommand help for detailed usage information. -` - return strings.TrimSpace(h) -} diff --git a/command/acl_policy_apply.go b/command/acl_policy_apply.go deleted file mode 100644 index 224c05b..0000000 --- a/command/acl_policy_apply.go +++ /dev/null @@ -1,94 +0,0 @@ -package command - -import ( - "context" - "fmt" - "strings" - - "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLPolicyApplyCommand : -type ACLPolicyApplyCommand struct { - UI cli.UI - Command - - // Parsed flags - description string -} - -func (c *ACLPolicyApplyCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - return flags -} - -// Name : -func (c *ACLPolicyApplyCommand) Name() string { - return "acl policy apply" -} - -// Synopsis : -func (c *ACLPolicyApplyCommand) Synopsis() string { - return "Apply ACL policy" -} - -// Run : -func (c *ACLPolicyApplyCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago acl policy apply --help'`) - return 1 - } - - name := args[0] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - p := &structs.ACLPolicy{ - Name: name, - Description: c.description, - } - if _, err := api.ACLPolicies().Upsert(p); err != nil { - c.UI.Error(fmt.Sprintf("Error applying ACL policy: %s", err)) - return 1 - } - - return 0 -} - -// Help : -func (c *ACLPolicyApplyCommand) Help() string { - h := ` -Usage: drago acl policy apply [options] - - Create or update an ACL policy. - -General Options: -` + GlobalOptions() + ` - -ACL Policy Apply Options: - - --description= - Sets the description for the ACL policy. - -` - return strings.TrimSpace(h) -} diff --git a/command/acl_policy_delete.go b/command/acl_policy_delete.go deleted file mode 100644 index 2a9cc40..0000000 --- a/command/acl_policy_delete.go +++ /dev/null @@ -1,81 +0,0 @@ -package command - -import ( - "context" - "fmt" - "strings" - - "github.com/spf13/pflag" - - cli "github.com/seashell/drago/pkg/cli" -) - -// ACLPolicyDeleteCommand : -type ACLPolicyDeleteCommand struct { - UI cli.UI - Command -} - -func (c *ACLPolicyDeleteCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - return flags -} - -// Name : -func (c *ACLPolicyDeleteCommand) Name() string { - return "acl policy delete" -} - -// Synopsis : -func (c *ACLPolicyDeleteCommand) Synopsis() string { - return "Delete ACL policy" -} - -// Run : -func (c *ACLPolicyDeleteCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago acl policy delete --help'`) - return 1 - } - - name := args[0] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - if err := api.ACLPolicies().Delete(name); err != nil { - c.UI.Error(fmt.Sprintf("Error deleting ACL policies: %s", err)) - return 1 - } - - return 0 -} - -// Help : -func (c *ACLPolicyDeleteCommand) Help() string { - h := ` -Usage: drago acl policy delete [options] - - Delete an existing ACL policy. - -General Options: -` + GlobalOptions() - - return strings.TrimSpace(h) -} diff --git a/command/acl_policy_info.go b/command/acl_policy_info.go deleted file mode 100644 index 9bea17a..0000000 --- a/command/acl_policy_info.go +++ /dev/null @@ -1,125 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLPolicyInfoCommand : -type ACLPolicyInfoCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *ACLPolicyInfoCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ACLPolicyInfoCommand) Name() string { - return "acl policy info" -} - -// Synopsis : -func (c *ACLPolicyInfoCommand) Synopsis() string { - return "Display details about an existing ACL policy" -} - -// Run : -func (c *ACLPolicyInfoCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago acl policy info --help'`) - return 1 - } - - name := args[0] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - policy, err := api.ACLPolicies().Get(name) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving ACL policy: %s", err)) - return 1 - } - - c.UI.Output(c.formatPolicy(policy)) - - return 0 -} - -// Help : -func (c *ACLPolicyInfoCommand) Help() string { - h := ` -Usage: drago acl policy info [options] - - Display information on an existing ACL policy. - - Use the --json flag to see a detailed list of the rules associated with the policy. - -General Options: -` + GlobalOptions() + ` - -ACL Policy Info Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *ACLPolicyInfoCommand) formatPolicy(policy *structs.ACLPolicy) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - fpolicy := map[string]interface{}{ - "name": policy.Name, - "description": policy.Description, - "rules": policy.Rules, - } - if err := enc.Encode(fpolicy); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("POLICY NAME", "DESCRIPTION", "RULES").WithWriter(&b) - tbl.AddRow(policy.Name, policy.Description, len(policy.Rules)) - tbl.Print() - } - - return b.String() -} diff --git a/command/acl_policy_list.go b/command/acl_policy_list.go deleted file mode 100644 index 8944720..0000000 --- a/command/acl_policy_list.go +++ /dev/null @@ -1,129 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLPolicyListCommand : -type ACLPolicyListCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *ACLPolicyListCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ACLPolicyListCommand) Name() string { - return "acl policy list" -} - -// Synopsis : -func (c *ACLPolicyListCommand) Synopsis() string { - return "List ACL policies" -} - -// Run : -func (c *ACLPolicyListCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago acl policy list --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - policies, err := api.ACLPolicies().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving ACL policies: %s", err)) - return 1 - } - - if len(policies) == 0 { - return 0 - } - - c.UI.Output(c.formatPolicyList(policies)) - - return 0 -} - -// Help : -func (c *ACLPolicyListCommand) Help() string { - h := ` -Usage: drago acl policy list [options] - - List existing ACL policies. - -General Options: -` + GlobalOptions() + ` - -ACL Policy List Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *ACLPolicyListCommand) formatPolicyList(policies []*structs.ACLPolicyListStub) string { - - var b bytes.Buffer - fpolicies := []interface{}{} - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - for _, policy := range policies { - fpolicies = append(fpolicies, map[string]string{ - "name": policy.Name, - "description": policy.Description, - }) - } - if err := enc.Encode(fpolicies); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("POLICY NAME", "DESCRIPTION").WithWriter(&b) - for _, policy := range policies { - tbl.AddRow(policy.Name, policy.Description) - } - tbl.Print() - } - - return b.String() -} diff --git a/command/acl_token.go b/command/acl_token.go deleted file mode 100644 index 923dbab..0000000 --- a/command/acl_token.go +++ /dev/null @@ -1,40 +0,0 @@ -package command - -import ( - "context" - "strings" - - cli "github.com/seashell/drago/pkg/cli" -) - -// ACLTokenCommand : -type ACLTokenCommand struct { - UI cli.UI -} - -// Name : -func (c *ACLTokenCommand) Name() string { - return "acl token" -} - -// Synopsis : -func (c *ACLTokenCommand) Synopsis() string { - return "Interact with ACL tokens" -} - -// Run : -func (c *ACLTokenCommand) Run(ctx context.Context, args []string) int { - return cli.CommandReturnCodeHelp -} - -// Help : -func (c *ACLTokenCommand) Help() string { - h := ` -Usage: drago acl token [options] [args] - - This command groups subcommands for interacting with ACL tokens. - - Please see the individual subcommand help for detailed usage information. -` - return strings.TrimSpace(h) -} diff --git a/command/acl_token_create.go b/command/acl_token_create.go deleted file mode 100644 index 0005dc3..0000000 --- a/command/acl_token_create.go +++ /dev/null @@ -1,143 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLTokenCreateCommand : -type ACLTokenCreateCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool - name string - policyType string - policies []string -} - -func (c *ACLTokenCreateCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.StringVar(&c.name, "name", "", "") - flags.StringVar(&c.policyType, "type", "", "") - flags.StringSliceVar(&c.policies, "policy", []string{}, "") - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ACLTokenCreateCommand) Name() string { - return "acl token create" -} - -// Synopsis : -func (c *ACLTokenCreateCommand) Synopsis() string { - return "Create a new ACL token" -} - -// Run : -func (c *ACLTokenCreateCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago acl token create --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - token, err := api.ACLTokens().Create(&structs.ACLToken{ - Name: c.name, - Type: c.policyType, - Policies: c.policies, - }) - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating ACL token: %s", err)) - return 1 - } - - c.UI.Output(c.formatToken(token)) - - return 0 -} - -// Help : -func (c *ACLTokenCreateCommand) Help() string { - h := ` -Usage: drago acl token create [options] - - Create is used to issue a new ACL token. Requires a management token. - -General Options: -` + GlobalOptions() + ` - -ACL Token Create Options: - - --name= - Sets the human readable name for the ACL token. - - --type= - Sets the type of token. Must be one of "client" (default), or "management". - - --policy= - Specifies a policy to associate with client tokens. - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *ACLTokenCreateCommand) formatToken(token *structs.ACLToken) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "id": token.ID, - "name": token.Name, - "type": token.Type, - "secret": token.Secret, - "policies": token.Policies, - "createdAt": token.CreatedAt, - "updatedAt": token.UpdatedAt, - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} diff --git a/command/acl_token_delete.go b/command/acl_token_delete.go deleted file mode 100644 index 0dd6f8d..0000000 --- a/command/acl_token_delete.go +++ /dev/null @@ -1,80 +0,0 @@ -package command - -import ( - "context" - "fmt" - "strings" - - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLTokenDeleteCommand : -type ACLTokenDeleteCommand struct { - UI cli.UI - Command -} - -func (c *ACLTokenDeleteCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - return flags -} - -// Name : -func (c *ACLTokenDeleteCommand) Name() string { - return "acl token delete" -} - -// Synopsis : -func (c *ACLTokenDeleteCommand) Synopsis() string { - return "Delete an existing ACL token" -} - -// Run : -func (c *ACLTokenDeleteCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago acl token delete --help'`) - return 1 - } - - id := args[0] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - if err := api.ACLTokens().Delete(id); err != nil { - c.UI.Error(fmt.Sprintf("Error deleting ACL token: %s", err)) - return 1 - } - - return 0 -} - -// Help : -func (c *ACLTokenDeleteCommand) Help() string { - h := ` -Usage: drago acl token delete [options] - - Delete is used to delete an existing ACL token. Requires a management token. - -General Options: -` + GlobalOptions() - - return strings.TrimSpace(h) -} diff --git a/command/acl_token_info.go b/command/acl_token_info.go deleted file mode 100644 index ba8a9bd..0000000 --- a/command/acl_token_info.go +++ /dev/null @@ -1,127 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLTokenInfoCommand : -type ACLTokenInfoCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *ACLTokenInfoCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ACLTokenInfoCommand) Name() string { - return "acl token info" -} - -// Synopsis : -func (c *ACLTokenInfoCommand) Synopsis() string { - return "Display details about an existing ACL token" -} - -// Run : -func (c *ACLTokenInfoCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago acl token info --help'`) - return 1 - } - - id := args[0] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - token, err := api.ACLTokens().Get(id) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving ACL token: %s", err)) - return 1 - } - - c.UI.Output(c.formatToken(token)) - - return 0 -} - -// Help : -func (c *ACLTokenInfoCommand) Help() string { - h := ` -Usage: drago acl token info [options] - - Display information on an existing ACL token. - -General Options: -` + GlobalOptions() + ` - -ACL Token Info Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *ACLTokenInfoCommand) formatToken(token *structs.ACLToken) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "id": token.ID, - "name": token.Name, - "type": token.Type, - "secret": token.Secret, - "policies": token.Policies, - "createdAt": token.CreatedAt, - "updatedAt": token.UpdatedAt, - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("TOKEN ID", "NAME", "TYPE", "SECRET", "POLICIES").WithWriter(&b) - tbl.AddRow(token.ID, token.Name, token.Type, token.Secret, len(token.Policies)) - tbl.Print() - } - - return b.String() -} diff --git a/command/acl_token_list.go b/command/acl_token_list.go deleted file mode 100644 index bbd444f..0000000 --- a/command/acl_token_list.go +++ /dev/null @@ -1,130 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLTokenListCommand : -type ACLTokenListCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *ACLTokenListCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ACLTokenListCommand) Name() string { - return "acl token list" -} - -// Synopsis : -func (c *ACLTokenListCommand) Synopsis() string { - return "List ACL tokens" -} - -// Run : -func (c *ACLTokenListCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago acl token list --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - tokens, err := api.ACLTokens().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving ACL tokens: %s", err)) - return 1 - } - - if len(tokens) == 0 { - return 0 - } - - c.UI.Output(c.formatTokenList(tokens)) - - return 0 -} - -// Help : -func (c *ACLTokenListCommand) Help() string { - h := ` -Usage: drago acl token list [options] - - List existing ACL tokens. - -General Options: -` + GlobalOptions() + ` - -ACL Policy List Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *ACLTokenListCommand) formatTokenList(tokens []*structs.ACLTokenListStub) string { - - var b bytes.Buffer - ftokens := []interface{}{} - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - for _, token := range tokens { - ftokens = append(ftokens, map[string]string{ - "id": token.ID, - "name": token.Name, - "type": token.Type, - }) - } - if err := enc.Encode(ftokens); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("TOKEN ID", "NAME", "TYPE").WithWriter(&b) - for _, token := range tokens { - tbl.AddRow(token.ID, token.Name, token.Type) - } - tbl.Print() - } - - return b.String() -} diff --git a/command/acl_token_self.go b/command/acl_token_self.go deleted file mode 100644 index 869c50f..0000000 --- a/command/acl_token_self.go +++ /dev/null @@ -1,127 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLTokenSelfCommand : -type ACLTokenSelfCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *ACLTokenSelfCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ACLTokenSelfCommand) Name() string { - return "acl token self" -} - -// Synopsis : -func (c *ACLTokenSelfCommand) Synopsis() string { - return "Lookup self ACL token" -} - -// Run : -func (c *ACLTokenSelfCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago acl token self --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - token, err := api.ACLTokens().Self() - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving self ACL token: %s", err)) - return 1 - } - - c.UI.Output(c.formatToken(token)) - - return 0 -} - -// Help : -func (c *ACLTokenSelfCommand) Help() string { - h := ` -Usage: drago acl token self [options] - - Display information on the currently set ACL policy. - - Use the --json flag to see a detailed list of the rules associated with the token. - -General Options: -` + GlobalOptions() + ` - -ACL Token Info Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *ACLTokenSelfCommand) formatToken(token *structs.ACLToken) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "id": token.ID, - "name": token.Name, - "type": token.Type, - "secret": token.Secret, - "policies": token.Policies, - "createdAt": token.CreatedAt, - "updatedAt": token.UpdatedAt, - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("TOKEN ID", "NAME", "TYPE", "SECRET", "POLICIES").WithWriter(&b) - tbl.AddRow(token.ID, token.Name, token.Type, token.Secret, len(token.Policies)) - tbl.Print() - } - - return b.String() -} diff --git a/command/acl_token_update.go b/command/acl_token_update.go deleted file mode 100644 index d77c641..0000000 --- a/command/acl_token_update.go +++ /dev/null @@ -1,143 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ACLTokenUpdateCommand : -type ACLTokenUpdateCommand struct { - UI cli.UI - Command - - // parsed flags - json bool - name string - policyType string - policies []string -} - -func (c *ACLTokenUpdateCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.StringVar(&c.name, "name", "", "") - flags.StringVar(&c.policyType, "type", "", "") - flags.StringSliceVar(&c.policies, "policy", []string{}, "") - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ACLTokenUpdateCommand) Name() string { - return "acl token update" -} - -// Synopsis : -func (c *ACLTokenUpdateCommand) Synopsis() string { - return "Update ACL token" -} - -// Run : -func (c *ACLTokenUpdateCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago acl token update --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - token, err := api.ACLTokens().Update(&structs.ACLToken{ - Name: c.name, - Type: c.policyType, - Policies: c.policies, - }) - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating ACL token: %s", err)) - return 1 - } - - c.UI.Output(c.formatToken(token)) - - return 0 -} - -// Help : -func (c *ACLTokenUpdateCommand) Help() string { - h := ` -Usage: drago acl token update [options] - - Update an ACL token. - -General Options: -` + GlobalOptions() + ` - -ACL Token Update Options: - - --name= - Sets the name of the ACL token. - - --type= - Sets the type of the ACL token. Must be either "client" or "management". If not provided, defaults to "client". - - --policy= - Specifies policies to associate with a client token. Can be specified multiple times. - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *ACLTokenUpdateCommand) formatToken(token *structs.ACLToken) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "id": token.ID, - "name": token.Name, - "type": token.Type, - "secret": token.Secret, - "policies": token.Policies, - "createdAt": token.CreatedAt, - "updatedAt": token.UpdatedAt, - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} diff --git a/command/agent.go b/command/agent.go deleted file mode 100644 index 29b58d3..0000000 --- a/command/agent.go +++ /dev/null @@ -1,439 +0,0 @@ -package command - -import ( - "context" - "fmt" - "net/http" - "os" - "strconv" - "strings" - - "github.com/caarlos0/env" - "github.com/dimiro1/banner" - "github.com/hashicorp/hcl/v2/hclsimple" - "github.com/joho/godotenv" - agent "github.com/seashell/drago/agent" - cli "github.com/seashell/drago/pkg/cli" - log "github.com/seashell/drago/pkg/log" - simple "github.com/seashell/drago/pkg/log/simple" - "github.com/spf13/pflag" -) - -// AgentCommand : -type AgentCommand struct { - config *agent.Config - agent *agent.Agent - logger log.Logger - - UI cli.UI - StaticFS http.FileSystem - - Command - - // Parsed flags - dev bool - servers string - envs []string - configs []string - meta []string - node string - bind string - dataDir string - logLevel string - pluginDir string - server bool - client bool - stateDir string - wireguardPath string - aclEnabled bool -} - -func (c *AgentCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options (available in both client and server modes) - flags.StringSliceVar(&c.configs, "config", []string{}, "") - flags.StringVar(&c.bind, "bind", "", "") - flags.StringVar(&c.node, "node", "", "") - flags.BoolVar(&c.dev, "dev", false, "") - flags.StringVar(&c.dataDir, "data-dir", "", "") - flags.StringVar(&c.logLevel, "log-level", "", "") - flags.StringVar(&c.pluginDir, "plugin-dir", "", "") - flags.StringSliceVar(&c.envs, "env", []string{}, "") - - // Server-only options - flags.BoolVar(&c.server, "server", false, "") - - // Client-only options - flags.StringSliceVar(&c.meta, "meta", []string{}, "") - flags.BoolVar(&c.client, "client", false, "") - flags.StringVar(&c.servers, "servers", "", "") - flags.StringVar(&c.stateDir, "state-dir", "", "") - flags.StringVar(&c.wireguardPath, "wireguard-path", "", "") - - // ACL options - flags.BoolVar(&c.aclEnabled, "acl-enabled", false, "") - - return flags -} - -// Name : -func (c *AgentCommand) Name() string { - return "agent" -} - -// Synopsis : -func (c *AgentCommand) Synopsis() string { - return "Run a Drago agent" -} - -// Run : -func (c *AgentCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - c.UI.Error("==> Error: " + err.Error() + "\n") - os.Exit(1) - } - - if !c.server && !c.client && !c.dev { - c.UI.Output("==> Must specify either client, server or dev mode for the agent.") - os.Exit(1) - } - - printBanner() - - err := c.parseConfig(args) - if err != nil { - c.UI.Error(fmt.Sprintf("Invalid input: %s", err.Error())) - os.Exit(1) - } - - if err := c.setupLogger(); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - c.UI.Output("==> Starting Drago agent...") - - if err = c.setupDirectories(); err != nil { - c.UI.Error("Error setting up data directories") - } - - c.printConfig() - - if err = c.setupAgent(); err != nil { - c.UI.Error(fmt.Sprintf("Error setting up agent: %s\n", err.Error())) - return 1 - } - - <-ctx.Done() - - c.agent.Shutdown() - - return 0 -} - -// Help : -func (c *AgentCommand) Help() string { - h := ` -Usage: drago agent [options] - - Starts the Drago agent and runs until an interrupt is received. - The agent may be a client and/or server. - - The Drago agent's configuration primarily comes from the config - files used, but a subset of the options may also be passed directly - as CLI arguments. - -General Options (clients and servers): -` + GlobalOptions() + ` - - --bind= - The address the agent will bind to for all of its various network - services. The individual services that run bind to individual - ports on this address. Defaults to the loopback 127.0.0.1. - - --config= - Path to a HCL file containing valid Drago configurations. - Overrides the DRAGO_CONFIG_PATH environment variable if set. - - --data-dir= - The data directory where all state will be persisted. On Drago - clients this is used to store local network configurations, whereas - on server nodes, the data dir is also used to keep the desired state - for all the managed networks. Overrides the DRAGO_DATA_DIRenvironment - variable if set. - - --node= - The name of the local agent, use to identify the node. If not provided, - defaults to the hostname of the machine. - - --dev - Start the agent in development mode. This enables a pre-configured - dual-role agent (client + server) which is useful for developing - or testing Drago. No other configuration is required to start the - agent in this mode. - - --log-level= - The logging level Drago should log at. Valid values are INFO, WARN, - DEBUG, ERROR, FATAL. Overrides the DRAGO_LOG_LEVEL environment variable - if set. - - --plugin-dir= - The plugin directory is used to discover Drago plugins. If not specified, - the plugin directory defaults to be that of /plugins/. - -Server Options: - - --server - Enable server mode for the agent. - -Client Options: - - --client - Enable client mode for the agent. Client mode enables a given node to be - evaluated for allocations. If client mode is not enabled, no work will be - scheduled to the agent. - - --state-dir - The directory used to store state and other persistent data. If not - specified a subdirectory under the "-data-dir" will be used. - - --servers - A comma-separated list of known server addresses to connect to in - "host:port" format. - - --meta - User-specified metadata in KEY=VALUE format to associate with the node. - Repeat the meta flag for each key/value pair to be added. - -ACL Options: - - --acl-enabled - Specifies whether the agent should enable ACLs. - -` - return strings.TrimSpace(h) -} - -func (c *AgentCommand) setupAgent() error { - - c.config.StaticFS = c.StaticFS - - agent, err := agent.New(c.config, c.logger) - if err != nil { - return err - } - - c.agent = agent - - return nil -} - -func (c *AgentCommand) parseConfig(args []string) error { - - configFromFlags := c.parseFlags() - - configFromFile := c.parseConfigFiles(c.configs...) - configFromEnv := c.parseEnv(c.envs...) - - config := agent.DefaultConfig() - - config = config.Merge(configFromFile) - config = config.Merge(configFromEnv) - config = config.Merge(configFromFlags) - - if err := config.Validate(); err != nil { - return err - } - - c.config = config - - return nil -} - -func (c *AgentCommand) parseFlags() *agent.Config { - - config := agent.EmptyConfig() - - config.DevMode = &c.dev - config.Server.Enabled = c.server - config.Client.Enabled = c.client - - config.Name = c.node - config.BindAddr = c.bind - config.DataDir = c.dataDir - config.LogLevel = c.logLevel - config.PluginDir = c.pluginDir - - config.Client.StateDir = c.stateDir - config.Client.WireguardPath = c.wireguardPath - - config.ACL.Enabled = c.aclEnabled - - if *config.DevMode { - config.Server.Enabled = true - config.Client.Enabled = true - config.DataDir = "/tmp/drago" - config.LogLevel = "DEBUG" - } - - if c.servers != "" { - config.Client.Servers = strings.Split(c.servers, ",") - } - - metaLength := len(c.meta) - if metaLength != 0 { - config.Client.Meta = make(map[string]string, metaLength) - for _, kv := range c.meta { - parts := strings.SplitN(kv, "=", 2) - if len(parts) != 2 { - c.UI.Error(fmt.Sprintf("Error parsing Client.Meta value: %v", kv)) - return nil - } - config.Client.Meta[parts[0]] = parts[1] - } - } - - return config -} - -func (c *AgentCommand) parseConfigFiles(paths ...string) *agent.Config { - - config := agent.EmptyConfig() - - if len(paths) > 0 { - c.UI.Info(fmt.Sprintf("==> Loading configurations from: %v", paths)) - for _, s := range paths { - err := hclsimple.DecodeFile(s, nil, config) - if err != nil { - c.UI.Error("Failed to load configuration: " + err.Error()) - os.Exit(0) - } - } - } else { - c.UI.Output("==> No configuration files loaded") - } - - return config -} - -func (c *AgentCommand) parseEnv(paths ...string) *agent.Config { - - config := agent.EmptyConfig() - - if len(paths) > 0 { - - c.UI.Info(fmt.Sprintf("==> Loading environment variables from: %v", paths)) - c.UI.Warn(" - This will not override already existing variables!") - - err := godotenv.Load(paths...) - - if err != nil { - c.UI.Error(fmt.Sprintf("Error parsing env files: %s", err.Error())) - os.Exit(1) - } - } - - env.Parse(config) - - return config -} - -func (c *AgentCommand) printConfig() { - - config := c.config - - info := map[string]string{ - "data dir": config.DataDir, - "bind addrs": bindAddrsString(config), - "advertise addrs": advertiseAddrsString(config), - "log level": config.LogLevel, - "client": strconv.FormatBool(config.Client.Enabled), - "server": strconv.FormatBool(config.Server.Enabled), - "version": config.Version.VersionNumber(), - "acl enabled": strconv.FormatBool(config.ACL.Enabled), - } - - padding := 18 - c.UI.Output("==> Drago agent configuration:\n") - for k := range info { - c.UI.Info(fmt.Sprintf( - "%s%s: %v", - strings.Repeat(" ", padding-len(k)), - strings.Title(k), - info[k])) - } - - c.UI.Output("") -} - -func (c *AgentCommand) setupLogger() error { - - // logger, err := logrus.NewLoggerAdapter(logrus.Config{ - // LoggerOptions: log.LoggerOptions{ - // Level: c.config.LogLevel, - // Prefix: "agent: ", - // }, - // }) - - // logger, err := zap.NewLoggerAdapter(zap.Config{ - // LoggerOptions: log.LoggerOptions{ - // Level: c.config.LogLevel, - // Prefix: "agent: ", - // }, - // }) - - logger, err := simple.NewLoggerAdapter(simple.Config{ - LoggerOptions: log.LoggerOptions{ - Level: c.config.LogLevel, - Prefix: "agent: ", - }, - }) - - if err != nil { - return err - } - - c.logger = logger - - return nil -} - -// Create DataDir and other subdirectories if they do not exist -func (c *AgentCommand) setupDirectories() error { - if _, err := os.Stat(c.config.DataDir); os.IsNotExist(err) { - os.Mkdir(c.config.DataDir, 0755) - } - return nil -} - -// Prints an ASCII banner to the standard output -func printBanner() { - banner.Init(os.Stdout, true, true, strings.NewReader(agent.Banner)) -} - -func bindAddrsString(config *agent.Config) string { - http := fmt.Sprintf("%s:%d", config.BindAddr, config.Ports.HTTP) - rpc := fmt.Sprintf("%s:%d", config.BindAddr, config.Ports.RPC) - return fmt.Sprintf("HTTP: %s; RPC: %s", http, rpc) -} - -func advertiseAddrsString(config *agent.Config) string { - - peer := config.BindAddr - if config.AdvertiseAddrs.Peer != "" { - peer = config.AdvertiseAddrs.Peer - } - - server := fmt.Sprintf("%s:%d", config.BindAddr, config.Ports.RPC) - if config.AdvertiseAddrs.Server != "" { - server = fmt.Sprintf("%s:%d", config.AdvertiseAddrs.Server, config.Ports.RPC) - } - - return fmt.Sprintf("Peer: %s; Server: %s", peer, server) -} diff --git a/command/agent_info.go b/command/agent_info.go deleted file mode 100644 index e09266e..0000000 --- a/command/agent_info.go +++ /dev/null @@ -1,120 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// AgentInfoCommand : -type AgentInfoCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *AgentInfoCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *AgentInfoCommand) Name() string { - return "agent-info" -} - -// Synopsis : -func (c *AgentInfoCommand) Synopsis() string { - return "Display status information about the local agent" -} - -// Run : -func (c *AgentInfoCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago agent info --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - info, err := api.Agent().Self() - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving agent info: %s", err)) - return 1 - } - - c.UI.Output(c.formatAgentInfo(info)) - - return 0 -} - -// Help : -func (c *AgentInfoCommand) Help() string { - h := ` -Usage: drago agent-info [options] - - Display status information about the local agent. - -General Options: -` + GlobalOptions() + ` - -Agent Info Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *AgentInfoCommand) formatAgentInfo(info *structs.Agent) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - fpolicy := map[string]interface{}{ - "config": info.Config, - "stats": info.Stats, - } - if err := enc.Encode(fpolicy); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} diff --git a/command/command.go b/command/command.go deleted file mode 100644 index ee0158d..0000000 --- a/command/command.go +++ /dev/null @@ -1,77 +0,0 @@ -package command - -import ( - "fmt" - "strings" - - "github.com/spf13/pflag" - - api "github.com/seashell/drago/api" - cli "github.com/seashell/drago/pkg/cli" -) - -// Command is the base command -type Command struct { - address string - token string -} - -// FlagSet declares flags that are common to all commands, -// returning a pflag.FlagSet struct that will hold their values after -// pflag.Parse() is called by the command. -func (c *Command) FlagSet(name string) *pflag.FlagSet { - - flags := pflag.NewFlagSet(name, pflag.ContinueOnError) - - flags.Usage = func() {} - - flags.StringVar(&c.address, "address", "", "") - flags.StringVar(&c.token, "token", "", "") - - // TODO: direct output to UI - flags.SetOutput(nil) - - return flags -} - -// APIClient returns a new api.Client struct, -// which can be used to interact with the Drago HTTP API. -func (c *Command) APIClient() (*api.Client, error) { - return api.NewClient(&api.Config{ - Address: c.address, - Token: c.token, - }) -} - -// GlobalOptions returns the global usage options string. -func GlobalOptions() string { - text := ` - --address= - The address of the Drago server. - Overrides the DRAGO_ADDR environment variable if set. - Default = http://127.0.0.1:8080 - - --token= - The token used to authenticate with the Drago server. - Overrides the DRAGO_TOKEN environment variable if set. - Default = "" -` - return text -} - -// DefaultErrorMessage returns the default error message for this command -func DefaultErrorMessage(cmd cli.NamedCommand) string { - return fmt.Sprintf("For additional help try 'drago %s --help'", cmd.Name()) -} - -// manyStrings -type manyStrings []string - -func (s *manyStrings) Set(val string) error { - *s = append(*s, val) - return nil -} - -func (s *manyStrings) String() string { - return strings.Join(*s, ",") -} diff --git a/command/connection.go b/command/connection.go deleted file mode 100644 index 6f70918..0000000 --- a/command/connection.go +++ /dev/null @@ -1,43 +0,0 @@ -package command - -import ( - "context" - "strings" - - cli "github.com/seashell/drago/pkg/cli" -) - -// ConnectionCommand : -type ConnectionCommand struct { - UI cli.UI -} - -// Name : -func (c *ConnectionCommand) Name() string { - return "connection" -} - -// Synopsis : -func (c *ConnectionCommand) Synopsis() string { - return "Interact with connections" -} - -// Run : -func (c *ConnectionCommand) Run(ctx context.Context, args []string) int { - return cli.CommandReturnCodeHelp -} - -// Help : -func (c *ConnectionCommand) Help() string { - h := ` -Usage: drago connection [options] [args] - - This command groups subcommands for interacting with connections. - - Please see the individual subcommand help for detailed usage information. - -General Options: -` + GlobalOptions() + ` -` - return strings.TrimSpace(h) -} diff --git a/command/connection_create.go b/command/connection_create.go deleted file mode 100644 index 36f1541..0000000 --- a/command/connection_create.go +++ /dev/null @@ -1,252 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ConnectionCreateCommand : -type ConnectionCreateCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool - persistentKeepalive int - allowAll bool -} - -func (c *ConnectionCreateCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - flags.IntVar(&c.persistentKeepalive, "keepalive", 0, "") - flags.BoolVar(&c.allowAll, "allow-all", false, "") - - return flags -} - -// Name : -func (c *ConnectionCreateCommand) Name() string { - return "connection create" -} - -// Synopsis : -func (c *ConnectionCreateCommand) Synopsis() string { - return "Create a new connection" -} - -// Run : -func (c *ConnectionCreateCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 3 { - c.UI.Error("This command takes three arguments: ") - c.UI.Error(`For additional help, try 'drago connection create --help'`) - return 1 - } - - networkID := "" - networkAddressRange := "" - - nodeIDs := []string{args[0], args[1]} - networkName := args[2] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - // Resolve network name - networks, err := api.Networks().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error getting networks: %s", err)) - return 1 - } - - for _, n := range networks { - if n.Name == networkName { - networkID = n.ID - networkAddressRange = n.AddressRange - break - } - } - - if networkID == "" { - c.UI.Error("Error: network not found") - return 1 - } - - conn := &structs.Connection{ - NetworkID: networkID, - PersistentKeepalive: &c.persistentKeepalive, - PeerSettings: []*structs.PeerSettings{}, - } - - for idx, nodeID := range nodeIDs { - - filters := map[string][]string{ - "node": {nodeID}, - } - - interfaces, err := api.Interfaces().List(filters) - if err != nil { - c.UI.Error(fmt.Sprintf("Error getting node interfaces: %s", err)) - return 1 - } - - // Find interface in node which is connected to the target network, - // and add it to the connection struct together with default settings. - for _, iface := range interfaces { - if iface.NetworkID == networkID { - - conn.PeerSettings = append(conn.PeerSettings, &structs.PeerSettings{ - InterfaceID: iface.ID, - RoutingRules: &structs.RoutingRules{ - AllowedIPs: []string{}, - }, - }) - - // Allow all traffic if allowAll is set - if c.allowAll { - conn.PeerSettingsByInterfaceID(iface.ID).RoutingRules.AllowedIPs = []string{networkAddressRange} - } - - break - } - } - - if len(conn.PeerSettings) < idx+1 { - c.UI.Error(fmt.Sprintf("Error: node %s does not have any interface in network %s", nodeID, networkID)) - return 1 - } - } - - err = api.Connections().Create(conn) - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating connection: %s", err)) - return 1 - } - - //c.UI.Output(c.formatConnection(connection)) - - return 0 -} - -// Help : -func (c *ConnectionCreateCommand) Help() string { - h := ` -Usage: drago connection create [options] - - Create is used to create a new connection between two nodes that have interfaces in the same network. - - If ACLs are enabled, this option requires a token with the 'connection:write' capability. - -General Options: -` + GlobalOptions() + ` - -ACL Token Create Options: - - --json - Enable JSON output. - - --allow-all - Enables routing of all traffic in this connection. - - --keepalive= - Time interval between persistent keepalive packets. Defaults to 0, which disables the feature. - -` - return strings.TrimSpace(h) -} - -func (c *ConnectionCreateCommand) formatConnection(connection *structs.Connection) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "id": connection.ID, - "networkId": connection.NetworkID, - "persistentKeepalive": connection.PersistentKeepalive, - "peerSettings": c.formatPeerSettings(connection.PeerSettings), - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} - -func (c *ConnectionCreateCommand) formatPeerSettings(peerSettings []*structs.PeerSettings) string { - - var b bytes.Buffer - - for _, peer := range peerSettings { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "nodeId": peer.NodeID, - "interfaceID": peer.InterfaceID, - "routingRules": c.formatRoutingRules(peer.RoutingRules), - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} - -func (c *ConnectionCreateCommand) formatRoutingRules(rules *structs.RoutingRules) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "allowedIps": rules.AllowedIPs, - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} diff --git a/command/connection_delete.go b/command/connection_delete.go deleted file mode 100644 index d464412..0000000 --- a/command/connection_delete.go +++ /dev/null @@ -1,85 +0,0 @@ -package command - -import ( - "context" - "fmt" - "strings" - - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ConnectionDeleteCommand : -type ConnectionDeleteCommand struct { - UI cli.UI - Command -} - -func (c *ConnectionDeleteCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - return flags -} - -// Name : -func (c *ConnectionDeleteCommand) Name() string { - return "connection delete" -} - -// Synopsis : -func (c *ConnectionDeleteCommand) Synopsis() string { - return "Delete an existing connection" -} - -// Run : -func (c *ConnectionDeleteCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 3 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago connection delete --help'`) - return 1 - } - - id := args[0] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - err = api.Connections().Delete(id) - if err != nil { - c.UI.Error(fmt.Sprintf("Error deleting connection: %s", err)) - return 1 - } - - c.UI.Output("Deleted!") - - return 0 -} - -// Help : -func (c *ConnectionDeleteCommand) Help() string { - h := ` -Usage: drago connection delete [options] - - Delete is used to delete an existing connection. - - If ACLs are enabled, this option requires a token with the 'connection:write' capability. - -General Options: -` + GlobalOptions() + ` -` - return strings.TrimSpace(h) -} diff --git a/command/connection_list.go b/command/connection_list.go deleted file mode 100644 index fde36c0..0000000 --- a/command/connection_list.go +++ /dev/null @@ -1,130 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ConnectionListCommand : -type ConnectionListCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *ConnectionListCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ConnectionListCommand) Name() string { - return "connection list" -} - -// Synopsis : -func (c *ConnectionListCommand) Synopsis() string { - return "Display a list of connections" -} - -// Run : -func (c *ConnectionListCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago connection list --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - connections, err := api.Connections().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving connections: %s", err)) - return 1 - } - - if len(connections) == 0 { - return 0 - } - - c.UI.Output(c.formatConnectionList(connections)) - - return 0 -} - -// Help : -func (c *ConnectionListCommand) Help() string { - h := ` -Usage: drago connection list [options] - - Lists connections managed by Drago. - - If ACLs are enabled, this option requires a token with the 'connection:read' capability. - -General Options: -` + GlobalOptions() + ` - -Connection List Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *ConnectionListCommand) formatConnectionList(connections []*structs.ConnectionListStub) string { - - var b bytes.Buffer - fconnections := []interface{}{} - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - for _, conn := range connections { - fconnections = append(fconnections, map[string]string{ - "id": conn.ID, - }) - } - if err := enc.Encode(fconnections); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("CONNECTION ID").WithWriter(&b) - for _, conn := range connections { - tbl.AddRow(conn.ID) - } - tbl.Print() - } - - return b.String() -} diff --git a/command/connection_update.go b/command/connection_update.go deleted file mode 100644 index 7cc8611..0000000 --- a/command/connection_update.go +++ /dev/null @@ -1,188 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ConnectionUpdateCommand : -type ConnectionUpdateCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool - persistentKeepalive int - allowAll bool -} - -func (c *ConnectionUpdateCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - flags.IntVar(&c.persistentKeepalive, "keepalive", 0, "") - flags.BoolVar(&c.allowAll, "allow-all", false, "") - - return flags -} - -// Name : -func (c *ConnectionUpdateCommand) Name() string { - return "connection update" -} - -// Synopsis : -func (c *ConnectionUpdateCommand) Synopsis() string { - return "Update an existing connection" -} - -// Run : -func (c *ConnectionUpdateCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 3 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago connection update --help'`) - return 1 - } - - connectionID := "" - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - conn := &structs.Connection{ - ID: connectionID, - PersistentKeepalive: &c.persistentKeepalive, - } - - rcvConn, err := api.Connections().Update(conn) - if err != nil { - c.UI.Error(fmt.Sprintf("Error updating connection: %s", err)) - return 1 - } - - c.UI.Output(c.formatConnection(rcvConn)) - - return 0 -} - -// Help : -func (c *ConnectionUpdateCommand) Help() string { - h := ` -Usage: drago connection update [options] - - Update is used to update an existing connection between two nodes that have interfaces in the same network. - - If ACLs are enabled, this option requires a token with the 'connection:write' capability. - -General Options: -` + GlobalOptions() + ` - -ACL Token Create Options: - - --json - Enable JSON output. - - --allow-all - Enables routing of all traffic in this connection. - - --keepalive= - Time interval between persistent keepalive packets. Defaults to 0, which disables the feature. - -` - return strings.TrimSpace(h) -} - -func (c *ConnectionUpdateCommand) formatConnection(connection *structs.Connection) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "id": connection.ID, - "networkId": connection.NetworkID, - "persistentKeepalive": connection.PersistentKeepalive, - "peerSettings": c.formatPeerSettings(connection.PeerSettings), - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} - -func (c *ConnectionUpdateCommand) formatPeerSettings(peerSettings []*structs.PeerSettings) string { - - var b bytes.Buffer - - for _, peer := range peerSettings { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "nodeId": peer.NodeID, - "interfaceID": peer.InterfaceID, - "routingRules": c.formatRoutingRules(peer.RoutingRules), - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} - -func (c *ConnectionUpdateCommand) formatRoutingRules(rules *structs.RoutingRules) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "allowedIps": rules.AllowedIPs, - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} diff --git a/command/connection_update_rules.go b/command/connection_update_rules.go deleted file mode 100644 index 936a43c..0000000 --- a/command/connection_update_rules.go +++ /dev/null @@ -1,235 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// ConnectionUpdateRulesCommand : -type ConnectionUpdateRulesCommand struct { - UI cli.UI - Command - - // Parsed flags - allow []string - allowAll bool - allowNone bool - on string - json bool -} - -func (c *ConnectionUpdateRulesCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.StringVar(&c.on, "on", "", "") - flags.StringSliceVar(&c.allow, "allow", []string{}, "") - flags.BoolVar(&c.allowAll, "allow-all", false, "") - flags.BoolVar(&c.allowNone, "allow-none", false, "") - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *ConnectionUpdateRulesCommand) Name() string { - return "connection update rules" -} - -// Synopsis : -func (c *ConnectionUpdateRulesCommand) Synopsis() string { - return "Update routing rules of a connection" -} - -// Run : -func (c *ConnectionUpdateRulesCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 3 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago connection update-rules --help'`) - return 1 - } - - connectionID := "" - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - conn, err := api.Connections().Get(connectionID) - if err != nil { - c.UI.Error(fmt.Sprintf("Error getting connection: %s", err)) - return 1 - } - - if c.allowAll { - network, err := api.Networks().Get(conn.NetworkID) - if err != nil { - c.UI.Error(fmt.Sprintf("Error getting network: %s", err)) - return 1 - } - - if c.on == "" { - for i := range conn.PeerSettings { - conn.PeerSettings[i].RoutingRules.AllowedIPs = []string{network.AddressRange} - } - } else { - for i := range conn.PeerSettings { - if conn.PeerSettings[i].InterfaceID == c.on || conn.PeerSettings[i].NodeID == c.on { - conn.PeerSettings[i].RoutingRules.AllowedIPs = []string{network.AddressRange} - - break - } - } - } - } else { - if c.on == "" { - for i := range conn.PeerSettings { - conn.PeerSettings[i].RoutingRules.AllowedIPs = c.allow - } - } else { - for i := range conn.PeerSettings { - if conn.PeerSettings[i].InterfaceID == c.on || conn.PeerSettings[i].NodeID == c.on { - conn.PeerSettings[i].RoutingRules.AllowedIPs = c.allow - - break - } - } - } - } - - conn, err = api.Connections().Update(conn) - if err != nil { - c.UI.Error(fmt.Sprintf("Error updating connection: %s", err)) - return 1 - } - - c.UI.Output(c.formatConnection(conn)) - - return 0 -} - -// Help : -func (c *ConnectionUpdateRulesCommand) Help() string { - h := ` -Usage: drago connection update-rules [options] - - Update is used to update the routing rules enforced on each interface of a connection. - - If ACLs are enabled, this option requires a token with the 'connection:write' capability. - -General Options: -` + GlobalOptions() + ` - -ACL Token Create Options: - - --json - Enable JSON output. - - --allow - Allow routing traffic to this address by the specified end of the connection. - - --allow-all - Enable routing of all traffic by the specified end of the connection. - - --allow-none - Disable routing of all traffic by the specified end of the connection. - - --on= - Node or interface ID specifying to which end of the connection the rules should be applied. - -` - return strings.TrimSpace(h) -} - -func (c *ConnectionUpdateRulesCommand) formatConnection(connection *structs.Connection) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "id": connection.ID, - "networkId": connection.NetworkID, - "persistentKeepalive": connection.PersistentKeepalive, - "peerSettings": c.formatPeerSettings(connection.PeerSettings), - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} - -func (c *ConnectionUpdateRulesCommand) formatPeerSettings(peerSettings []*structs.PeerSettings) string { - - var b bytes.Buffer - - for _, peer := range peerSettings { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "nodeId": peer.NodeID, - "interfaceID": peer.InterfaceID, - "routingRules": c.formatRoutingRules(peer.RoutingRules), - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} - -func (c *ConnectionUpdateRulesCommand) formatRoutingRules(rules *structs.RoutingRules) string { - - var b bytes.Buffer - - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - formatted := map[string]interface{}{ - "allowedIps": rules.AllowedIPs, - } - if err := enc.Encode(formatted); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - s := b.String() - - if c.json { - return s - } - - return cleanJSONString(s) -} diff --git a/command/interface.go b/command/interface.go deleted file mode 100644 index 1a1824d..0000000 --- a/command/interface.go +++ /dev/null @@ -1,40 +0,0 @@ -package command - -import ( - "context" - "strings" - - cli "github.com/seashell/drago/pkg/cli" -) - -// InterfaceCommand : -type InterfaceCommand struct { - UI cli.UI -} - -// Name : -func (c *InterfaceCommand) Name() string { - return "interface" -} - -// Synopsis : -func (c *InterfaceCommand) Synopsis() string { - return "Interact with interfaces" -} - -// Run : -func (c *InterfaceCommand) Run(ctx context.Context, args []string) int { - return cli.CommandReturnCodeHelp -} - -// Help : -func (c *InterfaceCommand) Help() string { - h := ` -Usage: drago interface [options] [args] - - This command groups subcommands for interacting with interfaces. - - Please see the individual subcommand help for detailed usage information. -` - return strings.TrimSpace(h) -} diff --git a/command/interface_list.go b/command/interface_list.go deleted file mode 100644 index 160628c..0000000 --- a/command/interface_list.go +++ /dev/null @@ -1,191 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// InterfaceListCommand : -type InterfaceListCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool - self bool - node string - network string -} - -func (c *InterfaceListCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - flags.BoolVar(&c.self, "self", false, "") - flags.StringVar(&c.node, "node", "", "") - flags.StringVar(&c.network, "network", "", "") - - return flags -} - -// Name : -func (c *InterfaceListCommand) Name() string { - return "interface list" -} - -// Synopsis : -func (c *InterfaceListCommand) Synopsis() string { - return "Display a list of interfaces" -} - -// Run : -func (c *InterfaceListCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago interface list --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - filters := map[string][]string{} - networkID := "" - - if len(c.network) > 0 { - networks, err := api.Networks().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving networks: %s", err)) - return 1 - } - - for _, network := range networks { - if c.network == network.Name { - networkID = network.ID - - break - } - } - } - - if c.self && len(c.node) > 0 { - c.UI.Error("Can not have both the --self and the --node flags.") - return 1 - } - - if len(c.network) > 0 { - filters["network"] = []string{networkID} - } - - if len(c.node) > 0 { - filters["node"] = []string{c.node} - } - - if c.self { - nodeID := "" - - if nodeID, err = localAgentNodeID(api); err != nil { - c.UI.Error(fmt.Sprintf("Error determining local node ID: %s", err)) - return 1 - } - - filters["node"] = []string{nodeID} - } - - ifaces, err := api.Interfaces().List(filters) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving interfaces: %s", err)) - return 1 - } - - if len(ifaces) == 0 { - return 0 - } - - c.UI.Output(c.formatInterfaceList(ifaces)) - - return 0 -} - -// Help : -func (c *InterfaceListCommand) Help() string { - h := ` -Usage: drago interface list [options] - - List interfaces managed by Drago. - - If ACLs are enabled, this option requires a token with the 'interface:read' capability. - -General Options: -` + GlobalOptions() + ` - -Network List Options: - - --json - Enable JSON output. - - --self - Filter results by the local node ID. Can not be used with the --node filter flag. - - --node= - Filter results by node ID. Can not be used with the --self filter flag. - - --network= - Filter results by network. - -` - return strings.TrimSpace(h) -} - -func (c *InterfaceListCommand) formatInterfaceList(interfaces []*structs.InterfaceListStub) string { - - var b bytes.Buffer - fifaces := []interface{}{} - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - for _, iface := range interfaces { - fifaces = append(fifaces, map[string]string{ - "id": iface.ID, - "address": valueOrPlaceholder(iface.Address, "N/A"), - "network": iface.NetworkID, - "node": iface.NodeID, - }) - } - if err := enc.Encode(fifaces); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("INTERFACE ID", "ADDRESS", "NETWORK ID", "NODE ID").WithWriter(&b) - for _, iface := range interfaces { - tbl.AddRow(iface.ID, valueOrPlaceholder(iface.Address, "N/A"), iface.NetworkID, iface.NodeID) - } - tbl.Print() - } - - return b.String() -} diff --git a/command/interface_update.go b/command/interface_update.go deleted file mode 100644 index 525c625..0000000 --- a/command/interface_update.go +++ /dev/null @@ -1,137 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// InterfaceUpdateCommand : -type InterfaceUpdateCommand struct { - UI cli.UI - Command - - // Parsed flags - address string - json bool -} - -func (c *InterfaceUpdateCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.StringVar(&c.address, "address", "", "") - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *InterfaceUpdateCommand) Name() string { - return "interface update" -} - -// Synopsis : -func (c *InterfaceUpdateCommand) Synopsis() string { - return "Update an existing interface" -} - -// Run : -func (c *InterfaceUpdateCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago interface update --help'`) - return 1 - } - - id := args[0] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - iface, err := api.Interfaces().Update(&structs.Interface{ - ID: id, - Address: &c.address, - }) - if err != nil { - c.UI.Error(fmt.Sprintf("Error updating interface: %s", err)) - return 1 - } - - c.UI.Output(c.formatInterface(iface)) - - return 0 -} - -// Help : -func (c *InterfaceUpdateCommand) Help() string { - h := ` -Usage: drago interface update [options] - - Update an existing interface. - - If ACLs are enabled, this option requires a token with the 'interface:write' capability. - -General Options: -` + GlobalOptions() + ` - -Network List Options: - - --json - Enable JSON output. - - --address= - Interface address. - -` - return strings.TrimSpace(h) -} - -func (c *InterfaceUpdateCommand) formatInterface(iface *structs.Interface) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - fiface := map[string]string{ - "id": iface.ID, - "address": valueOrPlaceholder(iface.Address, "N/A"), - "network": iface.NetworkID, - "node": iface.NodeID, - } - - if err := enc.Encode(fiface); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - } else { - tbl := table.New("INTERFACE ID", "ADDRESS", "NETWORK ID", "NODE ID").WithWriter(&b) - tbl.AddRow(iface.ID, valueOrPlaceholder(iface.Address, "N/A"), iface.NetworkID, iface.NodeID) - tbl.Print() - } - - return b.String() -} diff --git a/command/network.go b/command/network.go deleted file mode 100644 index 041020e..0000000 --- a/command/network.go +++ /dev/null @@ -1,40 +0,0 @@ -package command - -import ( - "context" - "strings" - - cli "github.com/seashell/drago/pkg/cli" -) - -// NetworkCommand : -type NetworkCommand struct { - UI cli.UI -} - -// Name : -func (c *NetworkCommand) Name() string { - return "network" -} - -// Synopsis : -func (c *NetworkCommand) Synopsis() string { - return "Interact with networks" -} - -// Run : -func (c *NetworkCommand) Run(ctx context.Context, args []string) int { - return cli.CommandReturnCodeHelp -} - -// Help : -func (c *NetworkCommand) Help() string { - h := ` -Usage: drago network [options] [args] - - This command groups subcommands for interacting with networks. - - Please see the individual subcommand help for detailed usage information. -` - return strings.TrimSpace(h) -} diff --git a/command/network_create.go b/command/network_create.go deleted file mode 100644 index ae86210..0000000 --- a/command/network_create.go +++ /dev/null @@ -1,101 +0,0 @@ -package command - -import ( - "context" - "fmt" - "strings" - - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NetworkCreateCommand : -type NetworkCreateCommand struct { - UI cli.UI - Command - - // Parsed flags - addressRange string -} - -func (c *NetworkCreateCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.StringVar(&c.addressRange, "range", "", "") - - return flags -} - -// Name : -func (c *NetworkCreateCommand) Name() string { - return "network create" -} - -// Synopsis : -func (c *NetworkCreateCommand) Synopsis() string { - return "Create a new network" -} - -// Run : -func (c *NetworkCreateCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago network create --help'`) - return 1 - } - - name := args[0] - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - err = api.Networks().Create(&structs.Network{ - Name: name, - AddressRange: c.addressRange, - }) - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating network: %s", err)) - return 1 - } - - c.UI.Output("Network created!") - - return 0 -} - -// Help : -func (c *NetworkCreateCommand) Help() string { - h := ` -Usage: drago network create [options] - - Create a new network. - - If ACLs are enabled, this option requires a token with the 'network:write' capability. - -General Options: -` + GlobalOptions() + ` - -Network Create Options: - - --range= - Sets the address range of the network, in CIDR notation. - -` - return strings.TrimSpace(h) -} diff --git a/command/network_delete.go b/command/network_delete.go deleted file mode 100644 index 779537b..0000000 --- a/command/network_delete.go +++ /dev/null @@ -1,111 +0,0 @@ -package command - -import ( - "context" - "fmt" - "strings" - - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NetworkDeleteCommand : -type NetworkDeleteCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *NetworkDeleteCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *NetworkDeleteCommand) Name() string { - return "network delete" -} - -// Synopsis : -func (c *NetworkDeleteCommand) Synopsis() string { - return "Delete an existing network" -} - -// Run : -func (c *NetworkDeleteCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago network delete --help'`) - return 1 - } - - name := args[0] - id := "" - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - networks, err := api.Networks().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error getting networks: %s", err)) - return 1 - } - - for _, n := range networks { - if n.Name == name { - id = n.ID - - break - } - } - - if id == "" { - c.UI.Error("Error: network not found") - return 1 - } - - err = api.Networks().Delete(id) - if err != nil { - c.UI.Error(fmt.Sprintf("Error deleting network: %s", err)) - return 1 - } - - c.UI.Output("Network deleted!") - - return 0 -} - -// Help : -func (c *NetworkDeleteCommand) Help() string { - h := ` -Usage: drago network delete [options] - - Delete an existing Drago network. - - If ACLs are enabled, this option requires a token with the 'network:write' capability. - -General Options: -` + GlobalOptions() - - return strings.TrimSpace(h) -} diff --git a/command/network_info.go b/command/network_info.go deleted file mode 100644 index 57cb500..0000000 --- a/command/network_info.go +++ /dev/null @@ -1,148 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NetworkInfoCommand : -type NetworkInfoCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *NetworkInfoCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *NetworkInfoCommand) Name() string { - return "network info" -} - -// Synopsis : -func (c *NetworkInfoCommand) Synopsis() string { - return "Display info about a network" -} - -// Run : -func (c *NetworkInfoCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago network info --help'`) - return 1 - } - - name := args[0] - id := "" - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - networks, err := api.Networks().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error getting networks: %s", err)) - return 1 - } - - for _, n := range networks { - if n.Name == name { - id = n.ID - - break - } - } - - if id == "" { - c.UI.Error("Error: network not found") - return 1 - } - - network, err := api.Networks().Get(id) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving network: %s", err)) - return 1 - } - - c.UI.Output(c.formatNetwork(network)) - - return 0 -} - -// Help : -func (c *NetworkInfoCommand) Help() string { - h := ` - Usage: drago network info [options] - - Display detailed information about an existing Drago network. - - If ACLs are enabled, this option requires a token with the 'network:read' capability. - -General Options: -` + GlobalOptions() + ` - -Network Info Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *NetworkInfoCommand) formatNetwork(network *structs.Network) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - fnetwork := map[string]string{ - "id": network.ID, - "name": network.Name, - "addressRange": network.AddressRange, - } - - if err := enc.Encode(fnetwork); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - } else { - tbl := table.New("NETWORK ID", "NAME", "ADDRESS RANGE").WithWriter(&b) - tbl.AddRow(network.ID, network.Name, network.AddressRange) - tbl.Print() - } - - return b.String() -} diff --git a/command/network_list.go b/command/network_list.go deleted file mode 100644 index a7422e6..0000000 --- a/command/network_list.go +++ /dev/null @@ -1,159 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NetworkListCommand : -type NetworkListCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool -} - -func (c *NetworkListCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *NetworkListCommand) Name() string { - return "network list" -} - -// Synopsis : -func (c *NetworkListCommand) Synopsis() string { - return "Display a list of networks" -} - -// Run : -func (c *NetworkListCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago network list --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - networks, err := api.Networks().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving networks: %s", err)) - return 1 - } - - if len(networks) == 0 { - return 0 - } - - c.UI.Output(c.formatNetworkList(networks)) - - return 0 -} - -// Help : -func (c *NetworkListCommand) Help() string { - h := ` -Usage: drago network list [options] - - Lists networks managed by Drago. - - If ACLs are enabled, this option requires a token with the 'network:read' capability. - -General Options: -` + GlobalOptions() + ` - -Network List Options: - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *NetworkListCommand) formatNetworkList(networks []*structs.NetworkListStub) string { - - var b bytes.Buffer - fnetworks := []interface{}{} - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - for _, network := range networks { - fnetworks = append(fnetworks, map[string]string{ - "id": network.ID, - "name": network.Name, - "addressRange": network.AddressRange, - }) - } - if err := enc.Encode(fnetworks); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("NETWORK ID", "NAME", "ADDRESS RANGE").WithWriter(&b) - for _, network := range networks { - tbl.AddRow(network.ID, network.Name, network.AddressRange) - } - tbl.Print() - } - - return b.String() -} - -func (c *NetworkListCommand) formatNetwork(network *structs.Network) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - fnetwork := map[string]string{ - "id": network.ID, - "name": network.Name, - "addressRange": network.AddressRange, - } - - if err := enc.Encode(fnetwork); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - } else { - tbl := table.New("NETWORK ID", "NAME", "ADDRESS RANGE").WithWriter(&b) - tbl.AddRow(network.ID, network.Name, network.AddressRange) - tbl.Print() - } - - return b.String() -} diff --git a/command/node.go b/command/node.go deleted file mode 100644 index 36255cb..0000000 --- a/command/node.go +++ /dev/null @@ -1,40 +0,0 @@ -package command - -import ( - "context" - "strings" - - cli "github.com/seashell/drago/pkg/cli" -) - -// NodeCommand : -type NodeCommand struct { - UI cli.UI -} - -// Name : -func (c *NodeCommand) Name() string { - return "node" -} - -// Synopsis : -func (c *NodeCommand) Synopsis() string { - return "Interact with nodes" -} - -// Run : -func (c *NodeCommand) Run(ctx context.Context, args []string) int { - return cli.CommandReturnCodeHelp -} - -// Help : -func (c *NodeCommand) Help() string { - h := ` -Usage: drago node [options] [args] - - This command groups subcommands for interacting with nodes. - - Please see the individual subcommand help for detailed usage information. -` - return strings.TrimSpace(h) -} diff --git a/command/node_info.go b/command/node_info.go deleted file mode 100644 index 4a8b472..0000000 --- a/command/node_info.go +++ /dev/null @@ -1,143 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NodeInfoCommand : -type NodeInfoCommand struct { - UI cli.UI - Command - - // Parsed flags - self bool - json bool -} - -func (c *NodeInfoCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.self, "self", false, "") - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *NodeInfoCommand) Name() string { - return "node info" -} - -// Synopsis : -func (c *NodeInfoCommand) Synopsis() string { - return "Display info about a node" -} - -// Run : -func (c *NodeInfoCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 && !c.self { - c.UI.Error("This command takes either one argument or a --self flag") - c.UI.Error(`For additional help, try 'drago node info --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - // Print status of a single node - var nodeID string - if !c.self { - nodeID = args[0] - } else { - if nodeID, err = localAgentNodeID(api); err != nil { - c.UI.Error(fmt.Sprintf("Error determining local node ID: %s", err)) - return 1 - } - } - - node, err := api.Nodes().Get(nodeID) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving node status: %s", err)) - return 1 - } - - c.UI.Output(c.formatNode(node)) - - return 0 -} - -// Help : -func (c *NodeInfoCommand) Help() string { - h := ` -Usage: drago node info [options] - - Display detailed information about an existing node. - - If ACLs are enabled, this option requires a token with the 'node:read' capability. - -General Options: -` + GlobalOptions() + ` - -Node List Options: - - --self - Query the status of the local node. - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *NodeInfoCommand) formatNode(node *structs.Node) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - fnode := map[string]string{ - "id": node.ID, - "name": node.Name, - "advertiseAddress": node.AdvertiseAddress, - "status": node.Status, - } - - if err := enc.Encode(fnode); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - } else { - tbl := table.New("NODE ID", "NAME", "ADVERTISE ADDRESS", "STATUS").WithWriter(&b) - tbl.AddRow(node.ID, node.Name, node.AdvertiseAddress, node.Status) - tbl.Print() - } - - return b.String() -} diff --git a/command/node_join.go b/command/node_join.go deleted file mode 100644 index 2d27f33..0000000 --- a/command/node_join.go +++ /dev/null @@ -1,153 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NodeJoinCommand : -type NodeJoinCommand struct { - UI cli.UI - Command - - json bool -} - -func (c *NodeJoinCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *NodeJoinCommand) Name() string { - return "node join" -} - -// Synopsis : -func (c *NodeJoinCommand) Synopsis() string { - return "Join a network" -} - -// Run : -func (c *NodeJoinCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument") - c.UI.Error(`For additional help, try 'drago node join --help'`) - return 1 - } - - networkName := args[0] - nodeID := "" - networkID := "" - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - if nodeID, err = localAgentNodeID(api); err != nil { - c.UI.Error(fmt.Sprintf("Error determining local node ID: %s", err)) - return 1 - } - - networks, err := api.Networks().List() - if err != nil { - c.UI.Error(fmt.Sprintf("Error getting networks: %s", err)) - return 1 - } - - for _, n := range networks { - if n.Name == networkName { - networkID = n.ID - - break - } - } - - if networkID == "" { - c.UI.Error("Error: network not found") - return 1 - } - - iface, err := api.Interfaces().Create(nodeID, networkID) - if err != nil { - c.UI.Error(fmt.Sprintf("Error joining network: %s", err)) - return 1 - } - - c.UI.Output("Joined!") - c.UI.Output(c.formatInterface(iface)) - - return 0 -} - -// Help : -func (c *NodeJoinCommand) Help() string { - h := ` -Usage: drago node join [options] - - Have the local client node join an existing network. - - If ACLs are enabled, this option requires a token with the 'interface:write' capability. - -General Options: -` + GlobalOptions() + ` - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *NodeJoinCommand) formatInterface(iface *structs.Interface) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - fiface := map[string]string{ - "id": iface.ID, - "address": valueOrPlaceholder(iface.Address, "N/A"), - "network": iface.NetworkID, - "node": iface.NodeID, - } - - if err := enc.Encode(fiface); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - } else { - tbl := table.New("INTERFACE ID", "ADDRESS", "NETWORK ID", "NODE ID").WithWriter(&b) - tbl.AddRow(iface.ID, valueOrPlaceholder(iface.Address, "N/A"), iface.NetworkID, iface.NodeID) - tbl.Print() - } - - return b.String() -} diff --git a/command/node_leave.go b/command/node_leave.go deleted file mode 100644 index f5a80cc..0000000 --- a/command/node_leave.go +++ /dev/null @@ -1,98 +0,0 @@ -package command - -import ( - "context" - "fmt" - "strings" - - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NodeLeaveCommand : -type NodeLeaveCommand struct { - UI cli.UI - Command - - // Parsed flags - node string -} - -func (c *NodeLeaveCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.StringVar(&c.node, "node", "", "") - - return flags -} - -// Name : -func (c *NodeLeaveCommand) Name() string { - return "node leave" -} - -// Synopsis : -func (c *NodeLeaveCommand) Synopsis() string { - return "Leave a network" -} - -// Run : -func (c *NodeLeaveCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 1 { - c.UI.Error("This command takes one argument: ") - c.UI.Error(`For additional help, try 'drago node leave --help'`) - return 1 - } - - networkID := args[0] - nodeID := c.node - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - if nodeID == "" { - if nodeID, err = localAgentNodeID(api); err != nil { - c.UI.Error(fmt.Sprintf("Error determining local node ID: %s", err)) - return 1 - } - } - - if _, err = api.Interfaces().Create(nodeID, networkID); err != nil { - c.UI.Error(fmt.Sprintf("Error joining network: %s", err)) - return 1 - } - - c.UI.Output("Left!") - - return 0 -} - -// Help : -func (c *NodeLeaveCommand) Help() string { - h := ` -Usage: drago node join [options] - - Have the local client node leave a network. - - If ACLs are enabled, this option requires a token with the 'interface:write' capability. - -General Options: -` + GlobalOptions() - - return strings.TrimSpace(h) -} diff --git a/command/node_list.go b/command/node_list.go deleted file mode 100644 index bcd3583..0000000 --- a/command/node_list.go +++ /dev/null @@ -1,174 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NodeListCommand : -type NodeListCommand struct { - UI cli.UI - Command - - // Parsed flags - json bool - status string - meta []string -} - -func (c *NodeListCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.json, "json", false, "") - flags.StringVar(&c.status, "status", "*", "") - flags.StringSliceVar(&c.meta, "meta", []string{}, "") - - return flags -} - -// Name : -func (c *NodeListCommand) Name() string { - return "node status" -} - -// Synopsis : -func (c *NodeListCommand) Synopsis() string { - return "List existing nodes" -} - -// Run : -func (c *NodeListCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) != 0 { - c.UI.Error("This command takes no arguments") - c.UI.Error(`For additional help, try 'drago node list --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - filters := map[string][]string{} - filters["meta"] = c.meta - filters["status"] = []string{c.status} - - nodes, err := api.Nodes().List(filters) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving node status: %s", err)) - return 1 - } - - if len(nodes) == 0 { - return 0 - } - - c.UI.Output(c.formatNodeList(nodes)) - - return 0 - -} - -// Help : -func (c *NodeListCommand) Help() string { - h := ` -Usage: drago node list [options] - - List nodes registered on Drago. - - If ACLs are enabled, this option requires a token with the 'node:read' capability. - -General Options: -` + GlobalOptions() + ` - -Node List Options: - - --json - Enable JSON output. - - --meta= - Filter nodes by metadata. - - --status= - Filter nodes by status. - -` - return strings.TrimSpace(h) -} - -func (c *NodeListCommand) formatNodeList(nodes []*structs.NodeListStub) string { - - var b bytes.Buffer - fnodes := []interface{}{} - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - for _, node := range nodes { - fnodes = append(fnodes, map[string]string{ - "id": node.ID, - "name": node.Name, - "status": node.Status, - }) - } - if err := enc.Encode(fnodes); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("NODE ID", "NAME", "STATUS").WithWriter(&b) - for _, node := range nodes { - tbl.AddRow(node.ID, node.Name, node.Status) - } - tbl.Print() - } - - return b.String() -} - -func (c *NodeListCommand) formatNode(node *structs.Node) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - fnode := map[string]string{ - "id": node.ID, - "name": node.Name, - "status": node.Status, - } - - if err := enc.Encode(fnode); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - } else { - tbl := table.New("NODE ID", "NAME", "STATUS").WithWriter(&b) - tbl.AddRow(node.ID, node.Name, node.Status) - tbl.Print() - } - - return b.String() -} diff --git a/command/node_status.go b/command/node_status.go deleted file mode 100644 index 26a9b07..0000000 --- a/command/node_status.go +++ /dev/null @@ -1,209 +0,0 @@ -package command - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - - table "github.com/rodaine/table" - structs "github.com/seashell/drago/drago/structs" - cli "github.com/seashell/drago/pkg/cli" - "github.com/spf13/pflag" -) - -// NodeStatusCommand : -type NodeStatusCommand struct { - UI cli.UI - Command - - // Parsed flags - self bool - status string - meta []string - json bool -} - -func (c *NodeStatusCommand) FlagSet() *pflag.FlagSet { - - flags := c.Command.FlagSet(c.Name()) - flags.Usage = func() { c.UI.Output("\n" + c.Help() + "\n") } - - // General options - flags.BoolVar(&c.self, "self", false, "") - flags.StringVar(&c.status, "status", "*", "") - flags.StringSliceVar(&c.meta, "meta", []string{}, "") - flags.BoolVar(&c.json, "json", false, "") - - return flags -} - -// Name : -func (c *NodeStatusCommand) Name() string { - return "node status" -} - -// Synopsis : -func (c *NodeStatusCommand) Synopsis() string { - return "Display status of existing nodes" -} - -// Run : -func (c *NodeStatusCommand) Run(ctx context.Context, args []string) int { - - flags := c.FlagSet() - - if err := flags.Parse(args); err != nil { - return 1 - } - - args = flags.Args() - if len(args) > 1 { - c.UI.Error("This command takes either one or no arguments") - c.UI.Error(`For additional help, try 'drago node status --help'`) - return 1 - } - - // Get the HTTP client - api, err := c.Command.APIClient() - if err != nil { - c.UI.Error(fmt.Sprintf("Error setting up API client: %s", err)) - return 1 - } - - if len(args) == 0 && !c.self { - - filters := map[string][]string{} - filters["meta"] = c.meta - filters["status"] = []string{c.status} - - // Print status of multiple nodes - nodes, err := api.Nodes().List(filters) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving node status: %s", err)) - return 1 - } - - if len(nodes) == 0 { - return 0 - } - - c.UI.Output(c.formatNodeList(nodes)) - - return 0 - } - - // Print status of a single node - var nodeID string - if !c.self { - nodeID = args[0] - } else { - if nodeID, err = localAgentNodeID(api); err != nil { - c.UI.Error(fmt.Sprintf("Error determining local node ID: %s", err)) - return 1 - } - } - - node, err := api.Nodes().Get(nodeID) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving node status: %s", err)) - return 1 - } - - c.UI.Output(c.formatNode(node)) - - return 0 -} - -// Help : -func (c *NodeStatusCommand) Help() string { - h := ` -Usage: drago node status [options] - - Display node status information. - - If a node ID is passed, information for that specific node will be displayed. - If no node ID's are passed, then a short-hand list of all nodes will be displayed. - The --self flag is useful to quickly access the status of the local node. - - If ACLs are enabled, this option requires a token with the 'node:read' capability. - -General Options: -` + GlobalOptions() + ` - -Node Status Options: - - --self - Query the status of the local node. - - --meta= - Filter nodes by metadata. - - --status= - Filter nodes by status. - - --json - Enable JSON output. - -` - return strings.TrimSpace(h) -} - -func (c *NodeStatusCommand) formatNodeList(nodes []*structs.NodeListStub) string { - - var b bytes.Buffer - fnodes := []interface{}{} - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - for _, node := range nodes { - fnodes = append(fnodes, map[string]string{ - "id": node.ID, - "name": node.Name, - "advertiseAddress": node.AdvertiseAddress, - "status": node.Status, - }) - } - if err := enc.Encode(fnodes); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - } else { - tbl := table.New("NODE ID", "NAME", "ADVERTISE ADDRESS", "STATUS").WithWriter(&b) - for _, node := range nodes { - tbl.AddRow(node.ID, node.Name, node.AdvertiseAddress, node.Status) - } - tbl.Print() - } - - return b.String() -} - -func (c *NodeStatusCommand) formatNode(node *structs.Node) string { - - var b bytes.Buffer - - if c.json { - enc := json.NewEncoder(&b) - enc.SetIndent("", " ") - - fnode := map[string]string{ - "id": node.ID, - "name": node.Name, - "advertiseAddress": node.AdvertiseAddress, - "status": node.Status, - } - - if err := enc.Encode(fnode); err != nil { - c.UI.Error(fmt.Sprintf("Error formatting JSON output: %s", err)) - } - - } else { - tbl := table.New("NODE ID", "NAME", "ADVERTISE ADDRESS", "STATUS").WithWriter(&b) - tbl.AddRow(node.ID, node.Name, node.AdvertiseAddress, node.Status) - tbl.Print() - } - - return b.String() -} diff --git a/command/ui.go b/command/ui.go deleted file mode 100644 index 757d177..0000000 --- a/command/ui.go +++ /dev/null @@ -1,81 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os/exec" - "runtime" - "strings" - - cli "github.com/seashell/drago/pkg/cli" -) - -// UICommand : -type UICommand struct { - UI cli.UI - - Command -} - -// Name : -func (c *UICommand) Name() string { - return "acl" -} - -// Synopsis : -func (c *UICommand) Synopsis() string { - return "Open the Drago web UI" -} - -// Run : -func (c *UICommand) Run(ctx context.Context, args []string) int { - - url := "http://127.0.0.1:8080" - if c.address != "" { - url = c.address - } - - c.UI.Output(fmt.Sprintf(`Opening URL "%s"`, url)) - - if err := openBrowser(url); err != nil { - c.UI.Error(err.Error()) - } - - return 0 -} - -// Help : -func (c *UICommand) Help() string { - h := ` -Usage: drago ui [options] [args] - - Open the Drago web UI in the default browser. - -General Options: -` + GlobalOptions() + ` -` - return strings.TrimSpace(h) -} - -// Credits: https://gist.github.com/hyg/9c4afcd91fe24316cbf0 -func openBrowser(url string) error { - - var err error - - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", url).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() - case "darwin": - err = exec.Command("open", url).Start() - default: - err = fmt.Errorf("unsupported platform") - } - if err != nil { - return err - } - - return nil - -} diff --git a/command/util.go b/command/util.go deleted file mode 100644 index 2fb1e0a..0000000 --- a/command/util.go +++ /dev/null @@ -1,63 +0,0 @@ -package command - -import ( - "fmt" - "strings" - - api "github.com/seashell/drago/api" -) - -// Returns the node ID of the local agent, in case it is a client. -// Otherwise, returns an error. -func localAgentNodeID(api *api.Client) (string, error) { - - self, err := api.Agent().Self() - if err != nil { - return "", fmt.Errorf("could not retrieve agent info: %s", err) - } - - clientStats, ok := self.Stats["client"] - if !ok { - return "", fmt.Errorf("not running in client mode") - } - - nodeID, ok := clientStats["node_id"] - if !ok { - return "", fmt.Errorf("could not determine node id") - } - - return nodeID, nil -} - -func valueOrPlaceholder(s *string, p string) string { - if s != nil { - return *s - } - return p -} - -// TODO: improve how we clean JSON strings -func cleanJSONString(s string) string { - - s = strings.TrimSpace(s) - s = strings.ReplaceAll(s, `""`, "N/A") - s = strings.ReplaceAll(s, `{}`, "\n N/A") - - s = strings.ReplaceAll(s, " ", " ") - s = strings.ReplaceAll(s, `,`, "") - - s = strings.ReplaceAll(s, "{", "") - s = strings.ReplaceAll(s, "}", "") - s = strings.ReplaceAll(s, `"`, "") - - s = strings.TrimLeftFunc(s, func(r rune) bool { - return r == '\n' - }) - - s = strings.TrimRightFunc(s, func(r rune) bool { - return r == '\n' || r == ' ' - }) - - return s - -} diff --git a/command/version.go b/command/version.go deleted file mode 100644 index 28b3662..0000000 --- a/command/version.go +++ /dev/null @@ -1,43 +0,0 @@ -package command - -import ( - "context" - "strings" - - cli "github.com/seashell/drago/pkg/cli" - version "github.com/seashell/drago/version" -) - -// VersionCommand : -type VersionCommand struct { - UI cli.UI -} - -// Name : -func (c *VersionCommand) Name() string { - return "version" -} - -// Synopsis : -func (c *VersionCommand) Synopsis() string { - return "Print the Drago version" -} - -// Run : -func (c *VersionCommand) Run(ctx context.Context, args []string) int { - c.UI.Output(version.GetVersion().VersionNumber()) - return 0 -} - -// Help : -func (c *VersionCommand) Help() string { - h := ` -Usage: drago version [options] - - Version prints out the Drago version. - -General Options: -` + GlobalOptions() + ` -` - return strings.TrimSpace(h) -} diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000..f34dcd1 Binary files /dev/null and b/demo.gif differ diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 9696202..0000000 --- a/docs/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Drago Documentation Website - -This subdirectory contains the source for the Drago website. - -### Overview -We use [Docsify](https://docsify.js.org/) for generating the website on the fly based on markdown files. - -### Contributing -In order to contribute to the Drago docs, simply fork the repo, edit the markdown in this directory, and submit a pull request. Also, feel to add any new assets such as diagrams and screenshots. \ No newline at end of file diff --git a/docs/_404.md b/docs/_404.md deleted file mode 100644 index 1516492..0000000 --- a/docs/_404.md +++ /dev/null @@ -1,7 +0,0 @@ - -
-
-

404 - Page not found

-

For more information, please contact us.

-
-
diff --git a/docs/_coverpage.md b/docs/_coverpage.md deleted file mode 100644 index 8107329..0000000 --- a/docs/_coverpage.md +++ /dev/null @@ -1,28 +0,0 @@ -
- drago-logo -
- -

drago

- -

- Go report: A+ - GitHub - Gitter - - GitHub stars - -

- -> A flexible configuration manager for WireGuard networks. - -- Single-binary, lightweight -- Encrypted node-to-node communication -- Support for different WireGuard implementations -- Slick management dashboard -- Extensible via REST API - -[GitHub](https://github.com/seashell/drago/) -[Get Started](/docs/) - - diff --git a/docs/_navbar.md b/docs/_navbar.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/_sidebar.md b/docs/_sidebar.md deleted file mode 100644 index 05fad93..0000000 --- a/docs/_sidebar.md +++ /dev/null @@ -1,67 +0,0 @@ -* [Welcome](/docs/ "Welcome") -* [Overview](/docs/overview "Overview") - -* Installing Drago - * [Overview](/docs/installing/) - * [Quickstart](/docs/installing/quickstart) - -* Internals - * [Overview](/docs/internals/) - * [Architecture](/docs/internals/architecture) - - -* Configuration - * [Overview](/docs/configuration/) - * [acl](/docs/configuration/acl) - * [client](/docs/configuration/client) - * [server](/docs/configuration/server) - -* Commands (CLI) - * [Overview](/docs/commands/) - * acl - * [Overview](/docs/commands/acl/) - * [bootstrap](/docs/commands/acl/bootstrap) - * [policy apply](/docs/commands/acl/policy-apply) - * [policy delete](/docs/commands/acl/policy-delete) - * [policy list](/docs/commands/acl/policy-list) - * [policy info](/docs/commands/acl/policy-info) - * [token create](/docs/commands/acl/token-create) - * [token delete](/docs/commands/acl/token-delete) - * [token list](/docs/commands/acl/token-list) - * [token info](/docs/commands/acl/token-info) - * [token self](/docs/commands/acl/token-self) - * [token update](/docs/commands/acl/token-update) - * [agent](/docs/commands/agent) - * [agent info](/docs/commands/agent-info) - * interface - * [list](/docs/commands/interface/list) - * [update](/docs/commands/interface/update) - * connection - * [list](/docs/commands/connection/list) - * [update](/docs/commands/connection/update) - * network - * [create](/docs/commands/network/create) - * [list](/docs/commands/network/list) - * [delete](/docs/commands/network/delete) - * node - * [join](/docs/commands/node/join) - * [leave](/docs/commands/node/leave) - * [list](/docs/commands/node/list) - * [status](/docs/commands/node/status) - - * [ui](/docs/commands/ui) - -* HTTP API - * [Overview](/api/) - * [ACL Policies](/api/acl-policies) - * [ACL Tokens](/api/acl-tokens) - * [Connections](/api/connections) - * [Interfaces](/api/interfaces) - * [Networks](/api/networks) - * [Nodes](/api/nodes) - * [Status](/api/status) - * [UI](/api/ui) - -* [Contributing](/contributing) -* [License](/license) -* [FAQ](/faq) \ No newline at end of file diff --git a/docs/api/README.md b/docs/api/README.md deleted file mode 100644 index e0ca9bc..0000000 --- a/docs/api/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# HTTP API - -## Prefix - -All API routes are prefixed with `/api/`. - -## Addressing and Ports -Drago binds to a specific set of addresses and ports. The HTTP API is served via the HTTP address and port. The default port for the Drago HTTP API is 8081. This can be overridden via the Drago configuration block. Here is an example curl request to query the status of a Drago server: - -```bash -$ curl http://127.0.0.1:8081/api/status -``` - -## Data Model and Layout -There are four primary nouns in Drago: - -- nodes -- networks -- interfaces -- connections - -## ACLs -Several endpoints in Drago use or require ACL tokens to operate. The tokens are used to authenticate the request and determine if the request is allowed based on the associated authorizations. Tokens are specified per-request by using the X-Drago-Token request header set to the Secret of an ACL Token. - -## Authentication -When ACLs are enabled, a Drago token should be provided to API requests using the X-Drago-Token header. - -Here is an example using curl: - -```bash -$ curl \ - --header "X-Drago-Token: aa534e09-6a07-0a45-2295-a7f77063d429" \ - https://localhost:8080/api/nodes -``` - -## Formatted JSON Output -By default, the output of all HTTP API requests is JSON. - -## HTTP Methods -Drago's API aims to be RESTful, although there might be some exceptions. The API responds to the standard HTTP verbs GET, POST, PUT, and DELETE. Each API method will clearly document the verb(s) it responds to and the generated response. The same path with different verbs may trigger different behavior. For example: - -``` -POST /v1/networks/ -GET /v1/networks/ -``` - -Even though these share a path, the POST operation creates a new network whereas the GET operation reads all networks. - -## HTTP Response Codes -Individual API's will contain further documentation in the case that more specific response codes are returned but all clients should handle the following: - -- 200 and 204 as success codes. -- 400 indicates a validation failure and if a parameter is modified in the request, it could potentially succeed. -- 403 marks that the client isn't authenticated for the request. -- 404 indicates that the resource targeted does not exist. -- 5xx means that the client should not expect the request to succeed if retried. Whenever a status 5xx is returned, more details about the error will be contained within the response body. \ No newline at end of file diff --git a/docs/api/acl-policies.md b/docs/api/acl-policies.md deleted file mode 100644 index 7b18624..0000000 --- a/docs/api/acl-policies.md +++ /dev/null @@ -1 +0,0 @@ -# ACL Policies HTTP API diff --git a/docs/api/acl-tokens.md b/docs/api/acl-tokens.md deleted file mode 100644 index 72ee7e6..0000000 --- a/docs/api/acl-tokens.md +++ /dev/null @@ -1 +0,0 @@ -# ACL Tokens HTTP API diff --git a/docs/api/connections.md b/docs/api/connections.md deleted file mode 100644 index 09b1014..0000000 --- a/docs/api/connections.md +++ /dev/null @@ -1 +0,0 @@ -# Connections HTTP API diff --git a/docs/api/interfaces.md b/docs/api/interfaces.md deleted file mode 100644 index 52be5e8..0000000 --- a/docs/api/interfaces.md +++ /dev/null @@ -1 +0,0 @@ -# Interfaces HTTP API diff --git a/docs/api/networks.md b/docs/api/networks.md deleted file mode 100644 index bbb1d07..0000000 --- a/docs/api/networks.md +++ /dev/null @@ -1,23 +0,0 @@ -# Networks HTTP API - -The `/networks/` endpoints are used to manage Networks. - -## List Networks - -This endpoint lists all Networks. - -| **Method** | **Path** | **Produces** | -|------------|-----------------|--------------------| -| `GET` | `/networks/` | `application/json` | - - -| **ACL Required** | -|-------------------------------------| -| `management` for all policies. | - - -### Parameters - -### Sample Request - -### Sample Response \ No newline at end of file diff --git a/docs/api/nodes.md b/docs/api/nodes.md deleted file mode 100644 index 49088e2..0000000 --- a/docs/api/nodes.md +++ /dev/null @@ -1 +0,0 @@ -# Nodes HTTP API diff --git a/docs/api/status.md b/docs/api/status.md deleted file mode 100644 index 7b285d7..0000000 --- a/docs/api/status.md +++ /dev/null @@ -1 +0,0 @@ -# Status HTTP API \ No newline at end of file diff --git a/docs/api/ui.md b/docs/api/ui.md deleted file mode 100644 index cfff0bf..0000000 --- a/docs/api/ui.md +++ /dev/null @@ -1 +0,0 @@ -# UI HTTP API \ No newline at end of file diff --git a/docs/assets/logos/dragopher.svg b/docs/assets/logos/dragopher.svg deleted file mode 100644 index 268fa54..0000000 --- a/docs/assets/logos/dragopher.svg +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/contributing.md b/docs/contributing.md deleted file mode 100644 index 91609cb..0000000 --- a/docs/contributing.md +++ /dev/null @@ -1,35 +0,0 @@ -# Contributing - -If you are looking to help with a code contribution our project makes use of Golang and JS/React. If you don't feel ready to make a code contribution yet, no problem! You can also check out our documentation and design issues. - -If you are interested in making a code contribution and would like to learn more about the technologies that we use, check out the list below. - -- [WireGuard](https://www.wireguard.com) -- [bbolt](https://github.com/etcd-io/bbolt) -- [etcd](https://etcd.io/) -- [styled-components](https://styled-components.com/) -- [Apollo Client](https://www.apollographql.com/docs/react/) - -## How do I make a contribution? - -Never made an open source contribution before? Wondering how contributions work in the in our project? Here's a quick rundown! - -1. Find an issue that you are interested in addressing or a feature that you would like to add. -2. Fork the repository associated with the issue to your local GitHub organization. This means that you will have a copy of the repository under `your-username/drago`. -3. Clone the repository to your local machine using `git clone https://github.com/your-username/drago.git`. -4. Create a new branch for your fix using `git checkout -b branch-name-here`. -5. Use `git add insert-paths-of-changed-files-here` to add the file contents of the changed files to the "snapshot" git uses to manage the state of the project, also known as the index. -6. Use `git commit -m "Insert a short message of the changes made here"` to store the contents of the index with a descriptive message. -7. Push the changes to the remote repository using `git push origin branch-name-here`. -8. Submit a pull request to the upstream repository. -9. Title the pull request with a short description of the changes made and the issue or bug number associated with your change. For example, you can title an issue like so `"Added more log outputting to resolve #4352"`. -10. In the description of the pull request, explain the changes that you made, any issues you think exist with the pull request you made, and any questions you have for the maintainer. It's OK if your pull request is not perfect (no pull request is), the reviewer will be able to help you fix any problems and improve it! -11. Wait for the pull request to be reviewed by a maintainer. -12. Make changes to the pull request if the reviewing maintainer recommends them. -13. Celebrate your success after your pull request is merged! - -## Where can I go for help? -If you need help, you can ask questions on [our GitHub issues page](https://github.com/seashell/drago/issues), or on [Gitter](https://gitter.im/seashell/drago). - -## What does the Code of Conduct mean for me? -Our [Code of Conduct](https://github.com/seashell/drago/blob/master/CODE_OF_CONDUCT.md) means that you are responsible for treating everyone on the project with respect and courtesy regardless of their identity. If you are the victim of any inappropriate behavior or comments as described in our Code of Conduct, we are here for you and will do the best to ensure that the abuser is reprimanded appropriately, per our code. \ No newline at end of file diff --git a/docs/css/custom.css b/docs/css/custom.css deleted file mode 100644 index 6456ddf..0000000 --- a/docs/css/custom.css +++ /dev/null @@ -1,143 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); - -:root { - background: var(--background)!important; -} - -li:not(:last-child){ - margin-bottom: 10px; -} - -/* ----------------- Cover -----------------*/ -section.cover .cover-main>p{ - margin-top: 32px; -} -section.cover .cover-main>p>a{ - margin: 4px!important; -} -section.cover .cover-main>p:last-child a:last-child { - background-color: var(--accent); - color: var(--background); -} - -section.cover .cover-main>p:last-child a:last-child:hover{ - background-color: var(--accent); - color: var(--background); -} - -/* ----------------- Home brand -----------------*/ - -.home-brand { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.home-brand div { - display: flex; - align-items: flex-end; -} - -.home-brand img { - height: 120px; - margin-right: 12px; -} - -.home-brand span { - font-size: 84px; - font-weight: 200; - line-height: 64px; -} -/* ----------------- Markdown -----------------*/ -.markdown-section { - max-width: 900px; -} - -/* ----------------- Sidebar -----------------*/ -.sidebar { - width: 280px; -} - -.sidebar-nav { - padding-left: 20px; -} - -.sidebar-nav ul li.folder p { - margin-left: 4px; - font-weight: 400; - font-size: 14px !important; -} - -.sidebar-nav ul li.folder::before { - top: 12px !important; -} - -.sidebar-brand { - display: flex; - align-items: center; - justify-content: center; - display: flex; - flex-direction: column; - align-items: center; - border-bottom: 1px solid var(--borderColor); -} - -.sidebar-logo { - height: 75px; -} - -.search input:focus { - box-shadow: none !important; -} - -.input-wrap input { - outline: none; -} - -.clear-button { - background: none; - border: none; -} - -.clear-button > svg { - width: 28px; - height: 28px; - stroke: var(--accent); -} - -.search .matching-post { - border-color: var(--borderColor)!important; -} - -/* ----------------- Edit on Github -----------------*/ - -.github-corner .octo-body,.octo-arm { - fill: var(--background); -} - -/* ----------------- Darklight theme -----------------*/ - - @media only screen and (max-width: 768px) { - #docsify-darklight-theme { - visibility: hidden; - } - } - -/* ----------------- Code -----------------*/ - -code { - font-size: inherit!important; -} - -td::before { - content: '' !important; -} - -.docsify-copy-code-button span.success{ - right: 60%!important; -} - -.docsify-copy-code-button span.error{ - right: 60%!important; -} \ No newline at end of file diff --git a/docs/docs/README.md b/docs/docs/README.md deleted file mode 100644 index b191d0c..0000000 --- a/docs/docs/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Drago Documentation - -Welcome to the Drago documentation. This page is meant to be a reference for all available features and options in Drago. - -> [!NOTE] -> Drago is in active development, and its documentation is still a work-in-progress. We, therefore, apologize beforehand if information in this page is unclear or outdated. If you have any questions about a specific topic, or suggestions related to the project, please drop us a message on our [Gitter channel](https://gitter.im/seashell/drago) or create a [GitHub issue](https://github.com/seashell/drago/issues). -> diff --git a/docs/docs/commands/README.md b/docs/docs/commands/README.md deleted file mode 100644 index 92e6ad7..0000000 --- a/docs/docs/commands/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Drago Commands (CLI) - - -Drago is controlled via a very easy to use command-line interface (CLI). Drago is only a single command-line application: `drago`, which takes subcommands such as `agent` or `status`. The complete list of subcommands is in the navigation to the left. - -The Drago CLI is a well-behaved command line application. In erroneous cases, a non-zero exit status will be returned. It also responds to `-h` and `--help` as you would most likely expect. - -To view a list of the available commands at any time, just run Drago with no arguments. To get help for any specific subcommand, run the subcommand with the `-h` argument. - -Each command has been conveniently documented on this website. Links to each command can be found on the left. - -### Remote usage - -The Drago CLI can be used to interact with a remote Drago agent. - -To do so, set the `DRAGO_ADDR` environment variable or use the `--address=` flag when running commands. - -``` -$ DRAGO_ADDR=https://:8080 drago agent-info -$ drago agent-info --address=https://remote-address:4646 -``` - -The provided address must be reachable from your local machine. diff --git a/docs/docs/commands/acl/README.md b/docs/docs/commands/acl/README.md deleted file mode 100644 index 3a71469..0000000 --- a/docs/docs/commands/acl/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Command: acl - -The `acl` command is used to interact with ACL policies and tokens. - -## Usage - -Usage: `drago acl [options]` - -Run `drago acl [options] -h` for help on a specific subcommand. - -Available subcommands: - -- [`acl bootstrap`](/docs/commands/acl/bootstrap): Bootstrap the ACL system -- [`acl policy apply`](/docs/commands/acl/policy-apply): Create or update an ACL policy -- [`acl policy delete`](/docs/commands/acl/policy-delete): Delete an existing ACL policy -- [`acl policy info`](/docs/commands/acl/policy-info): Display info on an existing ACL policy -- [`acl policy list`](/docs/commands/acl/policy-list): List available ACL policies -- [`acl token create`](/docs/commands/acl/token-create): Create a new ACL token -- [`acl token delete`](/docs/commands/acl/token-delete): Delete an existing ACL token -- [`acl token info`](/docs/commands/acl/token-info): Display info on an existing ACL token -- [`acl token list`](/docs/commands/acl/token-list): List available ACL tokens -- [`acl token self`](/docs/commands/acl/token-self): Display info on self ACL token -- [`acl token update`](/docs/commands/acl/token-update): Update an existing ACL token diff --git a/docs/docs/commands/acl/bootstrap.md b/docs/docs/commands/acl/bootstrap.md deleted file mode 100644 index a5da103..0000000 --- a/docs/docs/commands/acl/bootstrap.md +++ /dev/null @@ -1,15 +0,0 @@ -# Command: acl bootstrap - -The `acl bootstrap` command can be used to bootstrap the initial ACL token. - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. diff --git a/docs/docs/commands/acl/policy-apply.md b/docs/docs/commands/acl/policy-apply.md deleted file mode 100644 index 925bc6d..0000000 --- a/docs/docs/commands/acl/policy-apply.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: acl policy apply - -The `acl policy apply` command is used to create or update ACL policies. - -## Usage - -``` -drago acl policy apply [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Apply Options - -- `--description=`: Sets the description of the ACL policy. diff --git a/docs/docs/commands/acl/policy-delete.md b/docs/docs/commands/acl/policy-delete.md deleted file mode 100644 index 130ad6a..0000000 --- a/docs/docs/commands/acl/policy-delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# Command: acl policy delete - -The `acl policy delete` command is used to delete an existing ACL policy. - -## Usage - -``` -drago acl policy delete [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. diff --git a/docs/docs/commands/acl/policy-info.md b/docs/docs/commands/acl/policy-info.md deleted file mode 100644 index 8b9526d..0000000 --- a/docs/docs/commands/acl/policy-info.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: acl policy info - -The `acl policy info` command is used to display detailed information about an existing ACL policy. - -## Usage - -``` -drago acl policy info [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## List Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/acl/policy-list.md b/docs/docs/commands/acl/policy-list.md deleted file mode 100644 index 2d0c5a8..0000000 --- a/docs/docs/commands/acl/policy-list.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: acl policy list - -The `acl policy list` command is used to list existing ACL policies. - -## Usage - -``` -drago acl policy list [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## List Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/acl/token-create.md b/docs/docs/commands/acl/token-create.md deleted file mode 100644 index 6f2c144..0000000 --- a/docs/docs/commands/acl/token-create.md +++ /dev/null @@ -1,31 +0,0 @@ -# Command: acl token create - -The `acl token create` command is used to issue new ACL tokens. - -## Usage - -``` -drago acl token create [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Create Options - -- `--name=`: Sets the name of the ACL token. - -- `--type=`: Sets the type of the ACL token. Must be either "client" or "management". If not provided, defaults to "client". - -- `--policy=`: Specifies policies to associate with a client token. Can be specified multiple times. - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/acl/token-delete.md b/docs/docs/commands/acl/token-delete.md deleted file mode 100644 index 5b2d1b2..0000000 --- a/docs/docs/commands/acl/token-delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# Command: acl token delete - -The `acl token delete` command is used to delete an existing ACL token. - -## Usage - -``` -drago acl token delete -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. diff --git a/docs/docs/commands/acl/token-info.md b/docs/docs/commands/acl/token-info.md deleted file mode 100644 index 8bf5b76..0000000 --- a/docs/docs/commands/acl/token-info.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: acl token info - -The `acl token info` command is used to display detailed information on an existing ACL token. - -## Usage - -``` -drago acl token info [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/acl/token-list.md b/docs/docs/commands/acl/token-list.md deleted file mode 100644 index 1ae67a1..0000000 --- a/docs/docs/commands/acl/token-list.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: acl token list - -The `acl token list` command is used to list all available ACL tokens. - -## Usage - -``` -drago acl token list [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/acl/token-self.md b/docs/docs/commands/acl/token-self.md deleted file mode 100644 index 345c814..0000000 --- a/docs/docs/commands/acl/token-self.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: acl token self - -The `acl token self` command is used to display detailed information on the currently set ACL token. - -## Usage - -``` -drago acl token self [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/acl/token-update.md b/docs/docs/commands/acl/token-update.md deleted file mode 100644 index 742ddf4..0000000 --- a/docs/docs/commands/acl/token-update.md +++ /dev/null @@ -1,31 +0,0 @@ -# Command: acl token update - -The `acl token update` command is used to update an existing ACL token. - -## Usage - -``` -drago acl token update [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Update Options - -- `--name=`: Sets the name of the ACL token. - -- `--type=`: Sets the type of the ACL token. Must be either "client" or "management". If not provided, defaults to "client". - -- `--policy=`: Specifies policies to associate with a client token. Can be specified multiple times. - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/agent-info.md b/docs/docs/commands/agent-info.md deleted file mode 100644 index ba6bb83..0000000 --- a/docs/docs/commands/agent-info.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: agent-info - -The `agent-info` command is used to display configurations and status from the agent to which the CLI is connected. - -## Usage - -``` -drago agent-info [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Agent Info Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/agent.md b/docs/docs/commands/agent.md deleted file mode 100644 index 117bb11..0000000 --- a/docs/docs/commands/agent.md +++ /dev/null @@ -1,29 +0,0 @@ -# Command: agent - -The `agent` command is likely Drago's most important command. It is used to start client and/or server agent. - -## Command-line Options - -The `agent` command accepts the following arguments (note that some of them can be overriden by CLI flags): - -- `--client`: Enable client mode on the local agent. - -- `--config=`: Indicate the path to a configuration file containing configurations to be used by the Drago agent. Can be speficied multiple times. - -- `--data-dir=`: - -- `--dev`: Enable development mode on the local agent. This means the agent will execute both as a client and as a server, with sane configurations that are ideal for development. - -- `--node=`: - -- `--server`: Enable server mode on the local agent. - -- `--servers=`: - -- `--wireguard-path`: Path to a userspace WireGuard implementation. Both `wireguard-go` and `cloudflare/boringtun` are supported. If not provided, the WireGuard kernel module will be used. - -## Example - -``` -$ drago agent --server -``` diff --git a/docs/docs/commands/connection/create.md b/docs/docs/commands/connection/create.md deleted file mode 100644 index 0a1f4fd..0000000 --- a/docs/docs/commands/connection/create.md +++ /dev/null @@ -1,29 +0,0 @@ -# Command: connection create - -The `connection create` command is used to create a connection. - -## Usage - -``` -drago connection create [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--json`: Enable JSON output. - -- `--allow-all`: Enables routing of all traffic in this connection. - -- `--keepalive=`: Time interval between persistent keepalive packets. Defaults to 0, which disables the feature. diff --git a/docs/docs/commands/connection/list.md b/docs/docs/commands/connection/list.md deleted file mode 100644 index a01dada..0000000 --- a/docs/docs/commands/connection/list.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: connection list - -The `connection list` command is used to list all available connections. - -## Usage - -``` -drago connection list [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/connection/update.md b/docs/docs/commands/connection/update.md deleted file mode 100644 index b471ee3..0000000 --- a/docs/docs/commands/connection/update.md +++ /dev/null @@ -1,21 +0,0 @@ -# Command: connection update - -The `connection update` command is used to update an existing connection. - -## Usage - -``` -drago connection update [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. diff --git a/docs/docs/commands/interface/list.md b/docs/docs/commands/interface/list.md deleted file mode 100644 index 2cecbe9..0000000 --- a/docs/docs/commands/interface/list.md +++ /dev/null @@ -1,31 +0,0 @@ -# Command: interface list - -The `interface list` command is used to list all available interfaces. - -## Usage - -``` -drago interface list [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--json`: Enable JSON output. - -- `--self`: Filter results by the local node ID. Can not be used with the --node filter flag. - -- `--node=`: Filter results by node ID. Can not be used with the --self filter flag. - -- `--network=`: Filter results by network. diff --git a/docs/docs/commands/interface/update.md b/docs/docs/commands/interface/update.md deleted file mode 100644 index d4c1a47..0000000 --- a/docs/docs/commands/interface/update.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: interface update - -The `interface update` command is used to update an existing interface. - -## Usage - -``` -drago interface update [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Update Options - -- `--address`: Interface IP address in CIDR notation diff --git a/docs/docs/commands/network/create.md b/docs/docs/commands/network/create.md deleted file mode 100644 index 660b57d..0000000 --- a/docs/docs/commands/network/create.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: network create - -The `network create` command is used to create a new network. - -## Usage - -``` -drago network create [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Create Options - -- `--range=`: Network IP address range in CIDR notation. diff --git a/docs/docs/commands/network/delete.md b/docs/docs/commands/network/delete.md deleted file mode 100644 index 7bfa9f2..0000000 --- a/docs/docs/commands/network/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# Command: network delete - -The `network delete` command is used to delete an existing network. - -## Usage - -``` -drago network delete [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. diff --git a/docs/docs/commands/network/info.md b/docs/docs/commands/network/info.md deleted file mode 100644 index 214d495..0000000 --- a/docs/docs/commands/network/info.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: network info - -The `network info` command is used to display detailed information about an existing network. - -## Usage - -``` -drago network info [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/network/list.md b/docs/docs/commands/network/list.md deleted file mode 100644 index 508e588..0000000 --- a/docs/docs/commands/network/list.md +++ /dev/null @@ -1,25 +0,0 @@ -# Command: network list - -The `network list` command is used to list all available networks. - -## Usage - -``` -drago network list [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## List Options - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/node/info.md b/docs/docs/commands/node/info.md deleted file mode 100644 index e5926c1..0000000 --- a/docs/docs/commands/node/info.md +++ /dev/null @@ -1,27 +0,0 @@ -# Command: node info - -The `node info` command is used to display detailed information about an existing node. - -## Usage - -``` -drago node info [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--self`: Query the status of the local node. - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/node/join.md b/docs/docs/commands/node/join.md deleted file mode 100644 index ff23d94..0000000 --- a/docs/docs/commands/node/join.md +++ /dev/null @@ -1,21 +0,0 @@ -# Command: node join - -The `node join` command is used to have the local client node join an existing network. - -## Usage - -``` -drago node join [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. diff --git a/docs/docs/commands/node/leave.md b/docs/docs/commands/node/leave.md deleted file mode 100644 index 5500826..0000000 --- a/docs/docs/commands/node/leave.md +++ /dev/null @@ -1,21 +0,0 @@ -# Command: node leave - -The `node leave` command is used to have the local client node leave a specific network. - -## Usage - -``` -drago node leave [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. diff --git a/docs/docs/commands/node/list.md b/docs/docs/commands/node/list.md deleted file mode 100644 index 1eb9481..0000000 --- a/docs/docs/commands/node/list.md +++ /dev/null @@ -1,29 +0,0 @@ -# Command: node list - -The `node list` command is used to list all available nodes. - -## Usage - -``` -drago node list [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--json`: Enable JSON output. - -- `--meta=`: Filter nodes by metadata. - -- `--status=`: Filter nodes by status. diff --git a/docs/docs/commands/node/status.md b/docs/docs/commands/node/status.md deleted file mode 100644 index 32b2331..0000000 --- a/docs/docs/commands/node/status.md +++ /dev/null @@ -1,27 +0,0 @@ -# Command: node status - -The `node status` command is used to list the status of one or more registered client nodes. - -## Usage - -``` -drago node status [options] -``` - -## General Options - -- `--address=` - The address of the Drago server. - Overrides the `DRAGO_ADDR` environment variable if set. - Defaults to `http://127.0.0.1:8080`. - -- `--token=` - The token used to authenticate with the Drago server. - Overrides the `DRAGO_TOKEN` environment variable if set. - Defaults to `""`. - -## Info Options - -- `--self`: Query the status of the local node. - -- `--json`: Enable JSON output. diff --git a/docs/docs/commands/ui.md b/docs/docs/commands/ui.md deleted file mode 100644 index 3fb16ca..0000000 --- a/docs/docs/commands/ui.md +++ /dev/null @@ -1,14 +0,0 @@ -# Command: ui - -The `ui` command is used to open Drago's Web UI. - -## Usage - -``` -drago ui [options] -``` - -## General Options - -- `--address`: -- `--token`: diff --git a/docs/docs/configuration/README.md b/docs/docs/configuration/README.md deleted file mode 100644 index 248069e..0000000 --- a/docs/docs/configuration/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Drago Configuration - -Drago agents can be configured via HCL configuration files or command-line flags. diff --git a/docs/docs/configuration/acl.md b/docs/docs/configuration/acl.md deleted file mode 100644 index 469afd2..0000000 --- a/docs/docs/configuration/acl.md +++ /dev/null @@ -1,7 +0,0 @@ -# `acl` Block - -The `acl`block is used to configure the Drago ACL system. - -## `acl` Parameters - -- `enabled` `(bool: false)` - Defines whether if ACL is enabled or not. All other configurations in this section are only applied if `enabled` is set to `true`. diff --git a/docs/docs/configuration/client.md b/docs/docs/configuration/client.md deleted file mode 100644 index b263308..0000000 --- a/docs/docs/configuration/client.md +++ /dev/null @@ -1,7 +0,0 @@ -# `client` Block - -The `client`block is used to configure the Drago agent when operating in client mode. - -## `client` Parameters - -- `enabled` `(bool: false)` - Specify if the agent will run in client mode. diff --git a/docs/docs/configuration/server.md b/docs/docs/configuration/server.md deleted file mode 100644 index 1d767ce..0000000 --- a/docs/docs/configuration/server.md +++ /dev/null @@ -1,7 +0,0 @@ -# `server` Block - -The `server`block is used to configure the Drago agent when operating in server mode. - -## `server` Parameters - -- `enabled` `(bool: false)` - Specify if the agent will run in server mode. diff --git a/docs/docs/installing/README.md b/docs/docs/installing/README.md deleted file mode 100644 index 26cbea2..0000000 --- a/docs/docs/installing/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Installing Drago - -We are working to make Drago available as a pre-compiled binary or as a package for several operating systems. - -Meanwhile, you can build Drago from source. - -## Compiling from Source - -To compile from source, you will need Go installed and configured properly. - -1. Clone the Drago repository: - ```bash - $ git clone https://github.com/seashell/drago.git - $ cd drago - ``` -2. Download all necessary Go modules into the module cache: - - ```bash - $ go mod download - ``` -3. Build Drago for your current system. - - ```bash - $ go build -o ./build/drago - ``` - -## Verify the binary - -To verify Drago was built correctly, run the binary: - -```bash -$ ./build/drago -``` diff --git a/docs/docs/installing/quickstart.md b/docs/docs/installing/quickstart.md deleted file mode 100644 index 2f46430..0000000 --- a/docs/docs/installing/quickstart.md +++ /dev/null @@ -1,6 +0,0 @@ -# Quickstart - -This page describes methods for installing Drago in different environments. - -> [!WARNING] -> This section is still a work-in-progress. If you think you can contribute, please see our [contribution guidelines](docs/../../../contributing.md). diff --git a/docs/docs/internals/README.md b/docs/docs/internals/README.md deleted file mode 100644 index 4f47e9a..0000000 --- a/docs/docs/internals/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Drago Internals - -This section covers the internals of Drago and explains the technical details of how Drago functions, its architecture, and sub-systems. - -> [!NOTE] -> Knowledge of Drago internals is not required to use Drago. If you aren't interested in the internals of Drago, you may safely skip this section. If you are operating Drago, we recommend understanding the internals. diff --git a/docs/docs/internals/architecture.md b/docs/docs/internals/architecture.md deleted file mode 100644 index 7780525..0000000 --- a/docs/docs/internals/architecture.md +++ /dev/null @@ -1,13 +0,0 @@ -# Architecture - -Drago follows a client-server paradigm, in which a centralized server provides multiple clients running alongside WireGuard with their desired state. The desired state is periodically retrieved from the server and applied to the WireGuard interfaces on each node. - -Drago exposes two APIs through which configurations can be retrieved and modified. The RPC API is primarily for client agents to synchronize their configurations, whereas the HTTP API is meant for management purposes. - -Drago implements authentication mechanisms to prevent unauthorized access, and serves a slick web UI to facilitate the process of updating and visualizing the state of the managed networks. Everything is nicely bundled within the same binary. - -The Drago client, expected to run on every node in the network, is responsible for directly interacting with the server through the API, and for retrieving the most up-to-date configurations. Through a simple reconciliation process, the Drago client then guarantees that the WireGuard configurations on each node match the desired state stored in the database. When running in client mode, Drago also takes care of automatically generating key pairs for WireGuard, and sharing the public key so that nodes can always connect to each other. - -The only assumption made by Drago is that each node running the client is also running WireGuard and that the node in which the configuration server is located is reachable through the network. - -Drago does not enforce any specific network topology. Its sole responsibility is to distribute the desired configurations, and guarantee that they are correctly applied to WireGuard on every single registered node. This means that it is up to you to define how your nodes are connected to each other and how your network should look like. diff --git a/docs/docs/overview.md b/docs/docs/overview.md deleted file mode 100644 index 23b29ee..0000000 --- a/docs/docs/overview.md +++ /dev/null @@ -1,26 +0,0 @@ -# Overview - -> [!WARNING] -> This section is still a work-in-progress. If you think you can contribute, please see our [contribution guidelines](docs/../../../contributing.md). - - -Drago is a flexible configuration manager for WireGuard networks that is designed to make it simple to configure network overlays spanning heterogeneous nodes distributed across different clouds and physical locations. - -Drago is meant to be simple and provide a solid foundation for higher-level functionality. Need automatic IP assignment, dynamic firewall rules, or some kind of telemetry? You are free to implement it on top of the already existing APIs. - -## Use-cases - -- Secure home automation, SSH access, etc -- Establish secure VPNs for your company -- Manage access to sensitive services deployed to private hosts -- Expose development servers for debugging and demonstration purposes -- Establish multi-cloud clusters with ease -- Build your own cloud with RaspberryPIs - -## Main features - -- Single-binary, lightweight -- Encrypted node-to-node communication -- Support for different WireGuard implementations -- Slick management dashboard -- Extensible via REST API \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 2fea9d4..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,10 +0,0 @@ -# FAQ - -> [!WARNING] -> This section is still a work-in-progress. If you think you can contribute, please see our [contribution guidelines](docs/../../../contributing.md). - -## Why Drago? - -WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. Initially released for the Linux kernel, it is now cross-platform and widely deployable, being regarded as the most secure, easiest to use, and simplest VPN solution in the industry. - -WireGuard presents several advantages over other VPN solutions, but it does not allow for the dynamic configuration of network parameters such as IP addresses and firewall rules. Drago builds on top of WireGuard, allowing users to dynamically manage the configuration of their VPN networks, providing a unified control plane for overlays spanning from containers to virtual machines to IoT devices. \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 4b2db14..0000000 --- a/docs/index.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - Drago Docs - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - diff --git a/docs/js/darklight-plugin.js b/docs/js/darklight-plugin.js deleted file mode 100644 index 6dfab8c..0000000 --- a/docs/js/darklight-plugin.js +++ /dev/null @@ -1,126 +0,0 @@ -// Disclaimer: This file was extracted from https://github.com/boopathikumar018/docsify-darklight-theme, -// and modified to fix an annoying bug that was breaking other plugins. A fix was already posted to GitHub -// issues (https://github.com/boopathikumar018/docsify-darklight-theme/issues/14), and as soon as it is -// applied by the maintainer we can go back to fetching the plugin from a CDN, as usual. - -const plugin = (hook, vm) => { - - var defaultConfig = { - siteFont : "PT Sans", - defaultTheme : 'dark', - codeFontFamily : 'Roboto Mono, Monaco, courier, monospace', - bodyFontSize : '17px', - dark: { - accent: '#42b983', - toogleBackground : '#ffffff', - background: '#091a28', - textColor: '#b4b4b4', - codeTextColor : '#ffffff', - codeBackgroudColor : '#0e2233', - borderColor : '#0d2538', - blockQuoteColour : '#858585', - highlightColor : '#d22778', - sidebarSublink : '#b4b4b4', - codeTypeColor : '#ffffff', - coverBackground : 'linear-gradient(to left bottom, hsl(118, 100%, 85%) 0%,hsl(181, 100%, 85%) 100%)', - toogleImage : 'url(https://cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/icons/sun.svg)' - }, - light: { - accent: '#42b983', - toogleBackground : '#091a28', - background: '#ffffff', - textColor: '#34495e', - codeTextColor : '#525252', - codeBackgroudColor : '#f8f8f8', - borderColor : 'rgba(0, 0, 0, 0.07)', - blockQuoteColor : '#858585', - highlightColor : '#d22778', - sidebarSublink : '#505d6b', - codeTypeColor : '#091a28', - coverBackground : 'linear-gradient(to left bottom, hsl(118, 100%, 85%) 0%,hsl(181, 100%, 85%) 100%)', - toogleImage : 'url(https://cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/icons/moon.svg)' - } - } - - let themeConfig = defaultConfig; - - if(vm.config.hasOwnProperty("darklightTheme")) { - for (var [key, value] of Object.entries(vm.config.darklightTheme)) { - if(key !== 'light' && key !== 'dark' && key !== 'defaultTheme') { - themeConfig[key] = value; - } - } - - for (var [key, value] of Object.entries(themeConfig)) { - if(key !== 'light' && key !== 'dark') { - themeConfig[key] = value; - document.documentElement.style.setProperty('--'+key , value); - } - } - - if(vm.config.darklightTheme.hasOwnProperty("dark")) { - for (var [key, value] of Object.entries(vm.config.darklightTheme.dark)) { - themeConfig.dark[key] = value - } - } - - if(vm.config.darklightTheme.hasOwnProperty("light")) { - for (var [key, value] of Object.entries(vm.config.darklightTheme.light)) - themeConfig.light[key] = value - } - - } else { - for (var [key, value] of Object.entries(themeConfig)) { - if(key !== 'light' && key !== 'dark') { - themeConfig[key] = value; - document.documentElement.style.setProperty('--'+key , value); - } - } - } - - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - themeConfig.defaultTheme = 'dark'; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - themeConfig.defaultTheme = 'light'; - } - - var setTheme = (theme) => { - - localStorage.setItem('DARK_LIGHT_THEME', theme); - themeConfig.defaultTheme = theme; - - if(theme == "light") { - for (var [key, value] of Object.entries(themeConfig.light)) - document.documentElement.style.setProperty('--'+key , value) - } else if ( theme == "dark") { - for (var [key, value] of Object.entries(themeConfig.dark)) - document.documentElement.style.setProperty('--'+key , value) - } - - } - - hook.afterEach(function(html, next) { - var darkEl = `

.

` - html = `${darkEl}${html}` - next(html) - }) - - hook.doneEach(function() { - let savedTheme = localStorage.getItem('DARK_LIGHT_THEME') - if ( savedTheme == "light" || savedTheme == "dark") { - themeConfig.defaultTheme = savedTheme; - setTheme(themeConfig.defaultTheme) - } else { - setTheme(themeConfig.defaultTheme); - } - - const el = document.getElementById('docsify-darklight-theme') - - if (el !== null) { - el.addEventListener('click', function() { themeConfig.defaultTheme === 'light' ? setTheme('dark') : setTheme('light')}) - } - - }) - } - - window.$docsify.plugins = [].concat(plugin, window.$docsify.plugins) \ No newline at end of file diff --git a/docs/license.md b/docs/license.md deleted file mode 100644 index 3d1679a..0000000 --- a/docs/license.md +++ /dev/null @@ -1,4 +0,0 @@ -# License - -Drago is released under the [Apache 2.0 license](https://github.com/seashell/drago/blob/master/LICENSE). - diff --git a/drago/acl.go b/drago/acl.go deleted file mode 100644 index d47aa43..0000000 --- a/drago/acl.go +++ /dev/null @@ -1,117 +0,0 @@ -package drago - -import ( - "context" - "time" - - auth "github.com/seashell/drago/drago/auth" - state "github.com/seashell/drago/drago/state" - structs "github.com/seashell/drago/drago/structs" - log "github.com/seashell/drago/pkg/log" - uuid "github.com/seashell/drago/pkg/uuid" -) - -const ( - // aclBootstrapResetFileName is the name of the file in the data dir containing the reset index. - aclBootstrapResetFileName = "acl-bootstrap-reset" -) - -// ACLService : -type ACLService struct { - config *Config - logger log.Logger - state state.Repository - authHandler auth.AuthorizationHandler -} - -// NewACLService : -func NewACLService(config *Config, logger log.Logger, state state.Repository, authHandler auth.AuthorizationHandler) *ACLService { - return &ACLService{ - config: config, - logger: logger, - state: state, - authHandler: authHandler, - } -} - -// BootstrapACL : -func (s *ACLService) BootstrapACL(args *structs.ACLBootstrapRequest, out *structs.ACLTokenUpsertResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - if s.isBootstrapped(ctx) { - return structs.ErrACLAlreadyBootstrapped - } - - t := &structs.ACLToken{ - ID: uuid.Generate(), - Name: "Root Token", - Secret: uuid.Generate(), - Type: structs.ACLTokenTypeManagement, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - err := s.state.UpsertACLToken(ctx, t) - if err != nil { - return err - } - - state := s.aclStateLazy() - state.RootTokenID = t.ID - state.RootTokenResetIndex++ - - // Update ACL state - err = s.state.ACLSetState(ctx, state) - if err != nil { - return err - } - - out.ACLToken = t - - return nil -} - -// ResolveToken : -func (s *ACLService) ResolveToken(args *structs.ResolveACLTokenRequest, out *structs.ResolveACLTokenResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - t, err := s.state.ACLTokenBySecret(context.TODO(), args.Secret) - if err != nil { - return err - } - - if t == nil { - t = AnonymousACLToken - } - - out.ACLToken = t - - return nil -} - -func (s *ACLService) isBootstrapped(ctx context.Context) bool { - return s.aclStateLazy().RootTokenID != "" -} - -// ACLStateLazy implements lazy state persistence -func (s *ACLService) aclStateLazy() *structs.ACLState { - ctx := context.TODO() - aclState, err := s.state.ACLState(ctx) - if err != nil { - aclState = &structs.ACLState{} - s.state.ACLSetState(ctx, aclState) - } - return aclState -} diff --git a/drago/acl_policy.go b/drago/acl_policy.go deleted file mode 100644 index a1c8a8c..0000000 --- a/drago/acl_policy.go +++ /dev/null @@ -1,124 +0,0 @@ -package drago - -import ( - "context" - "time" - - structs "github.com/seashell/drago/drago/structs" -) - -const ( - ACLPolicyList = "list" - ACLPolicyRead = "read" - ACLPolicyWrite = "write" -) - -func (s *ACLService) GetPolicy(args *structs.ACLPolicySpecificRequest, out *structs.SingleACLPolicyResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - // Check if authorized - if !s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "policy", args.Name, ACLPolicyRead); err != nil { - return structs.ErrPermissionDenied - } - } - - p, err := s.state.ACLPolicyByName(ctx, args.Name) - if err != nil { - return structs.ErrNotFound - } - - out.ACLPolicy = p - - return nil -} - -func (s *ACLService) UpsertPolicy(args *structs.ACLPolicyUpsertRequest, out *structs.GenericResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - // Check if authorized - if err := s.authHandler.Authorize(ctx, args.AuthToken, "policy", "", ACLPolicyWrite); err != nil { - return structs.ErrPermissionDenied - } - - p := args.ACLPolicy - - err := p.Validate() - if err != nil { - return structs.NewError(structs.ErrInvalidInput, err) - } - - old, err := s.state.ACLPolicyByName(ctx, p.Name) - if err != nil { - p.CreatedAt = time.Now() - } else { - p = old.Merge(p) - } - - p.UpdatedAt = time.Now() - - err = s.state.UpsertACLPolicy(ctx, p) - if err != nil { - return structs.ErrInternal - } - - return nil -} - -func (s *ACLService) DeletePolicies(args *structs.ACLPolicyDeleteRequest, out *structs.GenericResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - // Check if authorized - if err := s.authHandler.Authorize(ctx, args.AuthToken, "policy", "", ACLPolicyWrite); err != nil { - return structs.ErrPermissionDenied - } - - err := s.state.DeleteACLPolicies(ctx, args.Names) - if err != nil { - return structs.ErrInternal - } - - return nil -} - -func (s *ACLService) ListPolicies(args *structs.ACLPolicyListRequest, out *structs.ACLPolicyListResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - // Check if authorized - if err := s.authHandler.Authorize(ctx, args.AuthToken, "policy", "", ACLPolicyList); err != nil { - return structs.ErrPermissionDenied - } - - policies, err := s.state.ACLPolicies(ctx) - if err != nil { - return structs.ErrInternal - } - - out.Items = nil - - for _, p := range policies { - out.Items = append(out.Items, p.Stub()) - } - - return nil -} diff --git a/drago/acl_token.go b/drago/acl_token.go deleted file mode 100644 index c1c17e5..0000000 --- a/drago/acl_token.go +++ /dev/null @@ -1,135 +0,0 @@ -package drago - -import ( - "context" - "time" - - structs "github.com/seashell/drago/drago/structs" - uuid "github.com/seashell/drago/pkg/uuid" -) - -const ( - ACLTokenList = "list" - ACLTokenRead = "read" - ACLTokenWrite = "write" -) - -// GetToken returns a Token entity by ID -func (s *ACLService) GetToken(args *structs.ACLTokenSpecificRequest, out *structs.SingleACLTokenResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - // Check if authorized - if err := s.authHandler.Authorize(ctx, args.AuthToken, "token", args.ACLTokenID, ACLTokenRead); err != nil { - return structs.ErrPermissionDenied - } - - t, err := s.state.ACLTokenByID(ctx, args.ACLTokenID) - if err != nil { - return structs.ErrNotFound - } - - out.ACLToken = t - - return nil -} - -// UpsertToken creates or updates a new Token entity -func (s *ACLService) UpsertToken(args *structs.ACLTokenUpsertRequest, out *structs.ACLTokenUpsertResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - // Check if authorized - if err := s.authHandler.Authorize(ctx, args.AuthToken, "token", "", ACLTokenWrite); err != nil { - return structs.ErrPermissionDenied - } - - t := args.ACLToken - - err := t.Validate() - if err != nil { - return structs.ErrInvalidInput - } - - // Generate a new ID and secret if the token is new - if t.ID == "" { - t.ID = uuid.Generate() - t.Secret = uuid.Generate() - t.CreatedAt = time.Now() - } else { - old, err := s.state.ACLTokenByID(ctx, t.ID) - if err != nil { - return structs.ErrNotFound - } - t = old.Merge(t) - } - - t.UpdatedAt = time.Now() - - err = s.state.UpsertACLToken(ctx, t) - if err != nil { - return structs.ErrInternal - } - - out.ACLToken = t - - return nil -} - -// DeleteToken deletes a token entity from the repository -func (s *ACLService) DeleteToken(args *structs.ACLTokenDeleteRequest, out *structs.GenericResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - // Check if authorized - if err := s.authHandler.Authorize(ctx, args.AuthToken, "token", "", ACLTokenWrite); err != nil { - return structs.ErrPermissionDenied - } - - err := s.state.DeleteACLTokens(ctx, args.ACLTokenIDs) - if err != nil { - return structs.ErrInternal - } - - return nil -} - -// ListTokens retrieves all token entities in the repository -func (s *ACLService) ListTokens(args *structs.ACLTokenListRequest, out *structs.ACLTokenListResponse) error { - - if !s.config.ACL.Enabled { - return structs.ErrACLDisabled - } - - ctx := context.TODO() - - // Check if authorized - if err := s.authHandler.Authorize(ctx, args.AuthToken, "token", "", ACLTokenList); err != nil { - return structs.ErrPermissionDenied - } - - tokens, err := s.state.ACLTokens(ctx) - if err != nil { - return structs.ErrInternal - } - - out.Items = nil - - for _, t := range tokens { - out.Items = append(out.Items, t.Stub()) - } - - return nil -} diff --git a/drago/auth/auth.go b/drago/auth/auth.go deleted file mode 100644 index e99e49e..0000000 --- a/drago/auth/auth.go +++ /dev/null @@ -1,49 +0,0 @@ -package auth - -import ( - "context" - - "github.com/seashell/drago/pkg/acl" -) - -// AuthorizationHandler abstracts a handler capable of -// authorizing subjects to perform specific operations on -// resources at a given path -type AuthorizationHandler interface { - Authorize(ctx context.Context, sub, res, path, op string) error -} - -// authorizationHandler implements the AuthorizationHandler -// interface, thus being capable of authorizing operations. -type authorizationHandler struct { - resolver *acl.Resolver -} - -// NewAuthorizationHandler returns a new AuthorizationHandler -func NewAuthorizationHandler( - model *acl.Model, - secretResolver acl.SecretResolverFunc, - policyResolver acl.PolicyResolverFunc) AuthorizationHandler { - - aclResolver, _ := acl.NewResolver(&acl.ResolverConfig{ - Model: model, - SecretResolver: secretResolver, - PolicyResolver: policyResolver, - }) - - return &authorizationHandler{ - resolver: aclResolver, - } -} - -// Authorize checks whether or not the specified operation is authorized or -// not on the targeted resource and path, potentially returning an error. -func (h *authorizationHandler) Authorize(ctx context.Context, sub, res, path, op string) error { - - acl, err := h.resolver.ResolveSecret(ctx, sub) - if err != nil { - return err - } - - return acl.CheckAuthorized(ctx, res, path, op) -} diff --git a/drago/auth/policy.go b/drago/auth/policy.go deleted file mode 100644 index 1120de6..0000000 --- a/drago/auth/policy.go +++ /dev/null @@ -1,30 +0,0 @@ -package auth - -import "github.com/seashell/drago/pkg/acl" - -// Policy implements the acl.Policy interface. -type Policy struct { - name string - rules []acl.Rule -} - -// NewPolicy : -func NewPolicy(name string, rules []acl.Rule) *Policy { - return &Policy{name, rules} -} - -// Name returns the name of the policy. -func (p *Policy) Name() string { - return p.name -} - -// Rules return the policy rules. -func (p *Policy) Rules() []acl.Rule { - return p.rules -} - -// AddRule appends an acl.Rule to the policy rules, returning the resulting slice. -func (p *Policy) AddRule(r acl.Rule) []acl.Rule { - p.rules = append(p.rules, r) - return p.rules -} diff --git a/drago/auth/rule.go b/drago/auth/rule.go deleted file mode 100644 index 1465002..0000000 --- a/drago/auth/rule.go +++ /dev/null @@ -1,31 +0,0 @@ -package auth - -// Rule implements the acl.Rule interface. -type Rule struct { - resource string - path string - capabilities []string -} - -// NewRule : -func NewRule(res, path string, caps []string) *Rule { - return &Rule{res, path, caps} -} - -// Resource returns the type of resource targeted -// by the rule. -func (r *Rule) Resource() string { - return r.resource -} - -// Path returns the pattern this rule uses to -// match targeted specific resource instances. -func (r *Rule) Path() string { - return r.path -} - -// Capabilities return a slice of capabilities enabled -// on the targeted resource instances. -func (r *Rule) Capabilities() []string { - return r.capabilities -} diff --git a/drago/auth/token.go b/drago/auth/token.go deleted file mode 100644 index 7a1e4a6..0000000 --- a/drago/auth/token.go +++ /dev/null @@ -1,24 +0,0 @@ -package auth - -// Token implements the acl.Token interface -type Token struct { - privileged bool - policies []string -} - -// NewToken : -func NewToken(privileged bool, policies []string) *Token { - return &Token{privileged, policies} -} - -// Policies returns a slice of policies associated with -// the token. -func (t *Token) Policies() []string { - return t.policies -} - -// IsPrivileged returns true if the token has privileged access, -// and false otherwise. -func (t *Token) IsPrivileged() bool { - return t.privileged -} diff --git a/drago/config.go b/drago/config.go deleted file mode 100644 index 5aac8ac..0000000 --- a/drago/config.go +++ /dev/null @@ -1,81 +0,0 @@ -package drago - -import ( - "time" - - "github.com/seashell/drago/drago/structs/config" - log "github.com/seashell/drago/pkg/log" - version "github.com/seashell/drago/version" -) - -const ( - defaultBindAddress = "0.0.0.0" - defaultLogLevel = "DEBUG" - defaultDataDir = "/tmp/drago" - defaultHTTPPort = 8080 - defaultRPCPort = 8081 -) - -// Config : Drago server configuration. -type Config struct { - // UI enabled. - UI bool - - // DevMode enabled. - DevMode bool - - // Version is the version of the Drago server - Version *version.VersionInfo - - // LogLevel is the level at which the server should output logs - LogLevel string - - //Logger. - Logger log.Logger - - // BindAddr. - BindAddr string - - // RPCAdvertiseAddr is the address advertised to client nodes. - RPCAdvertiseAddr string - - // DataDir is the directory to store our state in. - DataDir string - - // Ports. - Ports *Ports - - // ACL - ACL *config.ACLConfig - - // Etcd. - Etcd *config.EtcdConfig - - // HostGCInterval is how often we perform garbage collection of hosts. - HostGCInterval time.Duration -} - -// Ports : -type Ports struct { - HTTP int - RPC int -} - -// DefaultConfig returns the default configuration. -func DefaultConfig() *Config { - return &Config{ - UI: true, - DevMode: false, - Version: version.GetVersion(), - LogLevel: defaultLogLevel, - BindAddr: defaultBindAddress, - DataDir: defaultDataDir, - Ports: &Ports{ - HTTP: defaultHTTPPort, - RPC: defaultRPCPort, - }, - ACL: config.DefaultACLConfig(), - Etcd: config.DefaultEtcdConfig(), - HostGCInterval: 5 * time.Minute, - } -} diff --git a/drago/connection.go b/drago/connection.go deleted file mode 100644 index fe5df4c..0000000 --- a/drago/connection.go +++ /dev/null @@ -1,296 +0,0 @@ -package drago - -import ( - "context" - "fmt" - "time" - - auth "github.com/seashell/drago/drago/auth" - state "github.com/seashell/drago/drago/state" - structs "github.com/seashell/drago/drago/structs" - log "github.com/seashell/drago/pkg/log" - uuid "github.com/seashell/drago/pkg/uuid" -) - -const ( - ConnectionList = "list" - ConnectionRead = "read" - ConnectionWrite = "write" -) - -type ConnectionService struct { - config *Config - logger log.Logger - state state.Repository - authHandler auth.AuthorizationHandler -} - -// NewConnectionService ... -func NewConnectionService(config *Config, logger log.Logger, state state.Repository, authHandler auth.AuthorizationHandler) *ConnectionService { - return &ConnectionService{ - config: config, - logger: logger, - state: state, - authHandler: authHandler, - } -} - -// GetConnection returns a Connection entity by ID -func (s *ConnectionService) GetConnection(args *structs.ConnectionSpecificRequest, out *structs.SingleConnectionResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "connection", args.ConnectionID, ConnectionRead); err != nil { - return structs.ErrPermissionDenied - } - } - - n, err := s.state.ConnectionByID(ctx, args.ConnectionID) - if err != nil { - return structs.ErrNotFound - } - - out.Connection = n - - return nil -} - -// ListConnections retrieves all connection entities in the repository -func (s *ConnectionService) ListConnections(args *structs.ConnectionListRequest, out *structs.ConnectionListResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "connection", "", ConnectionList); err != nil { - return structs.ErrPermissionDenied - } - } - - out.Items = nil - - var err error - var connections []*structs.Connection - - if args.InterfaceID != "" { - if connections, err = s.state.ConnectionsByInterfaceID(ctx, args.InterfaceID); err != nil { - return structs.ErrInternal - } - } else if args.NodeID != "" { - if connections, err = s.state.ConnectionsByNodeID(ctx, args.NodeID); err != nil { - return structs.ErrInternal - } - } else if args.NetworkID != "" { - if connections, err = s.state.ConnectionsByNetworkID(ctx, args.NetworkID); err != nil { - return structs.ErrInternal - } - } else { - if connections, err = s.state.Connections(ctx); err != nil { - return structs.ErrInternal - } - } - - for _, c := range connections { - shouldAppend := true - if args.NetworkID != "" && c.NetworkID != args.NetworkID { - shouldAppend = false - } - if args.InterfaceID != "" && !c.ConnectsInterface(args.InterfaceID) { - shouldAppend = false - } - if shouldAppend { - out.Items = append(out.Items, c.Stub()) - } - } - - return nil -} - -// UpsertConnection upserts a new Connection entity -func (s *ConnectionService) UpsertConnection(args *structs.ConnectionUpsertRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "connection", "", ConnectionWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - c := args.Connection - - err := c.Validate() - if err != nil { - return structs.NewInvalidInputError(err.Error()) - } - - // If the connection already exists, we simply merge the new values into the existing struct. - // Otherwise, we generate a new ID and set the protected attributes in preparation for inserting - // the new struct into the repository. - if c.ID != "" { - old, err := s.state.ConnectionByID(ctx, c.ID) - if err != nil { - return structs.ErrNotFound // connection does not exist - } - c = old.Merge(c) - } else { - c.ID = uuid.Generate() - c.CreatedAt = time.Now() - } - - connectedInterfaceIDs := c.ConnectedInterfaceIDs() - - if len(connectedInterfaceIDs) != 2 { - return structs.NewInternalError("A connection must specify exactly two interfaces") - } - if connectedInterfaceIDs[0] == connectedInterfaceIDs[1] { - return structs.NewInternalError("Can't connect an interface to itself") - } - // Make sure interfaces are not already connected - if conn, err := s.state.ConnectionByInterfaceIDs(ctx, connectedInterfaceIDs[0], connectedInterfaceIDs[1]); err == nil { - if conn.ID != c.ID { - return structs.NewInternalError("Interfaces already connected") - } - } - - // Make sure both peer settings are correctly initialized - for _, id := range connectedInterfaceIDs { - - // Initialize PeerSettings - if c.PeerSettingsByInterfaceID(id) == nil { - - c.PeerSettings = append(c.PeerSettings, &structs.PeerSettings{ - InterfaceID: id, - RoutingRules: &structs.RoutingRules{ - AllowedIPs: []string{}, - }, - }) - - } - - // Initialize RoutingRules, if necessary - peer := c.PeerSettingsByInterfaceID(id) - if peer.RoutingRules == nil { - peer.RoutingRules = &structs.RoutingRules{AllowedIPs: []string{}} - } - } - - // Make sure both peer interfaces exist - ifaces := []*structs.Interface{} - for _, id := range connectedInterfaceIDs { - if iface, err := s.state.InterfaceByID(ctx, id); err == nil { - ifaces = append(ifaces, iface) - continue - } - return structs.NewInternalError(fmt.Sprintf("Interface %s does not exist", id)) - } - - if ifaces[0].NetworkID != ifaces[1].NetworkID { - return structs.NewInternalError("Interfaces are not in the same network") - } - - // Assign network ID in case we're creating a new connection - c.NetworkID = ifaces[0].NetworkID - - c.UpdatedAt = time.Now() - - // TODO: wrap in a transaction - - for _, iface := range ifaces { - iface.UpsertConnection((c.ID)) - if err = s.state.UpsertInterface(ctx, iface); err != nil { - return structs.ErrInternal - } - - if node, err := s.state.NodeByID(ctx, iface.NodeID); err == nil { - - c.PeerSettingsByInterfaceID(iface.ID).NodeID = iface.NodeID - - node.UpsertConnection((c.ID)) - if err = s.state.UpsertNode(ctx, node); err != nil { - return structs.ErrInternal - } - } - } - - if network, err := s.state.NetworkByID(ctx, c.NetworkID); err == nil { - network.UpsertConnection((c.ID)) - if err = s.state.UpsertNetwork(ctx, network); err != nil { - return structs.ErrInternal - } - } - - err = s.state.UpsertConnection(ctx, c) - if err != nil { - return structs.ErrInternal - } - - return nil -} - -// DeleteConnection deletes a connection entity from the repository -func (s *ConnectionService) DeleteConnection(args *structs.ConnectionDeleteRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "connection", "", ConnectionWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - for _, connID := range args.ConnectionIDs { - if conn, err := s.state.ConnectionByID(ctx, connID); err == nil { - - var nodes []*structs.Node - var ifaces []*structs.Interface - var network *structs.Network - - for _, nodeID := range conn.ConnectedNodeIDs() { - if node, err := s.state.NodeByID(ctx, nodeID); err == nil { - nodes = append(nodes, node) - } - } - - for _, ifaceID := range conn.ConnectedInterfaceIDs() { - if iface, err := s.state.InterfaceByID(ctx, ifaceID); err == nil { - ifaces = append(ifaces, iface) - } - } - - if network, err = s.state.NetworkByID(ctx, conn.NetworkID); err != nil { - return structs.NewInternalError(err.Error()) - } - - for _, node := range nodes { - node.RemoveConnection(connID) - if err := s.state.UpsertNode(ctx, node); err != nil { - return structs.ErrInternal // could not update node - } - } - - for _, iface := range ifaces { - iface.RemoveConnection(connID) - if err = s.state.UpsertInterface(ctx, iface); err != nil { - return structs.ErrInternal // could not update interface - } - } - - network.RemoveConnection(connID) - if err := s.state.UpsertNetwork(ctx, network); err != nil { - return structs.ErrInternal // could not update network - } - } - } - - // Remove connections - if err := s.state.DeleteConnections(ctx, args.ConnectionIDs); err != nil { - return structs.ErrInternal - } - - return nil -} diff --git a/drago/interface.go b/drago/interface.go deleted file mode 100644 index ddfe9c0..0000000 --- a/drago/interface.go +++ /dev/null @@ -1,247 +0,0 @@ -package drago - -import ( - "context" - "time" - - auth "github.com/seashell/drago/drago/auth" - state "github.com/seashell/drago/drago/state" - structs "github.com/seashell/drago/drago/structs" - log "github.com/seashell/drago/pkg/log" - uuid "github.com/seashell/drago/pkg/uuid" -) - -const ( - InterfaceList = "list" - InterfaceRead = "read" - InterfaceWrite = "write" -) - -type InterfaceService struct { - config *Config - logger log.Logger - state state.Repository - authHandler auth.AuthorizationHandler -} - -// NewInterfaceService ... -func NewInterfaceService(config *Config, logger log.Logger, state state.Repository, authHandler auth.AuthorizationHandler) *InterfaceService { - return &InterfaceService{ - config: config, - logger: logger, - state: state, - authHandler: authHandler, - } -} - -// GetInterface returns an Interface entity by ID -func (s *InterfaceService) GetInterface(args *structs.InterfaceSpecificRequest, out *structs.SingleInterfaceResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "interface", args.InterfaceID, InterfaceRead); err != nil { - return structs.ErrPermissionDenied - } - } - - n, err := s.state.InterfaceByID(ctx, args.InterfaceID) - if err != nil { - return structs.ErrNotFound - } - - out.Interface = n - - return nil -} - -// ListInterfaces retrieves all interface entities in the repository -func (s *InterfaceService) ListInterfaces(args *structs.InterfaceListRequest, out *structs.InterfaceListResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "interface", "", InterfaceList); err != nil { - return structs.ErrPermissionDenied - } - } - - out.Items = nil - - var err error - var interfaces []*structs.Interface - - if args.NodeID != "" { - if interfaces, err = s.state.InterfacesByNodeID(ctx, args.NodeID); err != nil { - return structs.ErrInternal - } - } else if args.NetworkID != "" { - if interfaces, err = s.state.InterfacesByNetworkID(ctx, args.NetworkID); err != nil { - return structs.ErrInternal - } - } else { - if interfaces, err = s.state.Interfaces(ctx); err != nil { - return structs.ErrInternal - } - } - - for _, i := range interfaces { - if args.NetworkID != "" { - if i.NetworkID == args.NetworkID { - out.Items = append(out.Items, i.Stub()) - } - } else { - out.Items = append(out.Items, i.Stub()) - } - } - - return nil -} - -// UpsertInterface upserts a new Interface entity, which results in a node being added to a network -func (s *InterfaceService) UpsertInterface(args *structs.InterfaceUpsertRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "interface", "", InterfaceWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - i := args.Interface - - // Make sure the input is valid - err := i.Validate() - if err != nil { - return structs.NewInvalidInputError(err.Error()) - } - - // If the interface already exists, we simply merge the new values into the existing struct. - // Otherwise, we generate a new ID and set the protected attributes in preparation for inserting - // the new struct into the repository. - if i.ID != "" { - old, err := s.state.InterfaceByID(ctx, i.ID) - if err != nil { - return structs.ErrNotFound // interface does not exist - } - i = old.Merge(i) - } else { - i.ID = uuid.Generate() - i.Name = nil // Setting name is responsibility of the client node - i.Address = nil // TODO: set with leasing plugin if it is loaded and enabled - i.Peers = []*structs.Peer{} // TODO: set with meshing plugin if it is loaded and enabled - i.CreatedAt = time.Now() - } - - // Retrieve the network to which the interface is meant to be added, throwing an error if it does not exist - network, err := s.state.NetworkByID(ctx, i.NetworkID) - if err != nil { - return structs.ErrInternal // network does not exist - } - - // Retrieve the node to which the interface is meant to be added, throwing an error if it does not exist - node, err := s.state.NodeByID(ctx, i.NodeID) - if err != nil { - return structs.ErrInternal // node does not exist - } - - // Retrieve the already existing interfaces of the targeted node - nodeInterfaces, err := s.state.InterfacesByNodeID(ctx, node.ID) - if err != nil { - return structs.ErrInternal // error getting node interfaces - } - - // Make sure that the node will not end up with two interfaces in the same network - for _, iface := range nodeInterfaces { - if iface.NetworkID == network.ID && i.ID != iface.ID { - return structs.NewInternalError("Network already joined") - } - } - - i.UpdatedAt = time.Now() - - // TODO: wrap in a transaction - - node.UpsertInterface(i.ID) - err = s.state.UpsertNode(ctx, node) - if err != nil { - return structs.ErrInternal // could not update network with the new interface - } - - network.UpsertInterface(i.ID) - err = s.state.UpsertNetwork(ctx, network) - if err != nil { - return structs.ErrInternal // could not update network with the new interface - } - - err = s.state.UpsertInterface(ctx, i) - if err != nil { - return structs.ErrInternal // could not create interface - } - - return nil -} - -// DeleteInterface deletes an interface entity from the repository -func (s *InterfaceService) DeleteInterface(args *structs.InterfaceDeleteRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "interface", "", InterfaceWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - for _, id := range args.InterfaceIDs { - if iface, err := s.state.InterfaceByID(ctx, id); err == nil { - - network, err := s.state.NetworkByID(ctx, iface.NetworkID) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - node, err := s.state.NodeByID(ctx, iface.NodeID) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - connections, err := s.state.ConnectionsByInterfaceID(ctx, iface.ID) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - connectionIDs := []string{} - for _, c := range connections { - connectionIDs = append(connectionIDs, c.ID) - network.RemoveConnection(c.ID) - } - - network.RemoveInterface(iface.ID) - node.RemoveInterface(iface.ID) - - if err := s.state.DeleteConnections(ctx, connectionIDs); err != nil { - return structs.NewInternalError(err.Error()) - } - - if err := s.state.UpsertNetwork(ctx, network); err != nil { - return structs.NewInternalError(err.Error()) - } - - if err := s.state.UpsertNode(ctx, node); err != nil { - return structs.NewInternalError(err.Error()) - } - } - } - - if err := s.state.DeleteInterfaces(ctx, args.InterfaceIDs); err != nil { - return structs.ErrInternal - } - - return nil -} diff --git a/drago/mock/mock.go b/drago/mock/mock.go deleted file mode 100644 index fda21bc..0000000 --- a/drago/mock/mock.go +++ /dev/null @@ -1,102 +0,0 @@ -package mock - -import ( - "context" - - "github.com/seashell/drago/drago/state" - "github.com/seashell/drago/drago/structs" - "github.com/seashell/drago/pkg/util" - "github.com/seashell/drago/pkg/uuid" -) - -// AuthHandler : -type AuthHandler struct{} - -// Authorize : authorize anything -func (h *AuthHandler) Authorize() error { - return nil -} - -// PopulateRepository : -func PopulateRepository(repo state.Repository) error { - - ctx := context.TODO() - - nodeID1 := "8cbc8089-e294-3fab-9f79-84ea6700c431" - nodeID2 := "8dd4c160-3034-47f0-94b5-7de423d36424" - - repo.UpsertACLToken(ctx, &structs.ACLToken{ID: uuid.Generate(), Type: structs.ACLTokenTypeManagement, Name: "Bootstrap Token", Secret: "abc"}) - repo.UpsertACLToken(ctx, &structs.ACLToken{ID: uuid.Generate(), Type: structs.ACLTokenTypeClient, Name: "client-12343234", Secret: "xyz", Policies: []string{"read", "write"}}) - - // Create nodes - node1 := &structs.Node{ID: nodeID1, Name: "test-node", SecretID: "84dd48eb-5f4d-f8aa-6bb6-bed687d9ed56", Meta: map[string]string{"location": "uk"}, Status: structs.NodeStatusInit, AdvertiseAddress: "192.168.100.7"} - node2 := &structs.Node{ID: nodeID2, Name: "other-test-node", SecretID: "c9a1b3dc-bbc9-49dc-83ed-77ff8abb1f30", Meta: map[string]string{"location": "us"}, Status: structs.NodeStatusInit} - - // Create networks - net1 := &structs.Network{ID: "8579e9cc-787b-4e57-b37f-088ed4f491f2", Name: "network-1", AddressRange: "192.168.0.0/16"} - - // Populate repository with interfaces - ifaceID1 := "c01648a1-b675-455a-8e5b-29db18be6663" - ifaceID2 := "618969bc-60b8-4018-8bf4-d2f4fdce43ae" - - iface1 := &structs.Interface{ - ID: ifaceID1, - NodeID: nodeID1, - NetworkID: net1.ID, - Name: util.StrToPtr("wg0"), - PublicKey: util.StrToPtr("uNAObp9zCLkivCIv/mKvgNUVtgVRoDegtLnaGtVeQWo="), - Address: util.StrToPtr("192.168.0.200/24")} - iface2 := &structs.Interface{ - ID: ifaceID2, - NodeID: nodeID2, - NetworkID: net1.ID, - Name: util.StrToPtr("wg0"), - Address: util.StrToPtr("192.168.0.2/24")} - - conn1 := &structs.Connection{ - ID: "14b62335-ba2b-4a05-8c6d-29b4e11f86b6", - NetworkID: net1.ID, - PeerSettings: []*structs.PeerSettings{ - { - NodeID: iface1.NodeID, - InterfaceID: iface1.ID, - RoutingRules: &structs.RoutingRules{ - AllowedIPs: []string{}, - }, - }, - { - NodeID: iface2.NodeID, - InterfaceID: iface2.ID, - RoutingRules: &structs.RoutingRules{ - AllowedIPs: []string{}, - }, - }, - }, - } - - iface1.UpsertConnection(conn1.ID) - iface2.UpsertConnection(conn1.ID) - - node1.UpsertInterface(ifaceID1) - node2.UpsertInterface(ifaceID2) - - node1.UpsertConnection(conn1.ID) - node2.UpsertConnection(conn1.ID) - - net1.UpsertInterface(ifaceID1) - net1.UpsertInterface(ifaceID2) - net1.UpsertConnection(conn1.ID) - - // Commit to the repository - repo.UpsertNetwork(ctx, net1) - - repo.UpsertNode(ctx, node1) - repo.UpsertNode(ctx, node2) - - repo.UpsertInterface(ctx, iface1) - repo.UpsertInterface(ctx, iface2) - - repo.UpsertConnection(ctx, conn1) - - return nil -} diff --git a/drago/network.go b/drago/network.go deleted file mode 100644 index cdc34e4..0000000 --- a/drago/network.go +++ /dev/null @@ -1,188 +0,0 @@ -package drago - -import ( - "context" - "time" - - auth "github.com/seashell/drago/drago/auth" - state "github.com/seashell/drago/drago/state" - structs "github.com/seashell/drago/drago/structs" - log "github.com/seashell/drago/pkg/log" - uuid "github.com/seashell/drago/pkg/uuid" -) - -const ( - NetworkList = "list" - NetworkRead = "read" - NetworkWrite = "write" -) - -type NetworkService struct { - config *Config - logger log.Logger - state state.Repository - authHandler auth.AuthorizationHandler -} - -// NewNetworkService ... -func NewNetworkService(config *Config, logger log.Logger, state state.Repository, authHandler auth.AuthorizationHandler) *NetworkService { - return &NetworkService{ - config: config, - logger: logger, - state: state, - authHandler: authHandler, - } -} - -// GetNetwork returns a Network entity by ID -func (s *NetworkService) GetNetwork(args *structs.NetworkSpecificRequest, out *structs.SingleNetworkResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "network", args.NetworkID, NetworkRead); err != nil { - return structs.ErrPermissionDenied - } - } - - n, err := s.state.NetworkByID(ctx, args.NetworkID) - if err != nil { - return structs.ErrNotFound - } - - out.Network = n - - return nil -} - -// ListNetworks retrieves all network entities in the repository -func (s *NetworkService) ListNetworks(args *structs.NetworkListRequest, out *structs.NetworkListResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "network", "", NetworkList); err != nil { - return structs.ErrPermissionDenied - } - } - - networks, err := s.state.Networks(ctx) - if err != nil { - return structs.ErrInternal - } - - out.Items = nil - - for _, n := range networks { - out.Items = append(out.Items, n.Stub()) - } - - return nil -} - -// UpsertNetwork upserts a new Network entity -func (s *NetworkService) UpsertNetwork(args *structs.NetworkUpsertRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "network", "", NetworkWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - n := args.Network - - err := n.Validate() - if err != nil { - return structs.NewInvalidInputError(err.Error()) - } - - isNewNetwork := n.ID == "" - - if isNewNetwork { - n.ID = uuid.Generate() - n.CreatedAt = time.Now() - - networks, err := s.state.Networks(ctx) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - for _, net := range networks { - if net.Name == n.Name { - return structs.NewInvalidInputError("network name already in use") - } - } - - } else { - old, err := s.state.NetworkByID(ctx, n.ID) - if err != nil { - return structs.ErrNotFound - } - n = old.Merge(n) - } - - n.UpdatedAt = time.Now() - - err = s.state.UpsertNetwork(ctx, n) - if err != nil { - return structs.ErrInternal - } - - return nil -} - -// DeleteNetwork deletes a network entity from the repository -func (s *NetworkService) DeleteNetwork(args *structs.NetworkDeleteRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "network", "", NetworkWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - for _, id := range args.NetworkIDs { - - interfaces, err := s.state.InterfacesByNetworkID(ctx, id) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - connections, err := s.state.ConnectionsByNetworkID(ctx, id) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - connectionIDs := []string{} - interfaceIDs := []string{} - - for _, iface := range interfaces { - interfaceIDs = append(interfaceIDs, iface.ID) - } - - for _, conn := range connections { - connectionIDs = append(connectionIDs, conn.ID) - } - - if err := s.state.DeleteInterfaces(ctx, interfaceIDs); err != nil { - return structs.NewInternalError(err.Error()) - } - - if err := s.state.DeleteConnections(ctx, connectionIDs); err != nil { - return structs.NewInternalError(err.Error()) - } - } - - if err := s.state.DeleteNetworks(ctx, args.NetworkIDs); err != nil { - return structs.ErrInternal - } - - return nil -} diff --git a/drago/node.go b/drago/node.go deleted file mode 100644 index dd616dd..0000000 --- a/drago/node.go +++ /dev/null @@ -1,531 +0,0 @@ -package drago - -import ( - "context" - "fmt" - "path" - "strings" - "sync" - "time" - - auth "github.com/seashell/drago/drago/auth" - state "github.com/seashell/drago/drago/state" - structs "github.com/seashell/drago/drago/structs" - log "github.com/seashell/drago/pkg/log" - uuid "github.com/seashell/drago/pkg/uuid" -) - -const ( - NodeList = "list" - NodeRead = "read" - NodeWrite = "write" - - defaultExpectedHeartbeatInterval = 10 * time.Second -) - -type NodeService struct { - config *Config - logger log.Logger - state state.Repository - authHandler auth.AuthorizationHandler - heartbeatTimers map[string]*time.Timer - heartbeatTimersLock sync.Mutex -} - -// NewNodeService ... -func NewNodeService(config *Config, logger log.Logger, state state.Repository, authHandler auth.AuthorizationHandler) (*NodeService, error) { - - s := &NodeService{ - config: config, - state: state, - authHandler: authHandler, - logger: logger, - heartbeatTimers: map[string]*time.Timer{}, - } - - err := s.setupHeartbeatTimers() - if err != nil { - return nil, fmt.Errorf("error setting up heartbeat timers: %v", err) - } - - return s, nil -} - -func (s *NodeService) setupHeartbeatTimers() error { - - ctx := context.TODO() - - nodes, err := s.state.Nodes(ctx) - if err != nil { - return fmt.Errorf("failed to retrieve nodes from state: %v", err) - } - - for _, n := range nodes { - s.resetHeartbeatTimer(n.ID) - } - - return nil -} - -func (s *NodeService) resetHeartbeatTimer(id string) { - - s.heartbeatTimersLock.Lock() - defer s.heartbeatTimersLock.Unlock() - - if timer, ok := s.heartbeatTimers[id]; ok { - timer.Reset(defaultExpectedHeartbeatInterval) - return - } - - timer := time.AfterFunc(defaultExpectedHeartbeatInterval, func() { - - ctx := context.TODO() - - s.logger.Debugf("heartbeat missed by node %s", id) - - old, err := s.state.NodeByID(ctx, id) - if err != nil { - s.logger.Debugf("failed to set node status after heartbeat miss: %v", err) - } - - n := old.Merge(&structs.Node{ - ID: id, - Status: structs.NodeStatusDown, - }) - n.UpdatedAt = time.Now() - - if err := s.state.UpsertNode(ctx, n); err != nil { - s.logger.Debugf("failed to set node status after hearbeat miss: %v", err) - } - }) - - s.heartbeatTimers[id] = timer -} - -func (s *NodeService) Register(args *structs.NodeRegisterRequest, out *structs.NodeUpdateResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "node", "", NodeWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - err := args.Validate() - if err != nil { - return structs.NewInvalidInputError(err.Error()) - } - - n := args.Node - - if n.Status == "" { - n.Status = structs.NodeStatusInit - } - - if !structs.IsValidNodeStatus(n.Status) { - return structs.NewInvalidInputError(err.Error()) - } - - old, err := s.state.NodeByID(ctx, n.ID) - if err != nil { - s.logger.Debugf("registering a new node with id %s!", n.ID) - n.CreatedAt = time.Now() - } else { - s.logger.Debugf("node %s already registered.", n.ID) - if old != nil { - if args.Node.SecretID != old.SecretID { - return structs.NewInvalidInputError("Node secret does not match") - } - } - n = old.Merge(n) - } - - n.UpdatedAt = time.Now() - - err = s.state.UpsertNode(ctx, n) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - s.resetHeartbeatTimer(n.ID) - - return nil -} - -func (s *NodeService) UpdateStatus(args *structs.NodeUpdateStatusRequest, out *structs.NodeUpdateResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "node", args.NodeID, NodeWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - if args.NodeID == "" { - return structs.ErrInvalidInput - } - if !structs.IsValidNodeStatus(args.Status) { - return structs.NewInvalidInputError("Invalid node status") - } - - n, err := s.state.NodeByID(ctx, args.NodeID) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - n.Status = args.Status - n.AdvertiseAddress = args.AdvertiseAddress - - if args.Meta != nil { - n.Meta = args.Meta - } - - n.UpdatedAt = time.Now() - - err = s.state.UpsertNode(ctx, n) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - out.Servers = []string{s.config.RPCAdvertiseAddr} - - s.logger.Debugf("heartbeat from node %s", n.ID) - s.resetHeartbeatTimer(n.ID) - - return nil -} - -// GetInterfaces : -func (s *NodeService) GetInterfaces(args *structs.NodeSpecificRequest, out *structs.NodeInterfacesResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "node", args.NodeID, NodeRead); err != nil { - return structs.ErrPermissionDenied - } - } - - if args.NodeID == "" { - return structs.NewInvalidInputError("Missing NodeID") - } - - interfaces, err := s.state.InterfacesByNodeID(ctx, args.NodeID) - if err != nil { - return structs.ErrNotFound - } - - for _, iface := range interfaces { - - iface.Peers = []*structs.Peer{} - - connections, err := s.state.ConnectionsByInterfaceID(ctx, iface.ID) - if err != nil { - s.logger.Warnf("couldn't get connections for interface %s", iface.ID) - } - - for _, conn := range connections { - - ifaceSettings := conn.PeerSettingsByInterfaceID(iface.ID) - peerSettings := conn.OtherPeerSettingsByInterfaceID(iface.ID) - - if ifaceSettings == nil || peerSettings == nil { - s.logger.Warnf("couldn't get settings for connection") - } - - peerIface, err := s.state.InterfaceByID(ctx, peerSettings.InterfaceID) - if err != nil { - s.logger.Warnf("couldn't get peer interface %s", peerSettings.InterfaceID) - } - - peerNode, err := s.state.NodeByID(ctx, peerIface.NodeID) - if err != nil { - s.logger.Warnf("couldn't get peer node %s", peerIface.NodeID) - } - - peer := &structs.Peer{ - PublicKey: peerIface.PublicKey, - Address: &peerNode.AdvertiseAddress, - Port: peerIface.ListenPort, - AllowedIPs: []string{}, - PersistentKeepalive: conn.PersistentKeepalive, - } - - if ifaceSettings.RoutingRules != nil { - peer.AllowedIPs = ifaceSettings.RoutingRules.AllowedIPs - } - - iface.Peers = append(iface.Peers, peer) - - } - } - - out.Items = append(out.Items, interfaces...) - - return nil -} - -// UpdateInterfaces : -func (s *NodeService) UpdateInterfaces(args *structs.NodeInterfaceUpdateRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "node", "", NodeWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - node, err := s.state.NodeByID(ctx, args.NodeID) - if err != nil { - return structs.ErrNotFound - } - - nodeInterfaces, err := s.state.InterfacesByNodeID(ctx, node.ID) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - // Create a map for more efficient lookup - nodeInterfacesMap := map[string]*structs.Interface{} - for _, i := range nodeInterfaces { - nodeInterfacesMap[i.ID] = i - } - - for _, i := range args.Interfaces { - old, found := nodeInterfacesMap[i.ID] - if !found { - return structs.NewInternalError("Interface does not belong to node") - } - - i = old.Merge(i) - i.UpdatedAt = time.Now() - - err := s.state.UpsertInterface(ctx, i) - if err != nil { - return structs.NewInternalError("Can't update interface") - } - } - - return nil -} - -// GetNode returns a Node entity by ID -func (s *NodeService) GetNode(args *structs.NodeSpecificRequest, out *structs.SingleNodeResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "network", args.NodeID, NodeRead); err != nil { - return structs.ErrPermissionDenied - } - } - - n, err := s.state.NodeByID(ctx, args.NodeID) - if err != nil { - return structs.ErrNotFound - } - - out.Node = n - - return nil -} - -// ListNodes retrieves all node entities in the repository -func (s *NodeService) ListNodes(args *structs.NodeListRequest, out *structs.NodeListResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "node", "", NodeList); err != nil { - return structs.ErrPermissionDenied - } - } - - nodes, err := s.state.Nodes(ctx) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - out.Items = nil - - for _, n := range nodes { - out.Items = append(out.Items, n.Stub()) - } - - out.Items = filterNodes(out.Items, args.Filters) - - return nil -} - -// JoinNetwork : connects a node to a network -func (s *NetworkService) JoinNetwork(args *structs.NodeJoinNetworkRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "node", args.NodeID, NodeWrite); err != nil { - return structs.ErrPermissionDenied - } - if err := s.authHandler.Authorize(ctx, args.AuthToken, "network", args.NetworkID, NetworkWrite); err != nil { - return structs.ErrPermissionDenied - } - } - - network, err := s.state.NetworkByID(ctx, args.NetworkID) - if err != nil { - return structs.ErrNotFound // network not found - } - - node, err := s.state.NodeByID(ctx, args.NodeID) - if err != nil { - return structs.ErrNotFound // node not found - } - - interfaces, err := s.state.InterfacesByNodeID(ctx, node.ID) - if err != nil { - return structs.NewInternalError(err.Error()) - } - - // Check whether node has already joined the network - for _, iface := range interfaces { - if iface.NetworkID == network.ID { - return structs.NewInternalError("Network already joined") - } - } - - iface := &structs.Interface{ - ID: uuid.Generate(), - NodeID: node.ID, - NetworkID: network.ID, - Name: nil, // Setting name is responsibility of the client node - Address: nil, // TODO: set with leasing plugin if it is loaded and enabled - Peers: []*structs.Peer{}, // TODO: set with meshing plugin if it is loaded and enabled - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - err = s.state.UpsertInterface(ctx, iface) - if err != nil { - return structs.NewInternalError("Can't create interface") - } - - node.UpsertInterface(iface.ID) - err = s.state.UpsertNode(ctx, node) - if err != nil { - return structs.NewInternalError("Can't add interface to node") - } - - network.UpsertInterface(iface.ID) - err = s.state.UpsertNetwork(ctx, network) - if err != nil { - return structs.NewInternalError("Can't add interface to network") - } - - return nil -} - -// LeaveNetwork : disconnects a node from a network -func (s *NetworkService) LeaveNetwork(args *structs.NodeLeaveNetworkRequest, out *structs.GenericResponse) error { - - ctx := context.TODO() - - // Check if authorized - if s.config.ACL.Enabled { - if err := s.authHandler.Authorize(ctx, args.AuthToken, "node", args.NodeID, NodeList); err != nil { - return structs.ErrPermissionDenied - } - if err := s.authHandler.Authorize(ctx, args.AuthToken, "network", args.NetworkID, NodeList); err != nil { - return structs.ErrPermissionDenied - } - } - - network, err := s.state.NetworkByID(ctx, args.NodeID) - if err != nil { - return structs.NewInternalError("Network does not exist") - } - - node, err := s.state.NodeByID(ctx, args.NodeID) - if err != nil { - return structs.NewInternalError("Node does not exist") - } - - interfaces, err := s.state.InterfacesByNodeID(ctx, node.ID) - if err != nil { - return structs.NewInternalError("Can't retrieve node interfaces") - } - - // Check whether node is in the network - for _, iface := range interfaces { - if iface.NetworkID == network.ID { - - network.RemoveInterface(iface.ID) - if err := s.state.UpsertNetwork(ctx, network); err != nil { - return structs.NewInternalError("Can't update network") - } - - node.RemoveInterface(iface.ID) - if err := s.state.UpsertNode(ctx, node); err != nil { - return structs.NewInternalError("Can't update node") - } - - if err := s.state.DeleteInterfaces(ctx, []string{iface.ID}); err != nil { - return structs.NewInternalError("Can't delete interface") - } - } - } - - return nil -} - -func filterNodes(nodes []*structs.NodeListStub, filters structs.Filters) []*structs.NodeListStub { - - statusFilter := "*" - if len(filters.Get("status")) > 0 { - statusFilter = filters.Get("status")[0] - } - - metaFilters := map[string]string{} - for _, f := range filters.Get("meta") { - kv := strings.Split(f, ":") - if len(kv) != 2 { - continue - } - metaFilters[kv[0]] = kv[1] - } - - out := []*structs.NodeListStub{} - - for _, n := range nodes { - - matchStatusFilter := true - if matched, _ := path.Match(statusFilter, n.Status); !matched { - matchStatusFilter = false - } - - matchAllMetaFilters := true - for k, v := range metaFilters { - if metaValue, found := n.Meta[k]; found { - if matched, _ := path.Match(v, metaValue); !matched { - matchAllMetaFilters = false - } - continue - } - matchAllMetaFilters = false - } - if matchStatusFilter && matchAllMetaFilters { - out = append(out, n) - } - } - - return out -} diff --git a/drago/server.go b/drago/server.go deleted file mode 100644 index b31b613..0000000 --- a/drago/server.go +++ /dev/null @@ -1,311 +0,0 @@ -package drago - -import ( - "context" - "fmt" - "sync" - "time" - - auth "github.com/seashell/drago/drago/auth" - state "github.com/seashell/drago/drago/state" - "github.com/seashell/drago/drago/state/etcd" - structs "github.com/seashell/drago/drago/structs" - "github.com/seashell/drago/drago/structs/config" - acl "github.com/seashell/drago/pkg/acl" - log "github.com/seashell/drago/pkg/log" - rpc "github.com/seashell/drago/pkg/rpc" - "github.com/seashell/drago/pkg/uuid" -) - -var ( - // AnonymousACLToken is used when no secret is provided, - // and the request is made anonymously. - AnonymousACLToken = &structs.ACLToken{ - ID: "anonymous", - Name: "Anonymous Token", - Type: structs.ACLTokenTypeClient, - Policies: []string{"anonymous"}, - } -) - -// Server is the Drago server -type Server struct { - config *Config - - logger log.Logger - rpcServer *rpc.Server - rpcClient *rpc.Client - state state.Repository - authHandler auth.AuthorizationHandler - - services struct { - ACL *ACLService - Nodes *NodeService - Networks *NetworkService - Interfaces *InterfaceService - Connections *ConnectionService - Status *StatusService - } - - shutdown bool - shutdownCh chan struct{} - shutdownLock sync.Mutex -} - -// NewServer creates a new Drago server from the -// configuration, potentially returning an error -func NewServer(config *Config) (*Server, error) { - - s := &Server{ - config: config, - logger: config.Logger.WithName("server"), - shutdownCh: make(chan struct{}), - } - - var err error - - err = s.setupACLModel() - if err != nil { - s.logger.Errorf("Error setting up ACL model: %s", err.Error()) - return nil, err - } - - err = s.setupApplication() - if err != nil { - s.logger.Errorf("Error setting up application modules: %s", err.Error()) - return nil, err - } - - err = s.setupRPCServer() - if err != nil { - s.logger.Errorf("Error setting up rpc server: %s", err.Error()) - return nil, err - } - - return s, nil -} - -// Stats is used to return statistics for the server -func (s *Server) Stats() map[string]map[string]string { - - stats := map[string]map[string]string{ - "drago": { - "server": "true", - "peers": "[]", - }, - } - - return stats -} - -// Shutdown tears down the server -func (s *Server) Shutdown() error { - s.shutdownLock.Lock() - defer s.shutdownLock.Unlock() - - if s.shutdown { - s.logger.Infof("already shutdown") - return nil - } - s.logger.Infof("shutting down") - - //s.etcdServer.Close() - - s.shutdown = true - close(s.shutdownCh) - - return nil -} - -func (s *Server) setupApplication() error { - - state, err := etcd.NewStateRepository(&etcd.Config{ - DataDir: s.config.DataDir, - LogLevel: s.config.LogLevel, - EtcdConfig: config.EtcdConfig{ - Name: s.config.Etcd.Name, - ListenPeerURLs: s.config.Etcd.ListenPeerURLs, - ListenClientURLs: s.config.Etcd.ListenClientURLs, - InitialAdvertisePeerURLs: s.config.Etcd.InitialAdvertisePeerURLs, - InitialAdvertiseClientURLs: s.config.Etcd.InitialAdvertiseClientURLs, - InitialCluster: s.config.Etcd.InitialCluster, - InitialClusterState: s.config.Etcd.InitialClusterState, - ProxyModeEnabled: s.config.Etcd.ProxyModeEnabled, - CORS: s.config.Etcd.CORS, - }, - }) - if err != nil { - return err - } - s.state = state - - ctx := context.TODO() - - // Setup default network - if _, err := s.state.NetworkByName(ctx, "default"); err != nil { - s.logger.Infof("Creating default network...") - if s.state.UpsertNetwork(ctx, &structs.Network{ - ID: uuid.Generate(), - Name: "default", - AddressRange: "192.168.0.0/24", - CreatedAt: time.Now(), - UpdatedAt: time.Now()}) != nil { - s.logger.Warnf("Could not create default network") - } - } - - // Setup default policies - for _, p := range s.defaultACLPolicies() { - err := s.state.UpsertACLPolicy(ctx, p) - if err != nil { - return err - } - } - - s.authHandler = auth.NewAuthorizationHandler( - s.config.ACL.Model, - s.secretResolver(), - s.policyResolver(), - ) - - nodeService, err := NewNodeService(s.config, s.logger, s.state, s.authHandler) - if err != nil { - return fmt.Errorf("failed to create node service: %v", err) - } - - s.services.Nodes = nodeService - s.services.ACL = NewACLService(s.config, s.logger, s.state, s.authHandler) - s.services.Networks = NewNetworkService(s.config, s.logger, s.state, s.authHandler) - s.services.Interfaces = NewInterfaceService(s.config, s.logger, s.state, s.authHandler) - s.services.Connections = NewConnectionService(s.config, s.logger, s.state, s.authHandler) - - s.services.Status = NewStatusService(s.config, s.state, s.authHandler) - - return nil -} - -// setupACLModel defines an ACL model containing resource types, associated -// capabilities, and aliases which can be used by the application. -func (s *Server) setupACLModel() error { - - model := acl.NewModel() - - model.Resource("token"). - Capabilities(ACLTokenWrite, ACLTokenRead, ACLTokenList). - Alias("read", ACLTokenRead, ACLTokenList). - Alias("write", ACLTokenWrite, ACLTokenRead, ACLTokenList) - - model.Resource("policy"). - Capabilities(ACLPolicyWrite, ACLPolicyRead, ACLPolicyList). - Alias("read", ACLPolicyRead, ACLPolicyList). - Alias("write", ACLPolicyWrite, ACLPolicyRead, ACLPolicyList) - - model.Resource("network"). - Capabilities(NetworkWrite, NetworkRead, NetworkList). - Alias("read", NetworkRead, NetworkList). - Alias("write", NetworkWrite, NetworkRead, NetworkList) - - model.Resource("node"). - Capabilities(NodeWrite, NodeRead, NodeList). - Alias("read", NodeRead, NodeList). - Alias("write", NodeWrite, NodeRead, NodeList) - - model.Resource("interface"). - Capabilities(InterfaceWrite, InterfaceRead, InterfaceList). - Alias("read", InterfaceRead, InterfaceList). - Alias("write", InterfaceWrite, InterfaceRead, InterfaceList) - - model.Resource("connection"). - Capabilities(ConnectionWrite, ConnectionRead, ConnectionList). - Alias("read", ConnectionRead, ConnectionList). - Alias("write", ConnectionWrite, ConnectionRead, ConnectionList) - - s.config.ACL.Model = model - - return nil -} - -// returns the ACL policies to be loaded by default into the ACL when the Drago server starts. -func (s *Server) defaultACLPolicies() []*structs.ACLPolicy { - return []*structs.ACLPolicy{ - { - Name: "anonymous", - Description: "Default policy utilized when no access token is provided", - Rules: []*structs.ACLPolicyRule{ - {Resource: "token", Path: "*", Capabilities: []string{ACLTokenList}}, - {Resource: "policy", Path: "*", Capabilities: []string{ACLPolicyList}}, - {Resource: "network", Path: "*", Capabilities: []string{NetworkList}}, - {Resource: "node", Path: "*", Capabilities: []string{NodeList}}, - {Resource: "interface", Path: "*", Capabilities: []string{InterfaceList}}, - {Resource: "connection", Path: "*", Capabilities: []string{ConnectionList}}, - }, - }, - } -} - -// returns an acl.SecretResolverFunc -func (s *Server) secretResolver() acl.SecretResolverFunc { - return func(ctx context.Context, secret string) (acl.Token, error) { - - var err error - var t *structs.ACLToken - - if secret == "" { - t = AnonymousACLToken - } else { - t, err = s.state.ACLTokenBySecret(ctx, secret) - if err != nil { - return nil, err - } - if t == nil { - return nil, fmt.Errorf("token not found") - } - } - - return auth.NewToken( - t.Type == structs.ACLTokenTypeManagement, - t.Policies, - ), nil - } -} - -// returns an acl.SecretResolverFunc -func (s *Server) policyResolver() acl.PolicyResolverFunc { - return func(ctx context.Context, policy string) (acl.Policy, error) { - pol, err := s.state.ACLPolicyByName(ctx, policy) - if err != nil { - return nil, err - } - - res := auth.NewPolicy(pol.Name, []acl.Rule{}) - for _, r := range pol.Rules { - res.AddRule(auth.NewRule(r.Resource, r.Path, r.Capabilities)) - } - return res, nil - } -} - -func (s *Server) setupRPCServer() error { - - config := &rpc.ServerConfig{ - Logger: s.logger, - BindAddress: fmt.Sprintf("%s:%d", s.config.BindAddr, s.config.Ports.RPC), - Receivers: map[string]interface{}{ - "ACL": s.services.ACL, - "Node": s.services.Nodes, - "Interface": s.services.Interfaces, - "Connection": s.services.Connections, - "Network": s.services.Networks, - "Status": s.services.Status, - }, - } - - rpcServer, err := rpc.NewServer(config) - if err != nil { - return err - } - - s.rpcServer = rpcServer - - return nil -} diff --git a/drago/state/etcd/acl_policy.go b/drago/state/etcd/acl_policy.go deleted file mode 100644 index fa68dcc..0000000 --- a/drago/state/etcd/acl_policy.go +++ /dev/null @@ -1,80 +0,0 @@ -package etcd - -import ( - "context" - "errors" - - "github.com/seashell/drago/drago/structs" - "go.etcd.io/etcd/clientv3" -) - -// ACLPolicies : -func (r *StateRepository) ACLPolicies(ctx context.Context) ([]*structs.ACLPolicy, error) { - - prefix := resourceKey(resourceTypeACLPolicy, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.ACLPolicy{} - - for _, el := range res.Kvs { - policy := &structs.ACLPolicy{} - err := decodeValue(el.Value, policy) - if err != nil { - return nil, err - } - items = append(items, policy) - } - - return items, nil -} - -// ACLPolicyByName : -func (r *StateRepository) ACLPolicyByName(ctx context.Context, name string) (*structs.ACLPolicy, error) { - - key := resourceKey(resourceTypeACLPolicy, name) - - res, err := r.client.Get(ctx, key) - if err != nil { - return nil, err - } - - if res.Count == 0 { - return nil, errors.New("not found") - } - - policy := &structs.ACLPolicy{} - - err = decodeValue(res.Kvs[0].Value, policy) - if err != nil { - return nil, err - } - - return policy, nil -} - -// UpsertACLPolicy : -func (r *StateRepository) UpsertACLPolicy(ctx context.Context, p *structs.ACLPolicy) error { - key := resourceKey(resourceTypeACLPolicy, p.Name) - - _, err := r.client.Put(ctx, key, encodeValue(p)) - if err != nil { - return err - } - return nil -} - -// DeleteACLPolicies : -func (r *StateRepository) DeleteACLPolicies(ctx context.Context, names []string) error { - for _, name := range names { - key := resourceKey(resourceTypeACLPolicy, name) - _, err := r.client.Delete(ctx, key) - if err != nil { - return err - } - } - return nil -} diff --git a/drago/state/etcd/acl_state.go b/drago/state/etcd/acl_state.go deleted file mode 100644 index 1aadcc7..0000000 --- a/drago/state/etcd/acl_state.go +++ /dev/null @@ -1,50 +0,0 @@ -package etcd - -import ( - "context" - "errors" - "fmt" - - "github.com/seashell/drago/drago/structs" -) - -// ACLState : -func (r *StateRepository) ACLState(ctx context.Context) (*structs.ACLState, error) { - - key := aclStateKey() - - res, err := r.client.Get(ctx, key) - if err != nil { - return nil, err - } - - if res.Count == 0 { - return nil, errors.New("not found") - } - - state := &structs.ACLState{} - - err = decodeValue(res.Kvs[0].Value, state) - if err != nil { - return nil, err - } - - return state, nil -} - -// ACLSetState : -func (r *StateRepository) ACLSetState(ctx context.Context, s *structs.ACLState) error { - - key := aclStateKey() - - _, err := r.client.Put(ctx, key, encodeValue(s)) - if err != nil { - return err - } - - return nil -} - -func aclStateKey() string { - return fmt.Sprintf("%s/%s/global/%s", defaultPrefix, "acl", "state") -} diff --git a/drago/state/etcd/acl_token.go b/drago/state/etcd/acl_token.go deleted file mode 100644 index 8e14e65..0000000 --- a/drago/state/etcd/acl_token.go +++ /dev/null @@ -1,110 +0,0 @@ -package etcd - -import ( - "context" - "errors" - - "github.com/seashell/drago/drago/structs" - "go.etcd.io/etcd/clientv3" -) - -const ( - resourceTypeToken = "token" -) - -// ACLTokens : -func (r *StateRepository) ACLTokens(ctx context.Context) ([]*structs.ACLToken, error) { - - prefix := resourceKey(resourceTypeToken, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.ACLToken{} - - for _, el := range res.Kvs { - token := &structs.ACLToken{} - err := decodeValue(el.Value, token) - if err != nil { - return nil, err - } - items = append(items, token) - } - - return items, nil -} - -// ACLTokenByID : -func (r *StateRepository) ACLTokenByID(ctx context.Context, id string) (*structs.ACLToken, error) { - - key := resourceKey(resourceTypeToken, id) - - res, err := r.client.Get(ctx, key) - if err != nil { - return nil, err - } - - if res.Count == 0 { - return nil, errors.New("not found") - } - - token := &structs.ACLToken{} - - err = decodeValue(res.Kvs[0].Value, token) - if err != nil { - return nil, err - } - - return token, nil -} - -// ACLTokenBySecret : -func (r *StateRepository) ACLTokenBySecret(ctx context.Context, secret string) (*structs.ACLToken, error) { - - prefix := resourceKey(resourceTypeToken, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix()) - if err != nil { - return nil, err - } - - for _, el := range res.Kvs { - - token := &structs.ACLToken{} - - err := decodeValue(el.Value, token) - if err != nil { - return nil, err - } - - if token.Secret == secret { - return token, nil - } - } - - return nil, nil -} - -// UpsertACLToken : -func (r *StateRepository) UpsertACLToken(ctx context.Context, t *structs.ACLToken) error { - key := resourceKey(resourceTypeToken, t.ID) - _, err := r.client.Put(ctx, key, encodeValue(t)) - if err != nil { - return err - } - return nil -} - -// DeleteACLTokens : -func (r *StateRepository) DeleteACLTokens(ctx context.Context, ids []string) error { - for _, id := range ids { - key := resourceKey(resourceTypeToken, id) - _, err := r.client.Delete(ctx, key) - if err != nil { - return err - } - } - return nil -} diff --git a/drago/state/etcd/connection.go b/drago/state/etcd/connection.go deleted file mode 100644 index 77c4b36..0000000 --- a/drago/state/etcd/connection.go +++ /dev/null @@ -1,184 +0,0 @@ -package etcd - -import ( - "context" - "errors" - "fmt" - - structs "github.com/seashell/drago/drago/structs" - "go.etcd.io/etcd/clientv3" -) - -// Connections : -func (r *StateRepository) Connections(ctx context.Context) ([]*structs.Connection, error) { - - prefix := resourceKey(resourceTypeConnection, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Connection{} - - for _, el := range res.Kvs { - conn := &structs.Connection{} - err := decodeValue(el.Value, conn) - if err != nil { - return nil, err - } - items = append(items, conn) - } - - return items, nil -} - -// ConnectionByID : -func (r *StateRepository) ConnectionByID(ctx context.Context, id string) (*structs.Connection, error) { - - key := resourceKey(resourceTypeConnection, id) - - res, err := r.client.Get(ctx, key) - if err != nil { - return nil, err - } - - if res.Count == 0 { - return nil, errors.New("not found") - } - - network := &structs.Connection{} - - err = decodeValue(res.Kvs[0].Value, network) - if err != nil { - return nil, err - } - - return network, nil -} - -// ConnectionsByNetworkID : -func (r *StateRepository) ConnectionsByNetworkID(ctx context.Context, id string) ([]*structs.Connection, error) { - - prefix := resourceKey(resourceTypeConnection, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Connection{} - - for _, el := range res.Kvs { - conn := &structs.Connection{} - if err := decodeValue(el.Value, conn); err != nil { - return nil, err - } - if conn.NetworkID == id { - items = append(items, conn) - } - } - - return items, nil -} - -// ConnectionsByNodeID : -func (r *StateRepository) ConnectionsByNodeID(ctx context.Context, id string) ([]*structs.Connection, error) { - - prefix := resourceKey(resourceTypeConnection, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Connection{} - - for _, el := range res.Kvs { - conn := &structs.Connection{} - if err := decodeValue(el.Value, conn); err != nil { - return nil, err - } - - if conn.PeerSettings[0].NodeID == id || conn.PeerSettings[1].NodeID == id { - items = append(items, conn) - } - } - - return items, nil -} - -// ConnectionsByInterfaceID : -func (r *StateRepository) ConnectionsByInterfaceID(ctx context.Context, id string) ([]*structs.Connection, error) { - - prefix := resourceKey(resourceTypeConnection, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Connection{} - - for _, el := range res.Kvs { - conn := &structs.Connection{} - if err := decodeValue(el.Value, conn); err != nil { - return nil, err - } - - if conn.ConnectsInterface(id) { - items = append(items, conn) - } - } - - return items, nil -} - -// ConnectionByInterfaceIDs : -func (r *StateRepository) ConnectionByInterfaceIDs(ctx context.Context, a, b string) (*structs.Connection, error) { - - prefix := resourceKey(resourceTypeConnection, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - for _, el := range res.Kvs { - conn := &structs.Connection{} - if err := decodeValue(el.Value, conn); err != nil { - return nil, err - } - - if conn.ConnectsInterfaces(a, b) { - return conn, nil - } - } - - return nil, fmt.Errorf("not found") -} - -// UpsertConnection : -func (r *StateRepository) UpsertConnection(ctx context.Context, n *structs.Connection) error { - key := resourceKey(resourceTypeConnection, n.ID) - - _, err := r.client.Put(ctx, key, encodeValue(n)) - if err != nil { - return err - } - return nil -} - -// DeleteConnections : -func (r *StateRepository) DeleteConnections(ctx context.Context, ids []string) error { - - for _, id := range ids { - key := resourceKey(resourceTypeConnection, id) - _, err := r.client.Delete(ctx, key) - if err != nil { - return err - } - } - - return nil -} diff --git a/drago/state/etcd/etcd.go b/drago/state/etcd/etcd.go deleted file mode 100644 index 2696e41..0000000 --- a/drago/state/etcd/etcd.go +++ /dev/null @@ -1,182 +0,0 @@ -package etcd - -import ( - "context" - "encoding/json" - "fmt" - "net/url" - "path" - "strings" - "time" - - "github.com/seashell/drago/drago/state" - "github.com/seashell/drago/drago/structs/config" - "go.etcd.io/etcd/clientv3" - "go.etcd.io/etcd/embed" -) - -const ( - defaultNamespace = "default" - defaultPrefix = "/registry" - - resourceTypeACLPolicy = "policy" - resourceTypeACLToken = "token" - resourceTypeNetwork = "network" - resourceTypeNode = "node" - resourceTypeInterface = "interface" - resourceTypeConnection = "connection" - - transactionContextKey = "etcdtxn" -) - -type Config struct { - DataDir string - LogLevel string - - config.EtcdConfig -} - -// StateRepository implements StateRepository -type StateRepository struct { - server *embed.Etcd - client *clientv3.Client - config *Config -} - -// NewStateRepository : -func NewStateRepository(config *Config) (*StateRepository, error) { - - r := &StateRepository{ - config: config, - } - - err := r.setupEtcdServer() - if err != nil { - return nil, fmt.Errorf("Error setting up etcd server: %s", err.Error()) - } - - err = r.setupEtcdClient() - if err != nil { - return nil, fmt.Errorf("Error setting up etcd client: %s", err.Error()) - } - - return r, nil -} - -// Name returns the name identifying the state repository. -func (r *StateRepository) Name() string { - return "etcd" -} - -type transaction struct { - txn clientv3.Txn -} - -func (t transaction) Commit() (interface{}, error) { - out, err := t.txn.Commit() - return out, err -} - -// Transaction -func (r *StateRepository) Transaction(ctx context.Context) state.Transaction { - return transaction{r.client.Txn(ctx)} -} - -func (r *StateRepository) setupEtcdClient() error { - etcdClient, err := clientv3.New(clientv3.Config{ - Endpoints: r.config.InitialAdvertiseClientURLs, - AutoSyncInterval: time.Second * 5, - DialTimeout: 5 * time.Second, - }) - if err != nil { - return err - } - - r.client = etcdClient - - return nil -} - -func (r *StateRepository) setupEtcdServer() error { - - cfg := embed.NewConfig() - - // Advertise peer URLs - apURLs, err := parseUrls(r.config.InitialAdvertisePeerURLs) - if err != nil { - return err - } - - // Listen peer URLs - lpURLs, err := parseUrls(r.config.ListenPeerURLs) - if err != nil { - return err - } - - // Advertise client URLs - acURLs, err := parseUrls(r.config.InitialAdvertiseClientURLs) - if err != nil { - return err - } - - // Listen client URLs - lcURLs, err := parseUrls(r.config.ListenClientURLs) - if err != nil { - return err - } - - cfg.Name = r.config.Name - cfg.Dir = path.Join(r.config.DataDir, "/etcd") - cfg.WalDir = path.Join(r.config.DataDir, "/etcd", "/wal") - cfg.Logger = "zap" - - cfg.APUrls = apURLs - cfg.LPUrls = lpURLs - cfg.ACUrls = acURLs - cfg.LCUrls = lcURLs - - cfg.LogOutputs = []string{"stderr", path.Join(r.config.DataDir, "/etcd.log")} - cfg.LogLevel = strings.ToLower(r.config.LogLevel) - - etcdServer, err := embed.StartEtcd(cfg) - if err != nil { - return err - } - - r.server = etcdServer - - return nil -} - -func strToPtr(s string) *string { - return &s -} - -func resourceKey(resourceType, resourceID string) string { - key := fmt.Sprintf("%s/%s/%s/%s", defaultPrefix, resourceType, defaultNamespace, resourceID) - return key -} - -func encodeValue(in interface{}) string { - encoded, err := json.Marshal(in) - if err != nil { - panic(err) - } - return string(encoded) -} - -func decodeValue(data []byte, out interface{}) error { - return json.Unmarshal(data, out) -} - -func parseUrls(urls []string) ([]url.URL, error) { - res := []url.URL{} - for _, v := range urls { - url, err := url.Parse(v) - if err != nil { - return nil, err - } - res = append(res, *url) - } - return res, nil -} diff --git a/drago/state/etcd/interface.go b/drago/state/etcd/interface.go deleted file mode 100644 index 6711a58..0000000 --- a/drago/state/etcd/interface.go +++ /dev/null @@ -1,128 +0,0 @@ -package etcd - -import ( - "context" - "errors" - - structs "github.com/seashell/drago/drago/structs" - "go.etcd.io/etcd/clientv3" -) - -// Interfaces : -func (r *StateRepository) Interfaces(ctx context.Context) ([]*structs.Interface, error) { - - prefix := resourceKey(resourceTypeInterface, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Interface{} - - for _, el := range res.Kvs { - iface := &structs.Interface{} - if err := decodeValue(el.Value, iface); err != nil { - return nil, err - } - items = append(items, iface) - } - - return items, nil -} - -// InterfacesByNodeID : -func (r *StateRepository) InterfacesByNodeID(ctx context.Context, id string) ([]*structs.Interface, error) { - - prefix := resourceKey(resourceTypeInterface, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Interface{} - - for _, el := range res.Kvs { - iface := &structs.Interface{} - if err := decodeValue(el.Value, iface); err != nil { - return nil, err - } - if iface.NodeID == id { - items = append(items, iface) - } - } - - return items, nil -} - -// InterfacesByNetworkID : -func (r *StateRepository) InterfacesByNetworkID(ctx context.Context, id string) ([]*structs.Interface, error) { - - prefix := resourceKey(resourceTypeInterface, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Interface{} - - for _, el := range res.Kvs { - iface := &structs.Interface{} - if err := decodeValue(el.Value, iface); err != nil { - return nil, err - } - - if iface.NetworkID == id { - items = append(items, iface) - } - } - - return items, nil -} - -// InterfaceByID : -func (r *StateRepository) InterfaceByID(ctx context.Context, id string) (*structs.Interface, error) { - - key := resourceKey(resourceTypeInterface, id) - - res, err := r.client.Get(ctx, key) - if err != nil { - return nil, err - } - - if res.Count == 0 { - return nil, errors.New("not found") - } - - iface := &structs.Interface{} - - if err = decodeValue(res.Kvs[0].Value, iface); err != nil { - return nil, err - } - - return iface, nil -} - -// UpsertInterface : -func (r *StateRepository) UpsertInterface(ctx context.Context, n *structs.Interface) error { - key := resourceKey(resourceTypeInterface, n.ID) - if _, err := r.client.Put(ctx, key, encodeValue(n)); err != nil { - return err - } - return nil -} - -// DeleteInterfaces : -func (r *StateRepository) DeleteInterfaces(ctx context.Context, ids []string) error { - - for _, id := range ids { - key := resourceKey(resourceTypeInterface, id) - if _, err := r.client.Delete(ctx, key); err != nil { - return err - } - } - - return nil -} diff --git a/drago/state/etcd/network.go b/drago/state/etcd/network.go deleted file mode 100644 index 5f33c02..0000000 --- a/drago/state/etcd/network.go +++ /dev/null @@ -1,101 +0,0 @@ -package etcd - -import ( - "context" - "errors" - "fmt" - - structs "github.com/seashell/drago/drago/structs" - "go.etcd.io/etcd/clientv3" -) - -// Networks : -func (r *StateRepository) Networks(ctx context.Context) ([]*structs.Network, error) { - - prefix := resourceKey(resourceTypeNetwork, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Network{} - - for _, el := range res.Kvs { - network := &structs.Network{} - if err := decodeValue(el.Value, network); err != nil { - return nil, err - } - items = append(items, network) - } - - return items, nil -} - -// NetworkByID : -func (r *StateRepository) NetworkByID(ctx context.Context, id string) (*structs.Network, error) { - - key := resourceKey(resourceTypeNetwork, id) - - res, err := r.client.Get(ctx, key) - if err != nil { - return nil, err - } - - if res.Count == 0 { - return nil, errors.New("not found") - } - - network := &structs.Network{} - if err = decodeValue(res.Kvs[0].Value, network); err != nil { - return nil, err - } - - return network, nil -} - -// NetworkByName : -func (r *StateRepository) NetworkByName(ctx context.Context, s string) (*structs.Network, error) { - - prefix := resourceKey(resourceTypeNetwork, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - for _, el := range res.Kvs { - network := &structs.Network{} - if err := decodeValue(el.Value, network); err != nil { - return nil, err - } - - if network.Name == s { - return network, nil - } - } - - return nil, fmt.Errorf("not found") -} - -// UpsertNetwork : -func (r *StateRepository) UpsertNetwork(ctx context.Context, n *structs.Network) error { - key := resourceKey(resourceTypeNetwork, n.ID) - if _, err := r.client.Put(ctx, key, encodeValue(n)); err != nil { - return err - } - return nil -} - -// DeleteNetworks : -func (r *StateRepository) DeleteNetworks(ctx context.Context, ids []string) error { - - for _, id := range ids { - key := resourceKey(resourceTypeNetwork, id) - if _, err := r.client.Delete(ctx, key); err != nil { - return err - } - } - - return nil -} diff --git a/drago/state/etcd/node.go b/drago/state/etcd/node.go deleted file mode 100644 index 749923b..0000000 --- a/drago/state/etcd/node.go +++ /dev/null @@ -1,102 +0,0 @@ -package etcd - -import ( - "context" - "errors" - "fmt" - - structs "github.com/seashell/drago/drago/structs" - "go.etcd.io/etcd/clientv3" -) - -// Nodes : -func (r *StateRepository) Nodes(ctx context.Context) ([]*structs.Node, error) { - - prefix := resourceKey(resourceTypeNode, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - items := []*structs.Node{} - - for _, el := range res.Kvs { - node := &structs.Node{} - if err := decodeValue(el.Value, node); err != nil { - return nil, err - } - items = append(items, node) - } - - return items, nil -} - -// NodeByID : -func (r *StateRepository) NodeByID(ctx context.Context, id string) (*structs.Node, error) { - - key := resourceKey(resourceTypeNode, id) - - res, err := r.client.Get(ctx, key) - if err != nil { - return nil, err - } - - if res.Count == 0 { - return nil, errors.New("not found") - } - - node := &structs.Node{} - - if err := decodeValue(res.Kvs[0].Value, node); err != nil { - return nil, err - } - - return node, nil -} - -// Nodes : -func (r *StateRepository) NodeBySecretID(ctx context.Context, s string) (*structs.Node, error) { - - prefix := resourceKey(resourceTypeNode, "") - - res, err := r.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByVersion, clientv3.SortDescend)) - if err != nil { - return nil, err - } - - for _, el := range res.Kvs { - node := &structs.Node{} - if err := decodeValue(el.Value, node); err != nil { - return nil, err - } - if node.SecretID == s { - return node, nil - } - } - - return nil, fmt.Errorf("not found") -} - -// UpsertNode : -func (r *StateRepository) UpsertNode(ctx context.Context, n *structs.Node) error { - key := resourceKey(resourceTypeNode, n.ID) - - if _, err := r.client.Put(ctx, key, encodeValue(n)); err != nil { - return err - } - return nil -} - -// DeleteNodes : -func (r *StateRepository) DeleteNodes(ctx context.Context, ids []string) error { - - for _, id := range ids { - key := resourceKey(resourceTypeNode, id) - if _, err := r.client.Delete(ctx, key); err != nil { - return err - } - } - - return nil -} diff --git a/drago/state/inmem/acl_policy.go b/drago/state/inmem/acl_policy.go deleted file mode 100644 index 2ad812b..0000000 --- a/drago/state/inmem/acl_policy.go +++ /dev/null @@ -1,54 +0,0 @@ -package inmem - -import ( - "context" - "errors" - "strings" - - structs "github.com/seashell/drago/drago/structs" -) - -const ( - resourceTypePolicy = "policy" -) - -// ACLPolicies : -func (r *StateRepository) ACLPolicies(ctx context.Context) ([]*structs.ACLPolicy, error) { - prefix := resourcePrefix(resourceTypePolicy) - - items := []*structs.ACLPolicy{} - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if t, ok := el.Value.(*structs.ACLPolicy); ok { - items = append(items, t) - } - } - } - - return items, nil -} - -// ACLPolicyByName : -func (r *StateRepository) ACLPolicyByName(ctx context.Context, name string) (*structs.ACLPolicy, error) { - key := resourceKey(resourceTypePolicy, name) - if v, found := r.kv.Get(key); found { - return v.(*structs.ACLPolicy), nil - } - return nil, errors.New("not found") -} - -// UpsertACLPolicy : -func (r *StateRepository) UpsertACLPolicy(ctx context.Context, p *structs.ACLPolicy) error { - key := resourceKey(resourceTypePolicy, p.Name) - r.kv.Set(key, p) - return nil -} - -// DeleteACLPolicies : -func (r *StateRepository) DeleteACLPolicies(ctx context.Context, names []string) error { - for _, name := range names { - key := resourceKey(resourceTypePolicy, name) - r.kv.Delete((key)) - } - return nil -} diff --git a/drago/state/inmem/acl_state.go b/drago/state/inmem/acl_state.go deleted file mode 100644 index 420b23e..0000000 --- a/drago/state/inmem/acl_state.go +++ /dev/null @@ -1,29 +0,0 @@ -package inmem - -import ( - "context" - "errors" - "fmt" - - structs "github.com/seashell/drago/drago/structs" -) - -// ACLState : -func (r *StateRepository) ACLState(ctx context.Context) (*structs.ACLState, error) { - key := aclStateKey() - if v, found := r.kv.Get(key); found { - return v.(*structs.ACLState), nil - } - return nil, errors.New("not found") -} - -// ACLSetState : -func (r *StateRepository) ACLSetState(ctx context.Context, s *structs.ACLState) error { - key := aclStateKey() - r.kv.Set(key, s) - return nil -} - -func aclStateKey() string { - return fmt.Sprintf("%s/%s/global/%s", defaultPrefix, "acl", "state") -} diff --git a/drago/state/inmem/acl_token.go b/drago/state/inmem/acl_token.go deleted file mode 100644 index 50806ac..0000000 --- a/drago/state/inmem/acl_token.go +++ /dev/null @@ -1,67 +0,0 @@ -package inmem - -import ( - "context" - "errors" - "strings" - - structs "github.com/seashell/drago/drago/structs" -) - -const ( - resourceTypeToken = "token" -) - -// ACLTokenByID ... -func (r *StateRepository) ACLTokenByID(ctx context.Context, id string) (*structs.ACLToken, error) { - key := resourceKey(resourceTypeToken, id) - if v, found := r.kv.Get(key); found { - return v.(*structs.ACLToken), nil - } - return nil, errors.New("not found") -} - -// ACLTokenBySecret : -func (r *StateRepository) ACLTokenBySecret(ctx context.Context, secret string) (*structs.ACLToken, error) { - prefix := resourcePrefix(resourceTypeToken) - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if t, ok := el.Value.(*structs.ACLToken); ok { - if t.Secret == secret { - return t, nil - } - } - } - } - return nil, nil -} - -// UpsertACLToken : -func (r *StateRepository) UpsertACLToken(ctx context.Context, t *structs.ACLToken) error { - key := resourceKey(resourceTypeToken, t.ID) - r.kv.Set(key, t) - return nil -} - -// DeleteACLTokens : -func (r *StateRepository) DeleteACLTokens(ctx context.Context, ids []string) error { - for _, id := range ids { - key := resourceKey(resourceTypeToken, id) - r.kv.Delete(key) - } - return nil -} - -// ACLTokens : -func (r *StateRepository) ACLTokens(ctx context.Context) ([]*structs.ACLToken, error) { - prefix := resourcePrefix(resourceTypeToken) - items := []*structs.ACLToken{} - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if t, ok := el.Value.(*structs.ACLToken); ok { - items = append(items, t) - } - } - } - return items, nil -} diff --git a/drago/state/inmem/connection.go b/drago/state/inmem/connection.go deleted file mode 100644 index 766f02d..0000000 --- a/drago/state/inmem/connection.go +++ /dev/null @@ -1,126 +0,0 @@ -package inmem - -import ( - "context" - "errors" - "strings" - - structs "github.com/seashell/drago/drago/structs" -) - -const ( - resourceTypeConnection = "connection" -) - -// Connections : -func (r *StateRepository) Connections(ctx context.Context) ([]*structs.Connection, error) { - prefix := resourcePrefix(resourceTypeConnection) - items := []*structs.Connection{} - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if t, ok := el.Value.(*structs.Connection); ok { - items = append(items, t) - } - } - } - return items, nil -} - -// ConnectionByID ... -func (r *StateRepository) ConnectionByID(ctx context.Context, id string) (*structs.Connection, error) { - key := resourceKey(resourceTypeConnection, id) - if v, found := r.kv.Get(key); found { - return v.(*structs.Connection), nil - } - return nil, errors.New("not found") -} - -// ConnectionByInterfaceIDs ... -func (r *StateRepository) ConnectionByInterfaceIDs(ctx context.Context, a, b string) (*structs.Connection, error) { - prefix := resourcePrefix(resourceTypeConnection) - - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if c, ok := el.Value.(*structs.Connection); ok { - if c.ConnectsInterfaces(a, b) { - return c, nil - } - } - } - } - - return nil, errors.New("not found") -} - -// ConnectionsByNetworkID ... -func (r *StateRepository) ConnectionsByNetworkID(ctx context.Context, id string) ([]*structs.Connection, error) { - - res := []*structs.Connection{} - - prefix := resourcePrefix(resourceTypeConnection) - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if conn, ok := el.Value.(*structs.Connection); ok { - if conn.NetworkID == id { - res = append(res, conn) - } - } - } - } - - return res, nil -} - -// ConnectionsByNodeID ... -func (r *StateRepository) ConnectionsByNodeID(ctx context.Context, id string) ([]*structs.Connection, error) { - - res := []*structs.Connection{} - - prefix := resourcePrefix(resourceTypeConnection) - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if conn, ok := el.Value.(*structs.Connection); ok { - if conn.PeerSettings[0].NodeID == id || conn.PeerSettings[1].NodeID == id { - res = append(res, conn) - } - } - } - } - - return res, nil -} - -// ConnectionsByInterfaceID ... -func (r *StateRepository) ConnectionsByInterfaceID(ctx context.Context, id string) ([]*structs.Connection, error) { - - res := []*structs.Connection{} - - prefix := resourcePrefix(resourceTypeConnection) - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if conn, ok := el.Value.(*structs.Connection); ok { - if conn.ConnectsInterface(id) { - res = append(res, conn) - } - } - } - } - - return res, nil -} - -// UpsertConnection : -func (r *StateRepository) UpsertConnection(ctx context.Context, n *structs.Connection) error { - key := resourceKey(resourceTypeConnection, n.ID) - r.kv.Set(key, n) - return nil -} - -// DeleteConnections ... -func (r *StateRepository) DeleteConnections(ctx context.Context, ids []string) error { - for _, id := range ids { - key := resourceKey(resourceTypeConnection, id) - r.kv.Delete(key) - } - return nil -} diff --git a/drago/state/inmem/inmem.go b/drago/state/inmem/inmem.go deleted file mode 100644 index c8b3620..0000000 --- a/drago/state/inmem/inmem.go +++ /dev/null @@ -1,73 +0,0 @@ -package inmem - -import ( - "context" - "fmt" - "strings" - - state "github.com/seashell/drago/drago/state" - concurrent "github.com/seashell/drago/pkg/concurrent" - log "github.com/seashell/drago/pkg/log" -) - -const ( - defaultNamespace = "default" - defaultPrefix = "/registry" -) - -// StateRepository ... -type StateRepository struct { - kv *concurrent.Map - logger log.Logger -} - -// NewStateRepository ... -func NewStateRepository(logger log.Logger) *StateRepository { - return &StateRepository{ - kv: concurrent.NewMap(), - logger: logger, - } -} - -// Name ... -func (b *StateRepository) Name() string { - return "inmem" -} - -type transaction struct { -} - -func (t transaction) Commit() (interface{}, error) { - return nil, nil -} - -// Transaction ... -func (b *StateRepository) Transaction(ctx context.Context) state.Transaction { - return transaction{} -} - -// Dump ... -func (b *StateRepository) Dump() <-chan string { - dumpCh := make(chan string, 1) - padding := 72 - go func() { - for el := range b.kv.Iter() { - dumpCh <- fmt.Sprintf("%s%s %T", el.Key, strings.Repeat(" ", padding-len(el.Key)), el.Value) - } - close(dumpCh) - }() - return dumpCh -} - -// Clear ... -func (b *StateRepository) Clear() { - b.kv = &concurrent.Map{} -} - -func resourcePrefix(resourceType string) string { - return fmt.Sprintf("%s/%s/%s", defaultPrefix, resourceType, defaultNamespace) -} - -func resourceKey(resourceType, resourceID string) string { - return fmt.Sprintf("%s/%s", resourcePrefix(resourceType), resourceID) -} diff --git a/drago/state/inmem/interface.go b/drago/state/inmem/interface.go deleted file mode 100644 index 97d45af..0000000 --- a/drago/state/inmem/interface.go +++ /dev/null @@ -1,88 +0,0 @@ -package inmem - -import ( - "context" - "errors" - "strings" - - structs "github.com/seashell/drago/drago/structs" -) - -const ( - resourceTypeInterface = "interface" -) - -// Interfaces : -func (r *StateRepository) Interfaces(ctx context.Context) ([]*structs.Interface, error) { - prefix := resourcePrefix(resourceTypeInterface) - items := []*structs.Interface{} - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if t, ok := el.Value.(*structs.Interface); ok { - items = append(items, t) - } - } - } - return items, nil -} - -// InterfacesByNodeID ... -func (r *StateRepository) InterfacesByNodeID(ctx context.Context, s string) ([]*structs.Interface, error) { - - res := []*structs.Interface{} - - prefix := resourcePrefix(resourceTypeInterface) - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if iface, ok := el.Value.(*structs.Interface); ok { - if iface.NodeID == s { - res = append(res, iface) - } - } - } - } - return res, nil -} - -// InterfacesByNetworkID ... -func (r *StateRepository) InterfacesByNetworkID(ctx context.Context, s string) ([]*structs.Interface, error) { - - res := []*structs.Interface{} - - prefix := resourcePrefix(resourceTypeInterface) - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if iface, ok := el.Value.(*structs.Interface); ok { - if iface.NetworkID == s { - res = append(res, iface) - } - } - } - } - return res, nil -} - -// InterfaceByID ... -func (r *StateRepository) InterfaceByID(ctx context.Context, id string) (*structs.Interface, error) { - key := resourceKey(resourceTypeInterface, id) - if v, found := r.kv.Get(key); found { - return v.(*structs.Interface), nil - } - return nil, errors.New("not found") -} - -// UpsertInterface : -func (r *StateRepository) UpsertInterface(ctx context.Context, n *structs.Interface) error { - key := resourceKey(resourceTypeInterface, n.ID) - r.kv.Set(key, n) - return nil -} - -// DeleteInterfaces ... -func (r *StateRepository) DeleteInterfaces(ctx context.Context, ids []string) error { - for _, id := range ids { - key := resourceKey(resourceTypeInterface, id) - r.kv.Delete(key) - } - return nil -} diff --git a/drago/state/inmem/network.go b/drago/state/inmem/network.go deleted file mode 100644 index 8c9f104..0000000 --- a/drago/state/inmem/network.go +++ /dev/null @@ -1,67 +0,0 @@ -package inmem - -import ( - "context" - "errors" - "strings" - - structs "github.com/seashell/drago/drago/structs" -) - -const ( - resourceTypeNetwork = "network" -) - -// Networks : -func (r *StateRepository) Networks(ctx context.Context) ([]*structs.Network, error) { - prefix := resourcePrefix(resourceTypeNetwork) - items := []*structs.Network{} - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if t, ok := el.Value.(*structs.Network); ok { - items = append(items, t) - } - } - } - return items, nil -} - -// NetworkByID ... -func (r *StateRepository) NetworkByID(ctx context.Context, id string) (*structs.Network, error) { - key := resourceKey(resourceTypeNetwork, id) - if v, found := r.kv.Get(key); found { - return v.(*structs.Network), nil - } - return nil, errors.New("not found") -} - -// NetworkByName ... -func (r *StateRepository) NetworkByName(ctx context.Context, name string) (*structs.Network, error) { - prefix := resourcePrefix(resourceTypeNetwork) - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if n, ok := el.Value.(*structs.Network); ok { - if n.Name == name { - return n, nil - } - } - } - } - return nil, errors.New("not found") -} - -// UpsertNetwork : -func (r *StateRepository) UpsertNetwork(ctx context.Context, n *structs.Network) error { - key := resourceKey(resourceTypeNetwork, n.ID) - r.kv.Set(key, n) - return nil -} - -// DeleteNetworks ... -func (r *StateRepository) DeleteNetworks(ctx context.Context, ids []string) error { - for _, id := range ids { - key := resourceKey(resourceTypeNetwork, id) - r.kv.Delete(key) - } - return nil -} diff --git a/drago/state/inmem/node.go b/drago/state/inmem/node.go deleted file mode 100644 index bf91530..0000000 --- a/drago/state/inmem/node.go +++ /dev/null @@ -1,67 +0,0 @@ -package inmem - -import ( - "context" - "errors" - "strings" - - structs "github.com/seashell/drago/drago/structs" -) - -const ( - resourceTypeNode = "node" -) - -// Nodes : -func (r *StateRepository) Nodes(ctx context.Context) ([]*structs.Node, error) { - prefix := resourcePrefix(resourceTypeNode) - items := []*structs.Node{} - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if t, ok := el.Value.(*structs.Node); ok { - items = append(items, t) - } - } - } - return items, nil -} - -// NodeByID ... -func (r *StateRepository) NodeByID(ctx context.Context, id string) (*structs.Node, error) { - key := resourceKey(resourceTypeNode, id) - if v, found := r.kv.Get(key); found { - return v.(*structs.Node), nil - } - return nil, errors.New("not found") -} - -// NodeBySecretID ... -func (r *StateRepository) NodeBySecretID(ctx context.Context, s string) (*structs.Node, error) { - prefix := resourcePrefix(resourceTypeNode) - for el := range r.kv.Iter() { - if strings.HasPrefix(el.Key, prefix) { - if n, ok := el.Value.(*structs.Node); ok { - if n.SecretID == s { - return n, nil - } - } - } - } - return nil, errors.New("not found") -} - -// UpsertNode : -func (r *StateRepository) UpsertNode(ctx context.Context, n *structs.Node) error { - key := resourceKey(resourceTypeNode, n.ID) - r.kv.Set(key, n) - return nil -} - -// DeleteNodes ... -func (r *StateRepository) DeleteNodes(ctx context.Context, ids []string) error { - for _, id := range ids { - key := resourceKey(resourceTypeNode, id) - r.kv.Delete(key) - } - return nil -} diff --git a/drago/state/state.go b/drago/state/state.go deleted file mode 100644 index 0964bca..0000000 --- a/drago/state/state.go +++ /dev/null @@ -1,87 +0,0 @@ -package state - -import ( - "context" - - "github.com/seashell/drago/drago/structs" -) - -// Transaction : -type Transaction interface { - Commit() (interface{}, error) -} - -// Repository : -type Repository interface { - Name() string - Transaction(ctx context.Context) Transaction - - ACLTokenRepository - ACLPolicyRepository - - NodeRepository - - NetworkRepository - InterfaceRepository - ConnectionRepository - - ACLState(ctx context.Context) (*structs.ACLState, error) - ACLSetState(ctx context.Context, state *structs.ACLState) error -} - -// ACLTokenRepository : ACLToken repository interface -type ACLTokenRepository interface { - ACLTokens(ctx context.Context) ([]*structs.ACLToken, error) - ACLTokenByID(ctx context.Context, id string) (*structs.ACLToken, error) - ACLTokenBySecret(ctx context.Context, id string) (*structs.ACLToken, error) - UpsertACLToken(ctx context.Context, t *structs.ACLToken) error - DeleteACLTokens(ctx context.Context, ids []string) error -} - -// ACLPolicyRepository : Policy repository interface -type ACLPolicyRepository interface { - ACLPolicies(ctx context.Context) ([]*structs.ACLPolicy, error) - ACLPolicyByName(ctx context.Context, name string) (*structs.ACLPolicy, error) - UpsertACLPolicy(ctx context.Context, p *structs.ACLPolicy) error - DeleteACLPolicies(ctx context.Context, names []string) error -} - -// NetworkRepository : Network repository interface -type NetworkRepository interface { - Networks(ctx context.Context) ([]*structs.Network, error) - NetworkByID(ctx context.Context, id string) (*structs.Network, error) - NetworkByName(ctx context.Context, name string) (*structs.Network, error) - UpsertNetwork(ctx context.Context, n *structs.Network) error - DeleteNetworks(ctx context.Context, ids []string) error -} - -// NodeRepository : Node repository interface -type NodeRepository interface { - Nodes(ctx context.Context) ([]*structs.Node, error) - NodeByID(ctx context.Context, id string) (*structs.Node, error) - NodeBySecretID(ctx context.Context, sid string) (*structs.Node, error) - UpsertNode(ctx context.Context, n *structs.Node) error - DeleteNodes(ctx context.Context, ids []string) error -} - -// InterfaceRepository : Interface repository interface -type InterfaceRepository interface { - Interfaces(ctx context.Context) ([]*structs.Interface, error) - InterfacesByNodeID(ctx context.Context, s string) ([]*structs.Interface, error) - InterfacesByNetworkID(ctx context.Context, s string) ([]*structs.Interface, error) - InterfaceByID(ctx context.Context, id string) (*structs.Interface, error) - UpsertInterface(ctx context.Context, i *structs.Interface) error - DeleteInterfaces(ctx context.Context, ids []string) error -} - -// ConnectionRepository : Connection repository interface -type ConnectionRepository interface { - Connections(ctx context.Context) ([]*structs.Connection, error) - ConnectionsByNetworkID(ctx context.Context, s string) ([]*structs.Connection, error) - ConnectionsByNodeID(ctx context.Context, s string) ([]*structs.Connection, error) - ConnectionsByInterfaceID(ctx context.Context, s string) ([]*structs.Connection, error) - ConnectionByInterfaceIDs(ctx context.Context, a, b string) (*structs.Connection, error) - ConnectionByID(ctx context.Context, id string) (*structs.Connection, error) - UpsertConnection(ctx context.Context, i *structs.Connection) error - DeleteConnections(ctx context.Context, ids []string) error -} diff --git a/drago/status.go b/drago/status.go deleted file mode 100644 index 65a72e6..0000000 --- a/drago/status.go +++ /dev/null @@ -1,34 +0,0 @@ -package drago - -import ( - auth "github.com/seashell/drago/drago/auth" - state "github.com/seashell/drago/drago/state" - structs "github.com/seashell/drago/drago/structs" -) - -// StatusService is used to check on server status -type StatusService struct { - config *Config - state state.Repository - authHandler auth.AuthorizationHandler -} - -// NewStatusService ... -func NewStatusService(config *Config, state state.Repository, authHandler auth.AuthorizationHandler) *StatusService { - return &StatusService{ - config: config, - state: state, - authHandler: authHandler, - } -} - -// Ping is used to check for connectivity -func (s *StatusService) Ping(args structs.GenericRequest, out *structs.GenericResponse) error { - return nil -} - -// Version returns the version of the server -func (s *StatusService) Version(in structs.GenericRequest, out *structs.StatusVersionResponse) error { - out.Version = s.config.Version.VersionNumber() - return nil -} diff --git a/drago/structs/acl.go b/drago/structs/acl.go deleted file mode 100644 index 43a6a37..0000000 --- a/drago/structs/acl.go +++ /dev/null @@ -1,35 +0,0 @@ -package structs - -// ACLState ... -type ACLState struct { - RootTokenID string - RootTokenResetIndex int -} - -// Update ... -func (s *ACLState) Update(rootID string) error { - s.RootTokenID = rootID - s.RootTokenResetIndex++ - return nil -} - -// ACLBootstrapRequest : -type ACLBootstrapRequest struct { - ResetIndex uint64 - - WriteRequest -} - -// ResolveACLTokenRequest : -type ResolveACLTokenRequest struct { - Secret string - - QueryOptions -} - -// ResolveACLTokenResponse : -type ResolveACLTokenResponse struct { - ACLToken *ACLToken - - Response -} diff --git a/drago/structs/acl_policy.go b/drago/structs/acl_policy.go deleted file mode 100644 index e0947b7..0000000 --- a/drago/structs/acl_policy.go +++ /dev/null @@ -1,103 +0,0 @@ -package structs - -import "time" - -// ACLPolicy contains a composition of subpolicies for each resource exposed by Drago. -// It can be assigned to an ACL Token and, according to the capabilities within each -// subpolicy, gives different levels of access to these resources. -type ACLPolicy struct { - Name string - Description string - Rules []*ACLPolicyRule - CreatedAt time.Time - UpdatedAt time.Time -} - -func (p *ACLPolicy) Validate() error { - return nil -} - -func (p *ACLPolicy) Merge(in *ACLPolicy) *ACLPolicy { - - result := *p - - if in.Name != "" { - result.Name = in.Name - } - if in.Description != "" { - result.Description = in.Description - } - if in.Rules != nil { - result.Rules = in.Rules - } - - return &result -} - -// Stub : -func (p *ACLPolicy) Stub() *ACLPolicyListStub { - return &ACLPolicyListStub{ - Name: p.Name, - Description: p.Description, - CreatedAt: p.CreatedAt, - UpdatedAt: p.UpdatedAt, - } -} - -// ACLPolicyListStub : -type ACLPolicyListStub struct { - Name string - Description string - CreatedAt time.Time - UpdatedAt time.Time -} - -// ACLPolicyRule ... -type ACLPolicyRule struct { - Resource string - Path string - Capabilities []string -} - -// ACLPolicySpecificRequest : -type ACLPolicySpecificRequest struct { - // Name contains the name of the policy to be retrieved. - Name string - - QueryOptions -} - -// SingleACLPolicyResponse : -type SingleACLPolicyResponse struct { - ACLPolicy *ACLPolicy - - Response -} - -// ACLPolicyUpsertRequest : -type ACLPolicyUpsertRequest struct { - ACLPolicy *ACLPolicy - - WriteRequest -} - -// ACLPolicyDeleteRequest : -type ACLPolicyDeleteRequest struct { - // Name contains the name of the policy to be deleted. - Names []string - - WriteRequest -} - -// ACLPolicyListRequest : -type ACLPolicyListRequest struct { - QueryOptions -} - -// ACLPolicyListResponse : -type ACLPolicyListResponse struct { - Response - - // Items contains the policies found. - Items []*ACLPolicyListStub -} diff --git a/drago/structs/acl_token.go b/drago/structs/acl_token.go deleted file mode 100644 index f26c737..0000000 --- a/drago/structs/acl_token.go +++ /dev/null @@ -1,128 +0,0 @@ -package structs - -import ( - "fmt" - "time" -) - -const ( - // ACLTokenTypeClient ... - ACLTokenTypeClient = "client" - - // ACLTokenTypeManagement ... - ACLTokenTypeManagement = "management" -) - -// ACLToken : -type ACLToken struct { - ID string - Type string - Name string - Secret string - Policies []string - CreatedAt time.Time - UpdatedAt time.Time -} - -func (t *ACLToken) Validate() error { - - if t.Type != ACLTokenTypeClient && t.Type != ACLTokenTypeManagement { - return fmt.Errorf("invalid token type %s", t.Type) - } - - if t.Type == ACLTokenTypeManagement && !(t.Policies == nil || len(t.Policies) == 0) { - return fmt.Errorf("invalid token policies %v", t.Policies) - } - - return nil -} - -// Merge : -func (t *ACLToken) Merge(in *ACLToken) *ACLToken { - - result := *t - - if in.Name != "" { - result.Name = in.Name - } - if in.Type != "" { - result.Type = in.Type - } - if in.Secret != "" { - result.Secret = in.Secret - } - if in.Policies != nil { - result.Policies = in.Policies - } - - return &result -} - -// Stub : -func (t *ACLToken) Stub() *ACLTokenListStub { - return &ACLTokenListStub{ - ID: t.ID, - Name: t.Name, - Type: t.Type, - Policies: t.Policies, - CreatedAt: t.CreatedAt, - UpdatedAt: t.UpdatedAt, - } -} - -// ACLTokenListStub : -type ACLTokenListStub struct { - ID string - Name string - Type string - Policies []string - CreatedAt time.Time - UpdatedAt time.Time -} - -// ACLTokenListRequest : -type ACLTokenListRequest struct { - QueryOptions -} - -// ACLTokenListResponse : -type ACLTokenListResponse struct { - Items []*ACLTokenListStub - - Response -} - -// ACLTokenSpecificRequest : -type ACLTokenSpecificRequest struct { - ACLTokenID string - - QueryOptions -} - -// SingleACLTokenResponse : -type SingleACLTokenResponse struct { - ACLToken *ACLToken - - Response -} - -// ACLTokenUpsertRequest : -type ACLTokenUpsertRequest struct { - ACLToken *ACLToken - - WriteRequest -} - -// ACLTokenUpsertResponse : -type ACLTokenUpsertResponse struct { - ACLToken *ACLToken - - Response -} - -// ACLTokenDeleteRequest : -type ACLTokenDeleteRequest struct { - ACLTokenIDs []string - - WriteRequest -} diff --git a/drago/structs/agent.go b/drago/structs/agent.go deleted file mode 100644 index a840771..0000000 --- a/drago/structs/agent.go +++ /dev/null @@ -1,7 +0,0 @@ -package structs - -// Agent : -type Agent struct { - Config map[string]interface{} - Stats map[string]map[string]string -} diff --git a/drago/structs/config/acl.go b/drago/structs/config/acl.go deleted file mode 100644 index 2572d27..0000000 --- a/drago/structs/config/acl.go +++ /dev/null @@ -1,26 +0,0 @@ -package config - -import ( - "time" - - "github.com/seashell/drago/pkg/acl" -) - -// ACLConfig : -type ACLConfig struct { - Enabled bool - // TokenTTL controls for how long we keep ACL tokens in cache. - TokenTTL time.Duration - - // Model contains the ACL model - Model *acl.Model -} - -// DefaultACLConfig : -func DefaultACLConfig() *ACLConfig { - return &ACLConfig{ - Enabled: false, - TokenTTL: 30 * time.Second, - Model: acl.NewModel(), - } -} diff --git a/drago/structs/config/etcd.go b/drago/structs/config/etcd.go deleted file mode 100644 index ef2708e..0000000 --- a/drago/structs/config/etcd.go +++ /dev/null @@ -1,63 +0,0 @@ -package config - -// EtcdConfig : see https://etcd.io/docs/v3.4.0/op-guide/configuration -type EtcdConfig struct { - // Human-readable name of this node in the etcd cluster - Name string - - // List of URLs to listen on for peer traffic. This flag tells the - // etcd to accept incoming requests from its peers on the specified - // scheme://IP:port combinations. Defaults to [“http://localhost:2380”]. - ListenPeerURLs []string - - // List of URLs to listen on for client traffic. This flag tells the - // etcd to accept incoming requests from the clients on the specified - // scheme://IP:port combinations. Defaults to [“http://localhost:2379”]. - ListenClientURLs []string - - // List of this member’s peer URLs to advertise to the rest of the cluster. - // These addresses are used for communicating etcd data around the cluster. - // At least one must be routable to all cluster members. These URLs can - // contain domain names. Defaults to [“http://localhost:2380”]. - InitialAdvertisePeerURLs []string - - // List of this member’s client URLs to advertise to the rest of the cluster. - // These URLs can contain domain names. Defaults to [“http://localhost:2379”]. - InitialAdvertiseClientURLs []string - - // Initial cluster configuration for bootstrapping. Defaults to [“http://localhost:2380”]. - // The key is the value of Name for each node provided. The default uses 'default' for - // the key because this is the default for the --name flag - InitialCluster []string - - // Initial cluster state (“new” or “existing”). Set to new for all members - // present during initial static or DNS bootstrapping. If this option is set - // to existing, etcd will attempt to join the existing cluster. If the wrong - // value is set, etcd will attempt to start but fail safely. - InitialClusterState string - - // Defines whether this node should run on proxy mode. If enabled, this means that - // the node will simply forward requests to an already existing etcd cluster, without - // actually joining it. See https://etcd.io/docs/v2/proxy/. - ProxyModeEnabled bool - - // Comma-separated white list of origins for CORS (cross-origin - // resource sharing). - CORS string -} - -// DefaultEtcdConfig returns the canonical defaults for the Drago -// `etcd` configuration. -func DefaultEtcdConfig() *EtcdConfig { - return &EtcdConfig{ - Name: "default", - ListenPeerURLs: []string{"http://localhost:2380"}, - ListenClientURLs: []string{"http://localhost:2379"}, - InitialAdvertisePeerURLs: []string{"http://localhost:2380"}, - InitialAdvertiseClientURLs: []string{"http://localhost:2379"}, - InitialCluster: []string{"http://localhost:2380"}, - InitialClusterState: "new", - ProxyModeEnabled: false, - CORS: "", - } -} diff --git a/drago/structs/connection.go b/drago/structs/connection.go deleted file mode 100644 index 56172da..0000000 --- a/drago/structs/connection.go +++ /dev/null @@ -1,251 +0,0 @@ -package structs - -import ( - "sort" - "time" -) - -// Connection : -type Connection struct { - ID string - NetworkID string - - // PeerSettings contains the ID and the configurations to be applied - // to each of the connected interfaces. - PeerSettings []*PeerSettings - - // If the connection is going from a NAT-ed peer to a public peer, - // the node behind the NAT must regularly send an outgoing ping to - // keep the bidirectional connection alive in the NAT router's - // connection table. - PersistentKeepalive *int - - CreatedAt time.Time - UpdatedAt time.Time -} - -// Validate : -func (c *Connection) Validate() error { - return nil -} - -// ConnectedInterfaceIDs : -func (c *Connection) ConnectedInterfaceIDs() []string { - ids := []string{} - for _, peer := range c.PeerSettings { - ids = append(ids, peer.InterfaceID) - } - sort.Strings(ids) - return ids -} - -// ConnectedNodeIDs : -func (c *Connection) ConnectedNodeIDs() []string { - ids := []string{} - for _, peer := range c.PeerSettings { - ids = append(ids, peer.NodeID) - } - sort.Strings(ids) - return ids -} - -// PeerSettingsByNodeID : -func (c *Connection) PeerSettingsByNodeID(s string) *PeerSettings { - - if c.PeerSettings[0].NodeID == s { - return c.PeerSettings[0] - } else if c.PeerSettings[1].NodeID == s { - return c.PeerSettings[1] - } - - return nil -} - -// PeerSettingsByInterfaceID : -func (c *Connection) PeerSettingsByInterfaceID(s string) *PeerSettings { - - if c.PeerSettings[0].InterfaceID == s { - return c.PeerSettings[0] - } else if c.PeerSettings[1].InterfaceID == s { - return c.PeerSettings[1] - } - - return nil -} - -// OtherPeerSettingsByInterfaceID : given the ID of one of the connected interfaces, -// returns the settings for the peer/interface at the other end of the connection. -func (c *Connection) OtherPeerSettingsByInterfaceID(s string) *PeerSettings { - - if c.PeerSettings[0].InterfaceID == s { - return c.PeerSettings[1] - } else if c.PeerSettings[1].InterfaceID == s { - return c.PeerSettings[0] - } - - return nil -} - -// ConnectsInterfaces : checks whether a Connection connects two -// interfaces whose indices are passed as arguments. -func (c *Connection) ConnectsInterfaces(a, b string) bool { - if c.ConnectsInterface(a) && c.ConnectsInterface(b) { - return true - } - return false -} - -// ConnectsInterface : checks whether a connection connects -// an interface whose index is passed as argument. -func (c *Connection) ConnectsInterface(s string) bool { - - if c.PeerSettings[0].InterfaceID == s || c.PeerSettings[1].InterfaceID == s { - return true - } - return false -} - -// Merge : -func (c *Connection) Merge(in *Connection) *Connection { - - result := *c - - if in.ID != "" { - result.ID = in.ID - } - if in.PeerSettings != nil { - if result.PeerSettings == nil { - result.PeerSettings = in.PeerSettings - } else { - for _, peer := range in.PeerSettings { - if result.PeerSettings[0].InterfaceID == peer.InterfaceID { - result.PeerSettings[0] = result.PeerSettings[0].Merge(peer) - } else if result.PeerSettings[1].InterfaceID == peer.InterfaceID { - result.PeerSettings[1] = result.PeerSettings[1].Merge(peer) - } - } - } - } - if in.PersistentKeepalive != nil { - result.PersistentKeepalive = in.PersistentKeepalive - } - - return &result -} - -// Stub : -func (c *Connection) Stub() *ConnectionListStub { - - peers := []string{} - for _, peer := range c.PeerSettings { - peers = append(peers, peer.InterfaceID) - } - - return &ConnectionListStub{ - ID: c.ID, - NetworkID: c.NetworkID, - Peers: peers, - PeerSettings: c.PeerSettings, - PersistentKeepalive: c.PersistentKeepalive, - BytesTransferred: 0, - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - } -} - -// ConnectionListStub : -type ConnectionListStub struct { - ID string - NetworkID string - NodeIDs []string - Peers []string - PeerSettings []*PeerSettings - PersistentKeepalive *int - BytesTransferred uint64 - CreatedAt time.Time - UpdatedAt time.Time -} - -// PeerSettings : -type PeerSettings struct { - NodeID string - InterfaceID string - RoutingRules *RoutingRules -} - -// Merge : -func (r *PeerSettings) Merge(in *PeerSettings) *PeerSettings { - result := *r - if in.NodeID != "" { - result.NodeID = in.NodeID - } - if in.InterfaceID != "" { - result.InterfaceID = in.InterfaceID - } - if in.RoutingRules != nil { - result.RoutingRules = r.RoutingRules.Merge(in.RoutingRules) - } - return &result -} - -// RoutingRules : -type RoutingRules struct { - // AllowedIPs defines the IP ranges for which traffic will be routed/accepted. - // Example: If AllowedIPs = [192.0.2.3/32, 192.168.1.1/24], the node - // will accept traffic for itself (192.0.2.3/32), and for all nodes in the - // local network (192.168.1.1/24). - AllowedIPs []string -} - -// Merge : -func (r *RoutingRules) Merge(in *RoutingRules) *RoutingRules { - result := *r - if in.AllowedIPs != nil { - result.AllowedIPs = in.AllowedIPs - } - return &result -} - -// ConnectionSpecificRequest : -type ConnectionSpecificRequest struct { - ConnectionID string - - QueryOptions -} - -// SingleConnectionResponse : -type SingleConnectionResponse struct { - Connection *Connection - - Response -} - -// ConnectionUpsertRequest : -type ConnectionUpsertRequest struct { - Connection *Connection - - WriteRequest -} - -// ConnectionDeleteRequest : -type ConnectionDeleteRequest struct { - ConnectionIDs []string - - WriteRequest -} - -// ConnectionListRequest : -type ConnectionListRequest struct { - InterfaceID string - NodeID string - NetworkID string - - QueryOptions -} - -// ConnectionListResponse : -type ConnectionListResponse struct { - Items []*ConnectionListStub - - Response -} diff --git a/drago/structs/errors.go b/drago/structs/errors.go deleted file mode 100644 index d1cc8ba..0000000 --- a/drago/structs/errors.go +++ /dev/null @@ -1,65 +0,0 @@ -package structs - -import ( - "fmt" - - "errors" -) - -const ( - errPermissionDenied = "Permission denied" - errTokenNotFound = "ACL Token not found" - errACLDisabled = "ACL disabled" - errACLAlreadyBootstrapped = "ACL already bootstrapped" - errInvalidInput = "Invalid input" - errNotFound = "Resource not found" - errInternal = "Internal error" -) - -var ( - // ErrPermissionDenied : - ErrPermissionDenied = errors.New(errPermissionDenied) - - // ErrACLAlreadyBootstrapped ... - ErrACLAlreadyBootstrapped = errors.New(errACLAlreadyBootstrapped) - - // ErrACLDisabled ... - ErrACLDisabled = errors.New(errACLDisabled) - - // ErrInternal ... - ErrInternal = errors.New(errInternal) - - // ErrInvalidInput ... - ErrInvalidInput = errors.New(errInvalidInput) - - // ErrNotFound ... - ErrNotFound = errors.New(errNotFound) -) - -// Error : -type Error struct { - Message string -} - -// NewError ... -func NewError(base error, extra ...interface{}) error { - msg := base.Error() - for _, v := range extra { - msg = fmt.Sprintf("%s : %v", msg, v) - } - return &Error{ - Message: msg, - } -} - -func (e Error) Error() string { - return e.Message -} - -func NewInternalError(msg string) error { - return NewError(ErrInternal, msg) -} - -func NewInvalidInputError(msg string) error { - return NewError(ErrInvalidInput, msg) -} diff --git a/drago/structs/interface.go b/drago/structs/interface.go deleted file mode 100644 index 72ab092..0000000 --- a/drago/structs/interface.go +++ /dev/null @@ -1,180 +0,0 @@ -package structs - -import ( - "time" -) - -type Interface struct { - ID string - NodeID string - NetworkID string - Name *string - Address *string - ListenPort *int - PublicKey *string - Peers []*Peer - Connections []string - CreatedAt time.Time - UpdatedAt time.Time - - // Underlying struct for efficiently adding/removing connections. - // Always use the lazyConnectionsMap() method for accessing it. - connectionsMap map[string]struct{} -} - -// Merge : -func (i *Interface) Merge(in *Interface) *Interface { - - result := *i - - if in.ID != "" { - result.ID = in.ID - } - if in.NodeID != "" { - result.NodeID = in.NodeID - } - if in.NetworkID != "" { - result.NetworkID = in.NetworkID - } - if in.Name != nil { - result.Name = in.Name - } - if in.Address != nil { - result.Address = in.Address - } - if in.PublicKey != nil { - result.PublicKey = in.PublicKey - } - if in.ListenPort != nil { - result.ListenPort = in.ListenPort - } - if in.Peers != nil { - result.Peers = in.Peers - } - - return &result -} - -// Validate : validate interface fields -func (i *Interface) Validate() error { - return nil -} - -// If the interfaces's connectionsMap was already initialized, return it. -// Otherwise initialize and synchronize it with the interface connections slice. -func (i *Interface) lazyConnectionsMap() map[string]struct{} { - - if i.connectionsMap != nil { - return i.connectionsMap - } - - i.connectionsMap = map[string]struct{}{} - for _, conn := range i.Connections { - i.connectionsMap[conn] = struct{}{} - } - return i.connectionsMap -} - -// UpsertConnection : -func (i *Interface) UpsertConnection(id string) { - i.lazyConnectionsMap()[id] = struct{}{} - tmp := i.Connections[:0] - for k := range i.connectionsMap { - tmp = append(tmp, k) - } - i.Connections = tmp -} - -// RemoveConnection : -func (i *Interface) RemoveConnection(id string) { - - delete(i.lazyConnectionsMap(), id) - tmp := i.Connections[:0] - for k := range i.connectionsMap { - tmp = append(tmp, k) - } - i.Connections = tmp -} - -// Stub : -func (i *Interface) Stub() *InterfaceListStub { - return &InterfaceListStub{ - ID: i.ID, - Name: i.Name, - Address: i.Address, - ListenPort: i.ListenPort, - NodeID: i.NodeID, - NetworkID: i.NetworkID, - ConnectionsCount: len(i.Connections), - PublicKey: i.PublicKey, - HasPublicKey: i.PublicKey != nil, - CreatedAt: i.CreatedAt, - UpdatedAt: i.UpdatedAt, - } -} - -// InterfaceListStub : -type InterfaceListStub struct { - ID string - NodeID string - NetworkID string - Name *string - Address *string - ListenPort *int - ConnectionsCount int - PublicKey *string - HasPublicKey bool - CreatedAt time.Time - UpdatedAt time.Time -} - -// InterfaceSpecificRequest : -type InterfaceSpecificRequest struct { - InterfaceID string - - QueryOptions -} - -// SingleInterfaceResponse : -type SingleInterfaceResponse struct { - Interface *Interface - - Response -} - -// InterfaceUpsertRequest : -type InterfaceUpsertRequest struct { - Interface *Interface - - WriteRequest -} - -// InterfaceDeleteRequest : -type InterfaceDeleteRequest struct { - InterfaceIDs []string - - WriteRequest -} - -// InterfaceListRequest : -type InterfaceListRequest struct { - NodeID string - NetworkID string - - QueryOptions -} - -// InterfaceListResponse : -type InterfaceListResponse struct { - Items []*InterfaceListStub - - Response -} - -type Peer struct { - PublicKey *string - Address *string - Port *int - AllowedIPs []string - PersistentKeepalive *int -} diff --git a/drago/structs/network.go b/drago/structs/network.go deleted file mode 100644 index 3ece643..0000000 --- a/drago/structs/network.go +++ /dev/null @@ -1,206 +0,0 @@ -package structs - -import ( - "errors" - "fmt" - "net" - "time" -) - -// Network : -type Network struct { - ID string - Name string - AddressRange string - Interfaces []string - Connections []string - CreatedAt time.Time - UpdatedAt time.Time - - // Underlying structs for efficiently adding/removing interfaces and connections. - // Always use the lazyInterfacesMap() and lazyConnectionsMap() methods for accessing them. - interfacesMap map[string]struct{} - connectionsMap map[string]struct{} -} - -// Validate : -func (n *Network) Validate() error { - if n.Name == "" { - return fmt.Errorf("Name is empty") - } - if n.AddressRange == "" { - return fmt.Errorf("Address range is empty") - } - return nil -} - -// CheckAddressInRange : Check whether an IP address in CIDR notation -// is within the allowed range of the network. -func (n *Network) CheckAddressInRange(ip string) error { - _, subnet, _ := net.ParseCIDR(n.AddressRange) - addr, _, _ := net.ParseCIDR(ip) - if subnet.Contains(addr) { - return nil - } - return errors.New("ip address not within network's allowed range") -} - -// If the networks's interfacesMap was already initialized, return it. -// Otherwise initialize and synchronize it with the network interfaces slice. -func (n *Network) lazyInterfacesMap() map[string]struct{} { - - if n.interfacesMap != nil { - return n.interfacesMap - } - - n.interfacesMap = map[string]struct{}{} - for _, iface := range n.Interfaces { - n.interfacesMap[iface] = struct{}{} - } - return n.interfacesMap -} - -// UpsertInterface : -func (n *Network) UpsertInterface(id string) { - n.lazyInterfacesMap()[id] = struct{}{} - tmp := n.Interfaces[:0] - for k := range n.interfacesMap { - tmp = append(tmp, k) - } - n.Interfaces = tmp -} - -// RemoveInterface : -func (n *Network) RemoveInterface(id string) { - - delete(n.lazyInterfacesMap(), id) - tmp := n.Interfaces[:0] - for k := range n.interfacesMap { - tmp = append(tmp, k) - } - n.Interfaces = tmp -} - -// If the networks's connectionsMap was already initialized, return it. -// Otherwise initialize and synchronize it with the network connections slice. -func (n *Network) lazyConnectionsMap() map[string]struct{} { - - if n.connectionsMap != nil { - return n.connectionsMap - } - - n.connectionsMap = map[string]struct{}{} - for _, conn := range n.Connections { - n.connectionsMap[conn] = struct{}{} - } - return n.connectionsMap -} - -// UpsertConnection : -func (n *Network) UpsertConnection(id string) { - n.lazyConnectionsMap()[id] = struct{}{} - tmp := n.Connections[:0] - for k := range n.connectionsMap { - tmp = append(tmp, k) - } - n.Connections = tmp -} - -// RemoveConnection : -func (n *Network) RemoveConnection(id string) { - - delete(n.lazyConnectionsMap(), id) - tmp := n.Connections[:0] - for k := range n.connectionsMap { - tmp = append(tmp, k) - } - n.Connections = tmp -} - -// Merge : -func (n *Network) Merge(in *Network) *Network { - - result := *n - - if in.ID != "" { - result.ID = in.ID - } - if in.Name != "" { - result.Name = in.Name - } - if in.Interfaces != nil { - result.Interfaces = in.Interfaces - } - if in.Connections != nil { - result.Connections = in.Connections - } - if in.AddressRange != "" { - result.AddressRange = in.AddressRange - } - - return &result -} - -// Stub : -func (n *Network) Stub() *NetworkListStub { - return &NetworkListStub{ - ID: n.ID, - Name: n.Name, - AddressRange: n.AddressRange, - InterfacesCount: len(n.Interfaces), - ConnectionsCount: len(n.Connections), - CreatedAt: n.CreatedAt, - UpdatedAt: n.UpdatedAt, - } -} - -// NetworkListStub : -type NetworkListStub struct { - ID string - Name string - AddressRange string - InterfacesCount int - ConnectionsCount int - CreatedAt time.Time - UpdatedAt time.Time -} - -// NetworkSpecificRequest : -type NetworkSpecificRequest struct { - NetworkID string - - QueryOptions -} - -// SingleNetworkResponse : -type SingleNetworkResponse struct { - Network *Network - - Response -} - -// NetworkUpsertRequest : -type NetworkUpsertRequest struct { - Network *Network - - WriteRequest -} - -// NetworkDeleteRequest : -type NetworkDeleteRequest struct { - NetworkIDs []string - - WriteRequest -} - -// NetworkListRequest : -type NetworkListRequest struct { - QueryOptions -} - -// NetworkListResponse : -type NetworkListResponse struct { - Items []*NetworkListStub - - Response -} diff --git a/drago/structs/node.go b/drago/structs/node.go deleted file mode 100644 index be5ac75..0000000 --- a/drago/structs/node.go +++ /dev/null @@ -1,299 +0,0 @@ -package structs - -import ( - "fmt" - "time" -) - -const ( - NodeStatusInit = "initializing" - NodeStatusReady = "ready" - NodeStatusDown = "down" -) - -// Node : -type Node struct { - ID string - SecretID string - Name string - AdvertiseAddress string - Status string - Interfaces []string - Connections []string - Meta map[string]string - CreatedAt time.Time - UpdatedAt time.Time - - // Underlying struct for efficiently adding/removing interfaces and connections. - // Always use the lazyInterfacesMap() and lazyConnectionsMap() methods for accessing them. - interfacesMap map[string]struct{} - connectionsMap map[string]struct{} -} - -// Validate validates a structs.Node object -func (n *Node) Validate() error { - - if !IsValidNodeStatus(n.Status) { - return fmt.Errorf("invalid node status") - } - - return nil -} - -// IsValidNodeStatus returns true if the status passed as argument -// corresponds to a valid node status. Otherwise returns false. -func IsValidNodeStatus(s string) bool { - - valid := map[string]interface{}{ - NodeStatusInit: nil, - NodeStatusReady: nil, - NodeStatusDown: nil, - } - - if _, ok := valid[s]; !ok { - return false - } - - return true -} - -// Merge : -func (n *Node) Merge(in *Node) *Node { - - result := *n - - if in.ID != "" { - result.ID = in.ID - } - if in.SecretID != "" { - result.SecretID = in.SecretID - } - if in.Name != "" { - result.Name = in.Name - } - if in.AdvertiseAddress != "" { - result.AdvertiseAddress = in.AdvertiseAddress - } - if in.Interfaces != nil { - result.Interfaces = in.Interfaces - } - if in.Status != "" { - result.Status = in.Status - } - - return &result -} - -// If the node's interfacesMap was already initialized, return it. -// Otherwise initialize and synchronize it with the node interfaces slice. -func (n *Node) lazyInterfacesMap() map[string]struct{} { - - if n.interfacesMap != nil { - return n.interfacesMap - } - - n.interfacesMap = map[string]struct{}{} - for _, iface := range n.Interfaces { - n.interfacesMap[iface] = struct{}{} - } - return n.interfacesMap -} - -// UpsertInterface : -func (n *Node) UpsertInterface(id string) { - n.lazyInterfacesMap()[id] = struct{}{} - tmp := n.Interfaces[:0] - for k := range n.interfacesMap { - tmp = append(tmp, k) - } - n.Interfaces = tmp -} - -// RemoveInterface : -func (n *Node) RemoveInterface(id string) { - - delete(n.lazyInterfacesMap(), id) - tmp := n.Interfaces[:0] - for k := range n.interfacesMap { - tmp = append(tmp, k) - } - n.Interfaces = tmp -} - -// If the node's connectionsMap was already initialized, return it. -// Otherwise initialize and synchronize it with the node connections slice. -func (n *Node) lazyConnectionsMap() map[string]struct{} { - - if n.connectionsMap != nil { - return n.connectionsMap - } - - n.connectionsMap = map[string]struct{}{} - for _, conn := range n.Connections { - n.connectionsMap[conn] = struct{}{} - } - return n.connectionsMap -} - -// UpsertConnection : -func (n *Node) UpsertConnection(id string) { - n.lazyConnectionsMap()[id] = struct{}{} - tmp := n.Connections[:0] - for k := range n.connectionsMap { - tmp = append(tmp, k) - } - n.Connections = tmp -} - -// RemoveConnection : -func (n *Node) RemoveConnection(id string) { - - delete(n.lazyConnectionsMap(), id) - tmp := n.Connections[:0] - for k := range n.connectionsMap { - tmp = append(tmp, k) - } - n.Connections = tmp -} - -// Stub : -func (n *Node) Stub() *NodeListStub { - return &NodeListStub{ - ID: n.ID, - Name: n.Name, - AdvertiseAddress: n.AdvertiseAddress, - Status: n.Status, - InterfacesCount: len(n.Interfaces), - ConnectionsCount: len(n.Connections), - Meta: n.Meta, - CreatedAt: n.CreatedAt, - UpdatedAt: n.UpdatedAt, - } -} - -// NodeListStub : -type NodeListStub struct { - ID string - Name string - AdvertiseAddress string - Status string - InterfacesCount int - ConnectionsCount int - Meta map[string]string - CreatedAt time.Time - UpdatedAt time.Time -} - -// NodeSpecificRequest : -type NodeSpecificRequest struct { - NodeID string - SecretID string - - QueryOptions -} - -// SingleNodeResponse : -type SingleNodeResponse struct { - Node *Node - - Response -} - -// NodePreregisterRequest : -type NodePreregisterRequest struct { - Node *Node - - WriteRequest -} - -// NodePreregisterResponse : -type NodePreregisterResponse struct { - Node *Node - - Response -} - -// NodeRegisterRequest : -type NodeRegisterRequest struct { - Node *Node - - WriteRequest -} - -// Validate validates a structs.NodeRegisterRequest -func (r *NodeRegisterRequest) Validate() error { - - if r.Node == nil { - return fmt.Errorf("missing node") - } - if r.Node.ID == "" { - return fmt.Errorf("missing node ID") - } - if r.Node.Name == "" { - return fmt.Errorf("missing node name") - } - if r.Node.SecretID == "" { - return fmt.Errorf("missing node secret ID") - } - - return nil -} - -// NodeUpdateStatusRequest : -type NodeUpdateStatusRequest struct { - NodeID string - Status string - AdvertiseAddress string - Meta map[string]string - WriteRequest -} - -// NodeUpdateResponse is used to update nodes -type NodeUpdateResponse struct { - Servers []string - - Response -} - -// NodeListRequest : -type NodeListRequest struct { - QueryOptions -} - -// NodeListResponse : -type NodeListResponse struct { - Items []*NodeListStub - - Response -} - -// NodeInterfacesResponse : -type NodeInterfacesResponse struct { - Items []*Interface - - Response -} - -// NodeInterfaceUpdateRequest : -type NodeInterfaceUpdateRequest struct { - NodeID string - Interfaces []*Interface - - WriteRequest -} - -// NodeJoinNetworkRequest : -type NodeJoinNetworkRequest struct { - NodeID string - NetworkID string - - WriteRequest -} - -// NodeLeaveNetworkRequest : -type NodeLeaveNetworkRequest struct { - NodeID string - NetworkID string - - WriteRequest -} diff --git a/drago/structs/status.go b/drago/structs/status.go deleted file mode 100644 index 2f87132..0000000 --- a/drago/structs/status.go +++ /dev/null @@ -1,8 +0,0 @@ -package structs - -// StatusVersionResponse ... -type StatusVersionResponse struct { - Version string - - QueryOptions -} diff --git a/drago/structs/structs.go b/drago/structs/structs.go deleted file mode 100644 index 0637ab2..0000000 --- a/drago/structs/structs.go +++ /dev/null @@ -1,66 +0,0 @@ -package structs - -import ( - "context" - - "github.com/pkg/errors" - "github.com/seashell/drago/pkg/validator" -) - -type Filters map[string][]string - -func (f Filters) Get(k string) []string { - if v, ok := f[k]; ok { - return v - } - return []string{} -} - -func (f Filters) Add(k, v string) { - f[k] = append(f[k], []string{v}...) -} - -// QueryOptions contains information that is common to all read requests. -type QueryOptions struct { - AuthToken string - Filters Filters -} - -// WriteRequest contains information that is common to all write requests. -type WriteRequest struct { - AuthToken string -} - -// Response contains information that is common to all responses. -type Response struct { -} - -// GenericRequest is used to request where no -// specific information is needed. -type GenericRequest struct { - QueryOptions -} - -// GenericResponse is used to respond to a request where no -// specific response information is needed. -type GenericResponse struct { - Response -} - -// Validate validates a struct/DTO, returning an error in case its -// attributes are not compliant with the allowed values. -func Validate(s interface{}) error { - - ctx := context.Background() - v, err := validator.New(ctx) - if err != nil { - return err - } - - err = v.Validate(s) - if err != nil { - return errors.Wrap(errors.New("invalid struct"), err.Error()) - } - - return nil -} diff --git a/drago/test/acl_test.go b/drago/test/acl_test.go deleted file mode 100644 index 97ee06a..0000000 --- a/drago/test/acl_test.go +++ /dev/null @@ -1,212 +0,0 @@ -package test - -import ( - "context" - "fmt" - "testing" - - drago "github.com/seashell/drago/drago" - mock "github.com/seashell/drago/drago/mock" - inmem "github.com/seashell/drago/drago/state/inmem" - structs "github.com/seashell/drago/drago/structs" - uuid "github.com/seashell/drago/pkg/uuid" -) - -var config = &drago.Config{} - -func TestACLBootstrap(t *testing.T) { - - ctx := context.TODO() - - authHandler := &mock.AuthHandler{} - - state := inmem.NewStateRepository(nil) - service := drago.NewACLService(config, state, authHandler) - - t.Run("Once", func(t *testing.T) { - var out structs.ACLTokenUpsertResponse - err := service.BootstrapACL(&structs.ACLBootstrapRequest{}, &out) - if err != nil { - t.Fatal(err) - } - }) - - b.Clear() - - t.Run("Twice", func(t *testing.T) { - var out structs.ACLTokenUpsertResponse - service.BootstrapACL(&structs.ACLBootstrapRequest{}, &out) - err := service.BootstrapACL(&structs.ACLBootstrapRequest{}, &out) - if err == nil { - t.Fatal(err) - } - }) -} - -func TestACLTokens(t *testing.T) { - - ctx := context.TODO() - - authHandler := &mock.AuthHandler{} - - state := inmem.NewStateRepository(nil) - service := drago.NewACLService(config, state, authHandler) - - var out structs.ACLTokenUpsertResponse - - t.Run("Create", func(t *testing.T) { - _, err := service.UpsertToken(&structs.ACLTokenUpsertRequest{ - Name: "my-token-1", - Type: "management", - Policies: nil, - }, &out) - if err != nil { - t.Fatal(err) - } - - _, err = service.UpsertToken(&structs.ACLTokenUpsertRequest{ - Name: "my-token-2", - Type: "foo", - Policies: nil, - }, &out) - if err == nil { - t.Fatal(err) - } - - tokens, _ := state.ACLTokens(ctx) - if len(tokens) != 1 { - t.Fatal("failed to create token") - } - }) - - state.Clear() - - t.Run("GetByID", func(t *testing.T) { - - newToken := &structs.ACLToken{ - ID: uuid.Generate(), - Secret: uuid.Generate(), - Name: "some-token", - Type: "management", - } - - state.UpsertACLToken(ctx, newToken) - - var out structs.SingleACLTokenResponse - err := service.GetToken(&structs.ACLTokenSpecificRequest{ID: newToken.ID}, &out) - if err != nil { - t.Fatalf("failed to get token by id: %v", err) - } - if out.ID != *id { - t.Fatalf("failed to get token by id: %v", err) - } - }) - - state.Clear() - - t.Run("List", func(t *testing.T) { - - state.UpsertACLToken(ctx, &structs.ACLToken{ID: uuid.Generate(), Name: "foo", Type: "management"}) - state.UpsertACLToken(ctx, &structs.ACLToken{ID: uuid.Generate(), Name: "bar", Type: "client"}) - - var out structs.ACLTokenListResponse - err := service.ListTokens(&structs.ACLTokenListRequest{}, &out) - if err != nil { - t.Fatalf("failed to list tokens: %v", err) - } - if len(out.Items) != 2 { - t.Fatalf("failed to create tokens: %v", err) - } - }) -} - -func TestACLAuthorization(t *testing.T) { - - ctx := context.TODO() - - authHandler := &mock.AuthHandler{} - - state := inmem.NewStateRepository(nil) - service := drago.NewACLService(config, state, authHandler) - - // Create policies - state.UpsertACLPolicy(ctx, anonymousPolicy()) - state.UpsertACLPolicy(ctx, servicePolicy()) - state.UpsertACLPolicy(ctx, devicePolicy()) - - // Create new management token - - t1 := &structs.ACLToken{ID: uuid.Generate(), Name: "admin-1", Type: "management", Secret: uuid.Generate()} - t2 := &structs.ACLToken{ID: uuid.Generate(), Name: "device-1", Type: "client", Secret: uuid.Generate(), Policies: []string{"device"}} - t3 := &structs.ACLToken{ID: uuid.Generate(), Name: "service-1", Type: "client", Secret: uuid.Generate(), Policies: []string{"service"}} - - state.UpsertACLToken(ctx, t1) - state.UpsertACLToken(ctx, t2) - state.UpsertACLToken(ctx, t3) - - // Get token secret - token, _ := state.ACLTokenByID(ctx, t2.ID) - - t.Run("Resolve", func(t *testing.T) { - var out structs.ResolveACLTokenResponse - err := service.ResolveToken(&structs.ResolveACLTokenRequest{Secret: token.Secret}, &out) - if err != nil { - t.Fatalf("failed to resolve token: %v", err) - } - if out.ID != t2.ID { - t.Fatalf("failed to resolve token: retrieved wrong token") - } - }) - - t.Run("Evaluate Policies", func(t *testing.T) { - var out structs.ResolveACLTokenResponse - out, _ := service.ResolveToken(&structs.ACLResolveTokenRequest{Secret: token.Secret}, &out) - for _, name := range out.Policies { - p, err := state.ACLPolicyByName(ctx, name) - if err != nil { - t.Fatalf("failed to evaluate policies: %v", err) - } - } - }) - -} - -func dumpBackendState(b *inmem.Backend) { - fmt.Println("\n------------ Backend dump") - dumpCh := b.Dump() - for s := range dumpCh { - fmt.Println(s) - } - fmt.Println("------------") - fmt.Println("") -} - -func anonymousPolicy() *structs.ACLPolicy { - return &structs.ACLPolicy{ - Name: "anonymous", - Rules: []*structs.ACLPolicyRule{ - {"network", "*", []string{"write"}}, - {"host", "*", []string{"write"}}, - }, - } -} - -func servicePolicy() *structs.ACLPolicy { - return &structs.ACLPolicy{ - Name: "service", - Rules: []*structs.ACLPolicyRule{ - {"network", "*", []string{"write"}}, - {"host", "*", []string{"write"}}, - }, - } -} - -func devicePolicy() *structs.ACLPolicy { - return &structs.ACLPolicy{ - Name: "device", - Rules: []*structs.ACLPolicyRule{ - {"network", "*", []string{"write"}}, - {"host", "*", []string{"write"}}, - }, - } -} diff --git a/drago/test/interface_test.go b/drago/test/interface_test.go deleted file mode 100644 index 56e5404..0000000 --- a/drago/test/interface_test.go +++ /dev/null @@ -1 +0,0 @@ -package test diff --git a/drago/test/node_test.go b/drago/test/node_test.go deleted file mode 100644 index 1c47ea0..0000000 --- a/drago/test/node_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package test - -import "testing" - -func TestNodeRegister(t *testing.T) { - -} - -func TestNodeStateUpdate(t *testing.T) { - -} - -func TestNodeInterfacesUpdate(t *testing.T) { - -} -func TestNodeJoinNetwork(t *testing.T) { - -} - -func TestNodeLeaveNetwork(t *testing.T) { - -} diff --git a/dragopher.png b/dragopher.png new file mode 100644 index 0000000..a7bd264 Binary files /dev/null and b/dragopher.png differ diff --git a/example/client1.hcl b/example/client1.hcl deleted file mode 100644 index 6e8c48b..0000000 --- a/example/client1.hcl +++ /dev/null @@ -1,18 +0,0 @@ -data_dir = "/tmp/drago" -bind_addr = "0.0.0.0" - -name = "node-1" - -advertise { - peer = "192.168.100.13" -} - -server { - enabled = false -} - -client { - enabled = true - servers = ["192.168.99.1:8081"] - wireguard_path = "./boringtun" -} diff --git a/example/client2.hcl b/example/client2.hcl deleted file mode 100644 index 12dc0c6..0000000 --- a/example/client2.hcl +++ /dev/null @@ -1,21 +0,0 @@ -data_dir = "/tmp/drago" -bind_addr = "0.0.0.0" - -name = "node-2" - -advertise { - peer = "192.168.100.14" -} - -server { - enabled = false -} - -client { - enabled = true - servers = ["192.168.100.12:8081"] - wireguard_path = "./wireguard" - meta = { - test_meta = "test_meta_value" - } -} diff --git a/example/server.hcl b/example/server.hcl deleted file mode 100644 index 84c0b78..0000000 --- a/example/server.hcl +++ /dev/null @@ -1,11 +0,0 @@ -data_dir = "/tmp/drago" -bind_addr = "0.0.0.0" -ui = true - -server { - enabled = true -} - -client { - enabled = false -} diff --git a/go.mod b/go.mod deleted file mode 100644 index d99382b..0000000 --- a/go.mod +++ /dev/null @@ -1,24 +0,0 @@ -module github.com/seashell/drago - -go 1.16 - -require ( - github.com/caarlos0/env v3.5.0+incompatible - github.com/dimiro1/banner v1.1.0 - github.com/fatih/color v1.10.0 - github.com/go-playground/validator/v10 v10.4.1 - github.com/hashicorp/go-cleanhttp v0.5.2 - github.com/hashicorp/hcl/v2 v2.9.1 - github.com/imdario/mergo v0.3.12 - github.com/joho/godotenv v1.3.0 - github.com/pkg/errors v0.9.1 - github.com/rodaine/table v1.0.1 - github.com/sirupsen/logrus v1.8.1 - github.com/spf13/pflag v1.0.5 - github.com/vishvananda/netlink v1.1.1-0.20200604160102-dc0e1b988c57 - github.com/vmihailenco/msgpack v4.0.4+incompatible - go.etcd.io/bbolt v1.3.5 - go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 - go.uber.org/zap v1.16.0 - golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 6770017..0000000 --- a/go.sum +++ /dev/null @@ -1,327 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= -github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2 h1:tjT4Jp4gxECvsJcYpAMtW2I3YqzBTPuB67OejxXs86s= -github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= -github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf h1:CAKfRE2YtTUIjjh1bkBtyYFaUT/WmOqsJjgtihT0vMI= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dimiro1/banner v1.1.0 h1:TSfy+FsPIIGLzaMPOt52KrEed/omwFO1P15VA8PMUh0= -github.com/dimiro1/banner v1.1.0/go.mod h1:tbL318TJiUaHxOUNN+jnlvFSgsh/RX7iJaQrGgOiTco= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c h1:Lh2aW+HnU2Nbe1gqD9SOJLJxW1jBMmQOktN2acDyJk8= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/hcl/v2 v2.9.1 h1:eOy4gREY0/ZQHNItlfuEZqtcQbXIxzojlP301hDpnac= -github.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= -github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw= -github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -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/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0= -github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= -github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= -github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= -github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg= -github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= -github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= -github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= -github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/vishvananda/netlink v1.1.1-0.20200604160102-dc0e1b988c57 h1:hxVoLocxF7hR+ydB7IP4nx+AUQYbuAheSPMDEkc05SY= -github.com/vishvananda/netlink v1.1.1-0.20200604160102-dc0e1b988c57/go.mod h1:FSQhuTO7eHT34mPzX+B04SUAjiqLxtXs1et0S6l9k4k= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= -github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= -github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8= -golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b h1:l4mBVCYinjzZuR5DtxHuBD6wyd4348TGiavJ5vLrhEc= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b/go.mod h1:UdS9frhv65KTfwxME1xE8+rHYoFpbm36gOud1GhBe9c= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/init/README.md b/init/README.md deleted file mode 100644 index 7e6a12b..0000000 --- a/init/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# init - -The `init` folder contains system init (systemd, upstart, sysv) and process manager/supervisor (runit, supervisord) configs for multiple platforms. - -## Conventions - -On unix systems agent configurations shall be kept under `/etc/drago` and data under `/var/lib/drago/`. These directories are assumed to exist, and the Drago binary is assumed to be located at `/usr/bin/drago`. - -## Agent configuration - -The following configuration files are provided as examples: - - * `demo/server.yml` - * `demo/client.yml` - -Place one of these under `/etc/drago.d` depending on the host's role. You should use `server.yml` to configure a host as a server or `client.yml` to configure a host as a client. - -## systemd - -On systems using `systemd`, the basic unit file under `systemd/drago.service` starts and stops the drago agent. Place it under `/etc/systemd/system/drago.service`. - -You can control Drago with `systemctl start|stop|restart drago`. \ No newline at end of file diff --git a/init/systemd/drago.service b/init/systemd/drago.service deleted file mode 100644 index a5d30ca..0000000 --- a/init/systemd/drago.service +++ /dev/null @@ -1,21 +0,0 @@ -[Unit] -Description=Drago -Wants=network-online.target -After=network-online.target - -[Service] -ExecReload=/bin/kill -HUP $MAINPID -ExecStart=/usr/local/bin/drago agent --config /etc/drago.d -KillMode=process -KillSignal=SIGINT -LimitNOFILE=65536 -LimitNPROC=infinity -Restart=on-failure -RestartSec=2 -StartLimitBurst=3 -StartLimitIntervalSec=10 -TasksMax=infinity -OOMScoreAdjust=-1000 - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/docs/favicon.ico b/logo.png similarity index 99% rename from docs/favicon.ico rename to logo.png index b5fa034..45bf949 100644 Binary files a/docs/favicon.ico and b/logo.png differ diff --git a/ui/src/assets/icons/logo.svg b/logo.svg similarity index 100% rename from ui/src/assets/icons/logo.svg rename to logo.svg diff --git a/main.go b/main.go deleted file mode 100644 index 2af1f2e..0000000 --- a/main.go +++ /dev/null @@ -1,144 +0,0 @@ -//go:generate yarn --cwd ./ui -//go:generate yarn --cwd ./ui build -//go:generate touch ./ui/build/.keep - -package main - -import ( - "context" - "embed" - "fmt" - "io/fs" - "net/http" - "os" - "os/signal" - "runtime" - "runtime/debug" - - command "github.com/seashell/drago/command" - cli "github.com/seashell/drago/pkg/cli" - version "github.com/seashell/drago/version" -) - -//go:embed ui/build/* -var uiefs embed.FS - -// Credits to https://github.com/pulumi/pulumi/blob/master/pkg/cmd/pulumi/main.go -func panicHandler() { - if panicPayload := recover(); panicPayload != nil { - - stack := string(debug.Stack()) - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "================================================================================") - fmt.Fprintln(os.Stderr, "Drago has encountered a fatal error. This is a bug!") - fmt.Fprintln(os.Stderr, "We would appreciate a report: https://github.com/seashell/drago/issues/") - fmt.Fprintln(os.Stderr, "Please provide all of the below text in your report.") - fmt.Fprintln(os.Stderr, "================================================================================") - - fmt.Fprintf(os.Stderr, "Drago Version: %s\n", version.Version) - fmt.Fprintf(os.Stderr, "Go Version: %s\n", runtime.Version()) - fmt.Fprintf(os.Stderr, "Go Compiler: %s\n", runtime.Compiler) - fmt.Fprintf(os.Stderr, "Architecture: %s\n", runtime.GOARCH) - fmt.Fprintf(os.Stderr, "Operating System: %s\n", runtime.GOOS) - fmt.Fprintf(os.Stderr, "Panic: %s\n\n", panicPayload) - fmt.Fprintln(os.Stderr, stack) - } -} - -func init() { - os.Setenv("TZ", "UTC") -} - -func main() { - os.Exit(run(os.Args[1:])) -} - -func run(args []string) int { - - cli := setupCLI() - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - - signalCh := make(chan os.Signal) - signal.Notify(signalCh, os.Interrupt) - - go func() { - <-signalCh - fmt.Fprintf(os.Stderr, "Received signal. Interrupting...\n") - cancel() - }() - - defer panicHandler() - - code, err := cli.Run(ctx, args) - if err != nil { - fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) - return 1 - } - - return code -} - -func setupCLI() *cli.CLI { - - ui := &cli.SimpleUI{ - Reader: os.Stdin, - Writer: os.Stdout, - ErrorWriter: os.Stderr, - } - - subfs, err := fs.Sub(uiefs, "ui/build") - if err != nil { - panic(err) - } - - uifs := http.FS(subfs) - - cli := cli.New(&cli.Config{ - Name: "drago", - Commands: map[string]cli.Command{ - "agent": &command.AgentCommand{UI: ui, StaticFS: uifs}, - "agent-info": &command.AgentInfoCommand{UI: ui}, - "acl": &command.ACLCommand{UI: ui}, - "acl bootstrap": &command.ACLBootstrapCommand{UI: ui}, - "acl token": &command.ACLTokenCommand{UI: ui}, - "acl token create": &command.ACLTokenCreateCommand{UI: ui}, - "acl token delete": &command.ACLTokenDeleteCommand{UI: ui}, - "acl token info": &command.ACLTokenInfoCommand{UI: ui}, - "acl token list": &command.ACLTokenListCommand{UI: ui}, - "acl token self": &command.ACLTokenSelfCommand{UI: ui}, - "acl token update": &command.ACLTokenUpdateCommand{UI: ui}, - "acl policy": &command.ACLPolicyCommand{UI: ui}, - "acl policy apply": &command.ACLPolicyApplyCommand{UI: ui}, - "acl policy delete": &command.ACLPolicyDeleteCommand{UI: ui}, - "acl policy info": &command.ACLPolicyInfoCommand{UI: ui}, - "acl policy list": &command.ACLPolicyListCommand{UI: ui}, - "network": &command.NetworkCommand{UI: ui}, - "network create": &command.NetworkCreateCommand{UI: ui}, - "network delete": &command.NetworkDeleteCommand{UI: ui}, - "network info": &command.NetworkInfoCommand{UI: ui}, - "network list": &command.NetworkListCommand{UI: ui}, - "node": &command.NodeCommand{UI: ui}, - "node status": &command.NodeStatusCommand{UI: ui}, - "node join": &command.NodeJoinCommand{UI: ui}, - "node leave": &command.NodeLeaveCommand{UI: ui}, - "interface": &command.InterfaceCommand{UI: ui}, - "interface list": &command.InterfaceListCommand{UI: ui}, - "interface update": &command.InterfaceUpdateCommand{UI: ui}, - "connection": &command.ConnectionCommand{UI: ui}, - "connection list": &command.ConnectionListCommand{UI: ui}, - "connection create": &command.ConnectionCreateCommand{UI: ui}, - "connection delete": &command.ConnectionDeleteCommand{UI: ui}, - "connection update": &command.ConnectionUpdateCommand{UI: ui}, - "connection update-rules": &command.ConnectionUpdateRulesCommand{UI: ui}, - // "system": &command.SystemCommand{UI: ui}, - // "system gc": &command.SystemGCCommand{UI: ui}, - "ui": &command.UICommand{UI: ui}, - "version": &command.VersionCommand{UI: ui}, - }, - Version: version.GetVersion().VersionNumber(), - }) - - return cli -} diff --git a/pkg/acl/README.md b/pkg/acl/README.md deleted file mode 100644 index f17482a..0000000 --- a/pkg/acl/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# acl -Generic implementation of an Access Control List system for enforcing authorization across different resources. While originally meant for utilization in the [Drago](https://github.com/seashell/drago) project, this ACL system was built with flexibility in mind, and should be straightforward to configure and integrate into other codebases. - -Although different in many aspects, this implementation is largely based on the ACL systems built by Hashicorp for Nomad and Consul. It also took some inspiration from GCP's ACL system. - -## Concepts -* **Resource**: any type of resource that might require authorization. Example: book. - -* **Instance**: an uniquely identifiable instance of a resource. Example: "The lord of the rings" - -* **Capability**: an operation that can be performed in the context of a resource or instance. In other words, a capability is an allowed operation. From an acessor perspective, it makes sense to talk in terms of capabilities, whereas from a resource perspective, its more usual to use the term operation. - -* **Policy**: a named set of rules for enabling/disabling capabilities on one or more instances. - -* **Rule**: a combination of a filter for targetting specific resources/instances, as well as a set of capabilities that should be enabled on them. - -* **Alias**: a convenience shorthand for representing multiple capabilities with a single string. - -* **Secret**: an object in possession of the acessor which can be associated to a set of policies. A secret can be anything, from an opaque tokens to a JWT, as long as it is possible to resolve it. - -* **ACL**: Object containing a compiled version of the access rules associated with a secret, and that can be used to find out whether a specific operation can be performed on a resource/instance. - - -## Usage - -First one needs to define the resources requiring protection, as well as the available operations/capabilities. Let's assume we're building a backend for an online library, and would like to limit access to our books API. - -We start by defining the capabilities: - -```go -const ( - capBookRead = "read" - capBookWrite = "write" - capBookList = "list" -) -``` - -Then we create a `acl.Config` object: - -```go -var c = &Config{ - Resources: map[string]*Resource{ - "book": &Resource{ - Name: "book", - Capabilities: map[string]*Capability{ - capBookRead: {capBookRead, "Allows a book to be read"}, - capBookWrite: {capBookWrite, "Allows a book to be written"}, - capBookList: {capBookList, "Allows books to be listed"}, - }, - Aliases: map[string]*CapabilityAlias{ - "write": {"read", []string{capBookWrite, capBookRead, capBookList}}, - "read": {"read", []string{capBookRead, capBookList}}, - }, - }, - }, -} -``` - -Note that besides the capabilities associated to the `book` resource, we have also defined aliases, which are basically shorthands for expressing more than one capability at once. Since our ACL system always expands all aliases prior to processing the actual capabilities, we can have an alias and a capability sharing the same name. - -Finally, we need to tell our ACL system how it can resolve policy names and secrets. In order to decouple the ACL system from the persistence layer, we make use of the `acl.Policy` and `acl.Token` interfaces for abstracting these two objects. - -Both functions can be set in the configuration struct, as follows: - -```go -c.ResolvePolicy = func(policy string) (acl.Policy, error) { - return someStorage.GetPolicyByName(policy) -} - -c.ResolveToken = func(secret string) (acl.Token, error) { - return someStorage.FindTokenBySecret(secret) -} -``` - -Now we can initialize our ACL: - -```go -// Initialize ACL -Initialize(c) -``` - -When we receive a request to perform an operation of any kind, say `write` on a specific `book`, we can build a new ACL object based on the secret contained in this request, and query the ACL if the operation is authorized or not: - -```go -acl, err := NewACLFromSecret("secret-in-the-request") -if err != nil { - panic(err) -} - -if !acl.IsAuthorized("book", "the-lord-of-the-rings-id", "write") { - return fmt.Errorf("not authorized") -} - -// Proceed normally with the operation. -... -``` - -## TODO: -- Benchmark time and memory overhead; \ No newline at end of file diff --git a/pkg/acl/acl.go b/pkg/acl/acl.go deleted file mode 100644 index b82b39a..0000000 --- a/pkg/acl/acl.go +++ /dev/null @@ -1,116 +0,0 @@ -package acl - -import ( - "context" - "fmt" - "path" - "strings" - - radix "github.com/seashell/drago/pkg/radix" -) - -const ( - capabilityDeny = "deny" -) - -// ACL is used to convert a set of policies into a structure that -// can be efficiently evaluated to determine if an action is allowed. -type ACL struct { - privileged bool - capabilities map[string]*radix.Tree -} - -// CheckAuthorized verifies whether the ACL is authorized to perform a specific action. -// If the ACL is not authorized, an error is returned, which provides more details. -// If an operation is not explicitly enabled in the ACL, it is forbidden by default. -func (a *ACL) CheckAuthorized(ctx context.Context, res string, path string, op string) error { - // A privileged ACL is able to do anything. - if a.privileged { - return nil - } - - capabilities, err := a.queryCapabilities(ctx, res, path) - if err != nil { - return err - } - - if capabilities.HasCapability(op) { - return nil - } - - return ErrUnauthorized -} - -// queryCapabilities searches the ACL for all rules matching the queried resource -// instance, and merges the capabilities they enable into one single capabilityMap, -// which can then be used to easily verify whether a capability is set or not. -func (a *ACL) queryCapabilities(ctx context.Context, resource string, instance string) (capMap, error) { - - capTree, ok := a.capabilities[resource] - if !ok { - return nil, ErrInvalidResource - } - - if v, found := capTree.Get(instance); found { - return v.(capMap), nil - } - - // Find all patterns in the capability tree - // matching the queried instance. - merged := make(capMap) - capTree.Walk(func(pattern string, raw interface{}) bool { - if matched, _ := path.Match(pattern, instance); matched { - for cap := range raw.(capMap) { - merged.AddCapability(cap) - } - return false - } - return true - }) - - return merged, nil -} - -// String builds a string representation of an ACL which -// can be useful for debugging purposes. -func (a *ACL) String() string { - s := "" - s += fmt.Sprintf("- management = %v\n", a.privileged) - for res, capTree := range a.capabilities { - s += fmt.Sprintf("-- %s capabilities\n", res) - capTree.Walk(func(k string, v interface{}) bool { - s += fmt.Sprintf(" %s%s --> ", k, strings.Repeat(" ", 18-len(k))) - for c := range v.(capMap) { - s += fmt.Sprintf("%s ", c) - } - s += fmt.Sprintf("\n") - return false - }) - } - return s -} - -// capMap is a map meant for making it easy/efficient to -// evaluate whether or not a given capability is enabled. If an -// entry exists in the map for a specific capability, it is enabled. -// Otherwise, it is not. -type capMap map[string]struct{} - -func (m capMap) AddCapability(cap string) { - m[cap] = struct{}{} -} - -func (m capMap) RemoveCapability(cap string) { - delete(m, cap) -} - -func (m capMap) HasCapability(cap string) bool { - _, found := m[cap] - return found -} - -func (m capMap) Clear() { - for cap := range m { - delete(m, cap) - } -} diff --git a/pkg/acl/acl_test.go b/pkg/acl/acl_test.go deleted file mode 100644 index 7f1ba4d..0000000 --- a/pkg/acl/acl_test.go +++ /dev/null @@ -1,281 +0,0 @@ -package acl - -import ( - "context" - "fmt" - "testing" -) - -const ( - capNamespaceReadX = "read-x" - capNamespaceReadY = "read-y" - capNamespaceWriteX = "write-x" - capNamespaceWriteY = "write-y" -) - -const ( - capNetworkRead = "read" - capNetworkWrite = "write" - capNetworkList = "list" -) - -const ( - capHostRead = "read" - capHostWrite = "write" - capHostList = "list" -) - -var repo *mockRepository -var ctx = context.TODO() - -type mockRepository struct { - tokens []*mockToken - policies []*mockPolicy -} - -func (r *mockRepository) FindTokenBySecret(s string) (Token, error) { - for _, t := range r.tokens { - if t.secret == s { - return t, nil - } - } - return nil, nil -} - -func (r *mockRepository) GetPolicyByName(n string) (Policy, error) { - for _, p := range r.policies { - if p.name == n { - return p, nil - } - } - return nil, fmt.Errorf("not found : %s", n) -} - -type mockToken struct { - privileged bool - secret string - policies []string -} - -func (t *mockToken) Policies() []string { - return t.policies -} - -func (t *mockToken) IsPrivileged() bool { - return t.privileged -} - -type mockPolicy struct { - name string - rules []*mockRule -} - -func (p *mockPolicy) Name() string { - return p.name -} - -func (p *mockPolicy) Rules() []Rule { - rules := []Rule{} - for _, r := range p.rules { - rules = append(rules, r) - } - return rules -} - -type mockRule struct { - resource string - path string - capabilities []string -} - -func (r *mockRule) Resource() string { - return r.resource -} - -func (r *mockRule) Path() string { - return r.path -} - -func (r *mockRule) Capabilities() []string { - return r.capabilities -} - -func setupMockRepo() { - repo = &mockRepository{ - tokens: []*mockToken{ - {true, "39076595-19a6-4582-b0d9-bb4a266fd48a", []string{""}}, - {false, "71036287-81d1-474a-b4d5-25c2ee6f57ae", []string{"policy1"}}, - {false, "54c06ace-7da6-443b-a5a2-05da5294fbd5", []string{"policy2"}}, - {false, "e690413b-827b-400e-bc38-92a4b1580eac", []string{"policy1", "policy2"}}, - }, - policies: []*mockPolicy{ - {"policy1", []*mockRule{ - {"namespace", "*", []string{"read"}}, - {"network", "*", []string{"read"}}, - {"host", "", []string{"read"}}, - }}, - {"policy2", []*mockRule{ - {"namespace", "*", []string{"write"}}, - {"network", "*", []string{"deny"}}, - {"host", "*", []string{"list"}}, - }}, - {"policy3", []*mockRule{ - {"network", "*", []string{"read"}}, - {"host", "*", []string{"write"}}, - }}, - {"anonymous", []*mockRule{ - {"network", "*", []string{"list"}}, - {"host", "*", []string{"list"}}, - }}, - }, - } -} - -func ACLResolverConfig() *ResolverConfig { - - model := NewModel() - model.Resource("namespace"). - Capabilities(capNamespaceReadX, capNamespaceReadY, capNamespaceWriteX, capNamespaceWriteY). - Alias("read", capNamespaceReadX, capNamespaceReadY). - Alias("write", capNamespaceWriteX, capNamespaceWriteY) - model.Resource("network"). - Capabilities(capNetworkRead, capNetworkWrite, capNetworkList). - Alias("read", capNetworkList, capHostRead). - Alias("write", capNetworkWrite, capNetworkRead, capNetworkWrite) - model.Resource("host"). - Capabilities(capNetworkRead, capNetworkWrite, capNetworkList). - Alias("read", capNetworkList, capHostRead). - Alias("write", capNetworkWrite, capNetworkRead, capNetworkWrite) - - config := &ResolverConfig{ - Model: model, - SecretResolver: func(ctx context.Context, s string) (Token, error) { - if s == "" { - return &mockToken{false, "", []string{"anonymous"}}, nil - } - return repo.FindTokenBySecret(s) - }, - PolicyResolver: func(ctx context.Context, p string) (Policy, error) { - return repo.GetPolicyByName(p) - }, - } - - return config -} - -func TestACL(t *testing.T) { - - setupMockRepo() - - t.Run("MissingConfigurations", func(t *testing.T) { - _, err := NewResolver(&ResolverConfig{}) - if err == nil { - t.Fatalf("expected error. have %v", err) - } - }) - - resolver, _ := NewResolver(ACLResolverConfig()) - - t.Run("ResolveSecret", func(t *testing.T) { - - secret := "54c06ace-7da6-443b-a5a2-05da5294fbd5" - - acl, err := resolver.ResolveSecret(ctx, secret) - if err != nil { - t.Fatal(err) - } - - if err := acl.CheckAuthorized(ctx, "namespace", "12345", "write-x"); err != nil { - t.Fatalf("%v", err) - } - - if err := acl.CheckAuthorized(ctx, "network", "12345", "write"); err == nil { - t.Fatalf("%v", err) - } - - if err := acl.CheckAuthorized(ctx, "host", "12345", "write"); err == nil { - t.Fatalf("%v", err) - } - }) - - t.Run("InvalidSecret", func(t *testing.T) { - - secret := "foobar" - - _, err := resolver.ResolveSecret(ctx, secret) - if err == nil { - t.Fatal(err) - } - }) - - t.Run("PrivilegedToken", func(t *testing.T) { - - secret := "39076595-19a6-4582-b0d9-bb4a266fd48a" - - acl, err := resolver.ResolveSecret(ctx, secret) - if err != nil { - t.Fatal(err) - } - - if err := acl.CheckAuthorized(ctx, "namespace", "12345", "write-x"); err != nil { - t.Fatalf("%v", err) - } - - if err := acl.CheckAuthorized(ctx, "network", "12345", "write"); err != nil { - t.Fatalf("%v", err) - } - - if err := acl.CheckAuthorized(ctx, "host", "12345", "write"); err != nil { - t.Fatalf("%v", err) - } - }) - - t.Run("InvalidResource", func(t *testing.T) { - secret := "54c06ace-7da6-443b-a5a2-05da5294fbd5" - acl, err := resolver.ResolveSecret(ctx, secret) - if err != nil { - t.Fatal(err) - } - if err := acl.CheckAuthorized(ctx, "foo", "bar", "write-x"); err == nil { - t.Fatalf("expected error. have %v", err) - } - }) - - t.Run("AnonymousToken", func(t *testing.T) { - - secret := "" - - acl, err := resolver.ResolveSecret(ctx, secret) - if err != nil { - t.Fatal(err) - } - - if err := acl.CheckAuthorized(ctx, "namespace", "12345", "write-x"); err == nil { - t.Fatalf("%v", err) - } - - if err := acl.CheckAuthorized(ctx, "network", "12345", "write"); err == nil { - t.Fatalf("%v", err) - } - - if err := acl.CheckAuthorized(ctx, "host", "12345", "list"); err != nil { - t.Fatalf("%v", err) - } - }) - - t.Run("StringDump", func(t *testing.T) { - - secret := "54c06ace-7da6-443b-a5a2-05da5294fbd5" - - acl, err := resolver.ResolveSecret(ctx, secret) - if err != nil { - t.Fatal(err) - } - - str := acl.String() - if str == "" { - t.Fatal(err) - } - }) - -} diff --git a/pkg/acl/config.go b/pkg/acl/config.go deleted file mode 100644 index d8c6403..0000000 --- a/pkg/acl/config.go +++ /dev/null @@ -1,45 +0,0 @@ -package acl - -import ( - "github.com/seashell/drago/pkg/log" -) - -// ResolverConfig contains configurations for an ACL Resolver. -type ResolverConfig struct { - Logger log.Logger - Model *Model - SecretResolver SecretResolverFunc - PolicyResolver PolicyResolverFunc -} - -// DefaultResolverConfig returns default configurations -// for an ACL Resolver. -func DefaultResolverConfig() *ResolverConfig { - return &ResolverConfig{ - Logger: &simpleLogger{ - fields: map[string]interface{}{}, - options: log.LoggerOptions{ - Prefix: "acl", - Level: levelInfo, - }, - }, - } -} - -// Merge merges two ResolverConfig structs, returning the result. -func (c *ResolverConfig) Merge(in *ResolverConfig) *ResolverConfig { - res := *c - if in.Logger != nil { - res.Logger = in.Logger - } - if in.Model != nil { - res.Model = in.Model - } - if in.SecretResolver != nil { - res.SecretResolver = in.SecretResolver - } - if in.PolicyResolver != nil { - res.PolicyResolver = in.PolicyResolver - } - return &res -} diff --git a/pkg/acl/errors.go b/pkg/acl/errors.go deleted file mode 100644 index c3336cd..0000000 --- a/pkg/acl/errors.go +++ /dev/null @@ -1,60 +0,0 @@ -package acl - -import ( - "errors" -) - -const ( - errTokenNotFound = "token not found" - errPolicyNotFound = "policy not found" - errMissingModel = "missing model" - errMissingSecretResolver = "missing secret resolver" - errMissingPolicyResolver = "missing policy resolver" - errResolvingSecret = "error resolving secret" - errResolvingPolicy = "error resolving policy" - errInvalidResource = "invalid resource" - errInvalidOperation = "invalid operation" - errNotAuthorized = "not authorized" -) - -var ( - // ErrTokenNotFound is returned when a token is not found - // by the resolver function. - ErrTokenNotFound = errors.New(errTokenNotFound) - - // ErrPolicyNotFound is returned when a policy is not found - // by the resolver function. - ErrPolicyNotFound = errors.New(errPolicyNotFound) - - // ErrMissingModel is returned when no Model is set in the Resolver - // configuration. - ErrMissingModel = errors.New(errMissingModel) - - // ErrMissingSecretResolver is returned when no SecretResolverFunc - // is set in the Resolver configuration. - ErrMissingSecretResolver = errors.New(errMissingSecretResolver) - - // ErrMissingPolicyResolver is returned when no PolicyResolverFunc - // is set in the ACL configuration. - ErrMissingPolicyResolver = errors.New(errMissingPolicyResolver) - - // ErrResolvingSecret is returned when an error occurs when resolving - // a secret. - ErrResolvingSecret = errors.New(errResolvingSecret) - - // ErrResolvingPolicy is returned when an error occurs when resolving - // a policy. - ErrResolvingPolicy = errors.New(errResolvingPolicy) - - // ErrUnauthorized is returned when the ACL does not have the - // authorization to perform the requested operation on the specified resource. - ErrUnauthorized = errors.New(errNotAuthorized) - - // ErrInvalidResource is returned when the resource being queried - // is not properly configured in the ACL system. - ErrInvalidResource = errors.New(errInvalidResource) - - // ErrInvalidOperation is returned when the operation being queried - // is not properly configured in the ACL system. - ErrInvalidOperation = errors.New(errInvalidOperation) -) diff --git a/pkg/acl/logger.go b/pkg/acl/logger.go deleted file mode 100644 index 49e6fe5..0000000 --- a/pkg/acl/logger.go +++ /dev/null @@ -1,97 +0,0 @@ -package acl - -import ( - "fmt" - - "github.com/imdario/mergo" - "github.com/seashell/drago/pkg/log" -) - -const ( - levelDebug = "DEBUG" - levelInfo = "INFO" - levelWarn = "WARN" - levelError = "ERROR" - levelFatal = "FATAL" - levelPanic = "PANIC" -) - -var levels = map[string]int{ - levelDebug: 5, - levelInfo: 4, - levelWarn: 3, - levelError: 2, - levelFatal: 1, - levelPanic: 0, -} - -type simpleLogger struct { - name string - fields log.Fields - options log.LoggerOptions -} - -func (l simpleLogger) Logf(level string, format string, args ...interface{}) { - if l.isLevelEnabled(level) { - fmt.Printf("%s%s", l.options.Prefix, fmt.Sprintf(format, args...)) - } -} - -func (l simpleLogger) Debugf(format string, args ...interface{}) { - l.Logf(levelDebug, format, args...) -} - -func (l simpleLogger) Infof(format string, args ...interface{}) { - l.Logf(levelInfo, format, args...) -} - -func (l simpleLogger) Warnf(format string, args ...interface{}) { - l.Logf(levelWarn, format, args...) -} - -func (l simpleLogger) Errorf(format string, args ...interface{}) { - l.Logf(levelError, format, args...) -} - -func (l simpleLogger) Fatalf(format string, args ...interface{}) { - l.Logf(levelFatal, format, args...) -} - -func (l simpleLogger) Panicf(format string, args ...interface{}) { - l.Logf(levelPanic, format, args...) -} - -func (l simpleLogger) WithFields(fields log.Fields) log.Logger { - return &simpleLogger{ - name: l.name, - fields: fields, - options: l.options, - } -} - -func (l simpleLogger) WithName(name string) log.Logger { - return &simpleLogger{ - name: name, - fields: l.fields, - options: l.options, - } -} - -func (l *simpleLogger) isLevelEnabled(level string) bool { - return levels[l.options.Level] >= levels[level] -} - -func mergeFields(a log.Fields, b log.Fields) log.Fields { - - res := log.Fields{} - - for k, v := range a { - res[k] = v - } - - if err := mergo.Merge(&a, b, mergo.WithOverride); err != nil { - return res - } - - return res -} diff --git a/pkg/acl/model.go b/pkg/acl/model.go deleted file mode 100644 index 69c9f94..0000000 --- a/pkg/acl/model.go +++ /dev/null @@ -1,92 +0,0 @@ -package acl - -// Model ... -type Model struct { - resources map[string]*resource -} - -// NewModel creates a new ACL model. -func NewModel() *Model { - return &Model{ - resources: map[string]*resource{}, - } -} - -// Resource configures a resource type within the ACL system, and -// returns a Resource interface which allows for the configuration -// of capabilities and aliases associated with this resource. -func (m *Model) Resource(res string) Resource { - if _, exists := m.resources[res]; exists { - panic("duplicate resource definition " + res) - } - m.resources[res] = &resource{ - name: res, - capabilities: map[string]*capability{}, - aliases: map[string]*capabilityAlias{}, - } - return m.resources[res] -} - -// Resource provides functions for the configuration -// of capabilities and aliases associated to a resource. -type Resource interface { - Capabilities(...string) Resource - Alias(string, ...string) Resource -} - -type resource struct { - name string - capabilities map[string]*capability - aliases map[string]*capabilityAlias -} - -// Capabilities ... -func (r *resource) Capabilities(caps ...string) Resource { - for _, cap := range caps { - r.capabilities[cap] = &capability{name: cap} - } - return r -} - -// Alias ... -func (r *resource) Alias(alias string, caps ...string) Resource { - // Make sure all capabilities are defined. - for _, c := range caps { - if !r.hasCapability(c) { - panic("undefined capability " + c) - } - } - r.aliases[alias] = &capabilityAlias{ - label: alias, - expandsTo: caps, - } - return r -} - -func (r *resource) hasCapability(s string) bool { - _, found := r.capabilities[s] - return found -} - -func (r *resource) hasAlias(s string) bool { - _, found := r.aliases[s] - return found -} - -// capability represents an operation possible in -// the context of a resource. -type capability struct { - name string - description string -} - -// capabilityAlias allows defining shorthands for -// representing multiple capabilities. -type capabilityAlias struct { - label string - expandsTo []string -} - -func (a *capabilityAlias) expand() []string { - return a.expandsTo -} diff --git a/pkg/acl/policy.go b/pkg/acl/policy.go deleted file mode 100644 index ddd15bf..0000000 --- a/pkg/acl/policy.go +++ /dev/null @@ -1,23 +0,0 @@ -package acl - -// Policy represents a named set of rules for allowing -// operations on specific resources. -type Policy interface { - Name() string - Rules() []Rule -} - -// Rule is used to allow operations on specific resources. In -// addition to the name of the target resource type and the allowed -// operations, a rule also specifies an optional glob pattern for -// targeting specific instances of the resource. -type Rule interface { - // Resource targeted by this rule. - Resource() string - // Path used to target specific instances - // of the target resource, if applicable. - Path() string - // Capabilities contains the actions allowed on - // instances of a resource matching this rule. - Capabilities() []string -} diff --git a/pkg/acl/resolver.go b/pkg/acl/resolver.go deleted file mode 100644 index 2ea6a6e..0000000 --- a/pkg/acl/resolver.go +++ /dev/null @@ -1,157 +0,0 @@ -package acl - -import ( - "context" - "fmt" - "sort" - - log "github.com/seashell/drago/pkg/log" - radix "github.com/seashell/drago/pkg/radix" -) - -// SecretResolverFunc returns the token associated with a secret -// if it exists, or nil otherwise. -type SecretResolverFunc func(context.Context, string) (Token, error) - -// PolicyResolverFunc returns a policy based on its name, -// or an error if it does not exist. -type PolicyResolverFunc func(context.Context, string) (Policy, error) - -// Resolver resolves ACL secrets and policies. -type Resolver struct { - logger log.Logger - model *Model - resolveSecret SecretResolverFunc - resolvePolicy PolicyResolverFunc -} - -// NewResolver ... -func NewResolver(config *ResolverConfig) (*Resolver, error) { - - config = DefaultResolverConfig().Merge(config) - - if config.Model == nil { - return nil, ErrMissingModel - } - - if config.SecretResolver == nil { - return nil, ErrMissingSecretResolver - } - - if config.PolicyResolver == nil { - return nil, ErrMissingPolicyResolver - } - - return &Resolver{ - logger: config.Logger, - model: config.Model, - resolveSecret: config.SecretResolver, - resolvePolicy: config.PolicyResolver, - }, nil -} - -// SecretResolver configures how secrets are resolved to ACL tokens. -func (r *Resolver) SecretResolver(f SecretResolverFunc) { - r.resolveSecret = f -} - -// PolicyResolver configures how policy names are resolved to ACL policies. -func (r *Resolver) PolicyResolver(f PolicyResolverFunc) { - r.resolvePolicy = f -} - -// ResolveSecret creates an ACL from a secret. -func (r *Resolver) ResolveSecret(ctx context.Context, secret string) (*ACL, error) { - - var token Token - - // Handle anonymous requests. - tkn, err := r.resolveSecret(ctx, secret) - if err != nil { - return nil, fmt.Errorf("%v : %v", ErrResolvingSecret, err) - } - if tkn == nil { - return nil, ErrTokenNotFound - } - token = tkn - - if token.IsPrivileged() { - return &ACL{privileged: true}, nil - } - - // Retrieve policy definitions from the repository. - policies := []Policy{} - for _, p := range token.Policies() { - policy, err := r.resolvePolicy(ctx, p) - if err != nil { - return nil, fmt.Errorf("%v : %v", ErrResolvingPolicy, err) - } - policies = append(policies, policy) - } - - sort.Slice(policies, func(i, j int) bool { - return policies[i].Name() < policies[j].Name() - }) - - // Initialize ACL struct - acl := &ACL{capabilities: map[string]*radix.Tree{}} - for resource := range r.model.resources { - acl.capabilities[resource] = radix.NewTree() - } - - // Based on the token policies, we populate the - // capability trees for each resource type. - for _, policy := range policies { - - for _, res := range r.model.resources { - - rules := policy.Rules() - - for _, rule := range rules { - - var capabilities capMap - - if rule.Resource() == res.name { - - // Initialize capability map for this pattern if it has not yet been - // initialized by a previously processed rule/policy. - if leaf, found := acl.capabilities[res.name].Get(rule.Path()); !found { - capabilities = make(capMap) - acl.capabilities[res.name].Set(rule.Path(), capabilities) - } else { - capabilities = leaf.(capMap) - } - - // Ignore all capabilities in the rule if a previously processed - // rule/policy has explicitly denied access to resources matching - // this pattern. - if capabilities.HasCapability(capabilityDeny) { - break - } - - // Expand aliases before processing capabilities. - expanded := []string{} - for _, cap := range rule.Capabilities() { - if res.hasAlias(cap) { - expanded = append(expanded, res.aliases[cap].expand()...) - } else { - expanded = append(expanded, cap) - } - } - - // Set all capabilities in the rule. - for _, cap := range expanded { - if cap == capabilityDeny { - capabilities.Clear() - capabilities.AddCapability(capabilityDeny) - break - } else { - capabilities.AddCapability(cap) - } - } - } - } - } - } - return acl, nil -} diff --git a/pkg/acl/token.go b/pkg/acl/token.go deleted file mode 100644 index b84a40c..0000000 --- a/pkg/acl/token.go +++ /dev/null @@ -1,7 +0,0 @@ -package acl - -// Token ... -type Token interface { - IsPrivileged() bool - Policies() []string -} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go deleted file mode 100644 index 06db433..0000000 --- a/pkg/cli/cli.go +++ /dev/null @@ -1,306 +0,0 @@ -package cli - -import ( - "context" - "fmt" - "io" - "regexp" - "sort" - "strings" - "text/template" -) - -// Credits: https://github.com/mitchellh/cli - -// CLI contains the state necessary to run subcommands and parse the -// command line arguments. -// -// CLI also supports nested subcommands, such as "cli foo bar". To use -// nested subcommands, the key in the Commands mapping below contains the -// full subcommand. In this example, it would be "foo bar". -// -// If you use a CLI with nested subcommands, some semantics change due to -// ambiguities: -// -// * We use longest prefix matching to find a matching subcommand. This -// means if you register "foo bar" and the user executes "cli foo qux", -// the "foo" command will be executed with the arg "qux". It is up to -// you to handle these args. One option is to just return the special -// help return code `RunResultHelp` to display help and exit. -// -// * The help flag "-h" or "-help" will look at all args to determine -// the help function. For example: "otto apps list -h" will show the -// help for "apps list" but "otto apps -h" will show it for "apps". -// In the normal CLI, only the first subcommand is used. -// -// * The help flag will list any subcommands that a command takes -// as well as the command's help itself. If there are no subcommands, -// it will note this. If the CLI itself has no subcommands, this entire -// section is omitted. -// -// * Any parent commands that don't exist are automatically created as -// no-op commands that just show help for other subcommands. For example, -// if you only register "foo bar", then "foo" is automatically created. -type CLI struct { - config *Config - router *Router - helpFunc HelpFunc - helpWriter io.Writer -} - -// Args contains parsed input to the CLI -type Args struct { - rootFlags []string - subcommand string - subcommandArgs []string - isHelp bool - isVersion bool -} - -// New returns a new CLI instance with sensible defaults. -func New(config *Config) *CLI { - - config = DefaultConfig().Merge(config) - - cli := &CLI{ - config: config, - router: NewRouter(), - helpFunc: config.HelpFunc, - helpWriter: config.HelpWriter, - } - - // Populate router based on config object - for name, cmd := range config.Commands { - name = strings.TrimSpace(name) - cli.router.AddCommand(name, cmd) - } - - cli.router.AddMissingParents(func() Command { - return &MockCommand{ - HelpText: "This command is accessed by using one of the subcommands below.", - RunReturnCode: CommandReturnCodeHelp, - } - }) - - return cli -} - -// WriteHelp : -func (cli *CLI) WriteHelp(data string) (int, error) { - return cli.helpWriter.Write([]byte(data)) -} - -// Run : -func (cli *CLI) Run(ctx context.Context, args []string) (int, error) { - - parsed := cli.parseArgs(args) - - // Print version help - if parsed.isVersion && cli.config.Version != "" { - cli.WriteHelp(cli.config.Version + "\n") - return 0, nil - } - - // Print global help - if parsed.isHelp && parsed.subcommand == "" { - allSubcommands := cli.router.GetSubcommands(parsed.subcommand) - cli.WriteHelp(cli.helpFunc(allSubcommands) + "\n") - return 0, nil - } - - // Print help for command parent - command, err := cli.router.GetCommand(parsed.subcommand) - if err != nil { - parent := cli.router.GetParent(parsed.subcommand) - parentSubcommands := cli.router.GetSubcommands(parent) - cli.WriteHelp(cli.helpFunc(parentSubcommands) + "\n") - return 1, nil - } - - // Print help for subcommand - if parsed.isHelp { - cli.commandHelp(parsed.subcommand) - return 0, nil - } - - // If there is an invalid flag, then error - if len(parsed.rootFlags) > 0 { - cli.WriteHelp("Invalid flags before the subcommand. If these flags are for\n" + - "the subcommand, please place them after the subcommand.\n\n") - cli.commandHelp(parsed.subcommand) - return 1, nil - } - - code := command.Run(ctx, parsed.subcommandArgs) - if code == CommandReturnCodeHelp { - cli.commandHelp(parsed.subcommand) - return 1, nil - } - - return code, nil -} - -func (cli *CLI) parseArgs(args []string) (out *Args) { - - out = &Args{} - - for i, arg := range args { - - if arg == "--" { - break - } - - if arg == "-h" || arg == "-help" || arg == "--help" { - out.isHelp = true - continue - } - - // If no subcommand has been found yet, continue setting top-level flags - if out.subcommand == "" { - if arg == "-v" || arg == "-version" || arg == "--version" { - out.isVersion = true - continue - } - if arg != "" && arg[0] == '-' { - out.rootFlags = append(out.rootFlags, arg) - } - } - - // If no subcommand has been found yet and we stumble upon a non-flag arg, - // then it must be the subcommand. - if out.subcommand == "" && arg != "" && arg[0] != '-' { - out.subcommand = arg - // If the command has a space in it, then it is invalid. - // Set a blank command so that it fails. - if strings.ContainsRune(arg, ' ') { - out.subcommand = "" - return - } - - // Determine the argument we look to to end subcommands. - // We look at all arguments until one has a space. This - // disallows commands like: ./cli foo "bar baz". An argument - // with a space is always an argument. - j := 0 - for k, v := range args[i:] { - if strings.ContainsRune(v, ' ') { - break - } - j = i + k + 1 - } - - // Nested CLI, the subcommand is actually the entire - // arg list up to a flag that is still a valid subcommand. - searchKey := strings.Join(args[i:j], " ") - k, _, ok := cli.router.GetLongestPrefix(searchKey) - if ok { - // k could be a prefix that doesn't contain the full - // command such as "foo" instead of "foobar", so we - // need to verify that we have an entire key. To do that, - // we look for an ending in a space or an end of string. - reVerify := regexp.MustCompile(regexp.QuoteMeta(k) + `( |$)`) - if reVerify.MatchString(searchKey) { - out.subcommand = k - i += strings.Count(k, " ") - } - } - // The remaining args the subcommand arguments - out.subcommandArgs = args[i+1:] - } - - // If a subcommand was not found, then use a default command if available - if out.subcommand == "" { - if _, err := cli.router.GetCommand(""); err == nil { - out.rootFlags = nil - out.subcommandArgs = append(out.rootFlags, out.subcommandArgs...) - } - } - } - - return -} - -func (cli *CLI) commandHelp(n string) { - tpl := ` -{{.Help}} -{{if gt (len .Subcommands) 0}} -Subcommands: -{{- range $value := .Subcommands }} - {{ $value.NameAligned }} {{ $value.Synopsis }} -{{- end }} -{{- end }} -` - tpl = strings.TrimSpace(tpl) - if !strings.HasSuffix(tpl, "\n") { - tpl += "\n" - } - - t, err := template.New("root").Parse(tpl) - if err != nil { - t = template.Must(template.New("root").Parse(fmt.Sprintf( - "Failed to parse command help template: %s\n", err))) - } - - cmd, err := cli.router.GetCommand(n) - if err != nil { - panic(err) - } - - data := map[string]interface{}{ - "Name": cli.config.Name, - "Help": cmd.Help(), - } - - // Build subcommand list if we have it - var subcommandsTpl []map[string]interface{} - - subcommands := cli.router.GetSubcommands(n) - - keys := make([]string, 0, len(subcommands)) - for k := range subcommands { - keys = append(keys, k) - } - - // Sort the keys - sort.Strings(keys) - - // Figure out the padding length - var longest int - for _, k := range keys { - if v := len(k); v > longest { - longest = v - } - } - - // Go through and create their structures - subcommandsTpl = make([]map[string]interface{}, 0, len(subcommands)) - for _, k := range keys { - - subcommand, ok := subcommands[k] - if !ok { - cli.WriteHelp(fmt.Sprintf( - "Error getting subcommand %q", k)) - } - - name := k - if idx := strings.LastIndex(k, " "); idx > -1 { - name = name[idx+1:] - } - - subcommandsTpl = append(subcommandsTpl, map[string]interface{}{ - "Name": name, - "NameAligned": name + strings.Repeat(" ", longest-len(k)), - "Help": subcommand.Help(), - "Synopsis": subcommand.Synopsis(), - }) - } - - data["Subcommands"] = subcommandsTpl - - err = t.Execute(cli.helpWriter, data) - if err == nil { - return - } - - cli.WriteHelp(fmt.Sprintf("Error rendering help: %s", err)) -} diff --git a/pkg/cli/command.go b/pkg/cli/command.go deleted file mode 100644 index a56aff0..0000000 --- a/pkg/cli/command.go +++ /dev/null @@ -1,30 +0,0 @@ -package cli - -import "context" - -const ( - CommandReturnCodeHelp = -18511 -) - -// A Command is a runnable sub-command of a CLI -type Command interface { - // Help should return long-form help text that includes the command-line - // usage, a brief few sentences explaining the function of the command, - // and the complete list of flags the command accepts. - Help() string - - // Synopsis should return a one-line, short synopsis of the command. - // This should be less than 50 characters ideally. - Synopsis() string - - // Run should run the actual command with the given CLI instance and - // command-line arguments. It should return the exit status when it is - // finished. - Run(ctx context.Context, args []string) int -} - -// A NamedCommand is a runnable sub-command of a CLI with a name -type NamedCommand interface { - Command - Name() string -} diff --git a/pkg/cli/command_mock.go b/pkg/cli/command_mock.go deleted file mode 100644 index e6f0d39..0000000 --- a/pkg/cli/command_mock.go +++ /dev/null @@ -1,28 +0,0 @@ -package cli - -import "context" - -// MockCommand is an implementation of Command that can be used for testing. -// It is also used for automatically populating missing parent commands. -type MockCommand struct { - HelpText string - SynopsisText string - RunReturnCode int - // Set by the command - RunCalled bool - RunArgs []string -} - -func (c *MockCommand) Help() string { - return c.HelpText -} - -func (c *MockCommand) Run(ctx context.Context, args []string) int { - c.RunCalled = true - c.RunArgs = args - return c.RunReturnCode -} - -func (c *MockCommand) Synopsis() string { - return c.SynopsisText -} diff --git a/pkg/cli/config.go b/pkg/cli/config.go deleted file mode 100644 index a46a67b..0000000 --- a/pkg/cli/config.go +++ /dev/null @@ -1,76 +0,0 @@ -package cli - -import ( - "io" - "os" -) - -// CLI configurations -type Config struct { - // Name defines the name of the CLI - Name string - - // Version of the CLI - Version string - - // Commands is a mapping of subcommand names to a Command implementation. - // If there is a command with a blank string "", then it will be used as - // the default command if no subcommand is specified. - // - // If the key has a space in it, this will create a nested subcommand. - // For example, if the key is "foo bar", then to access it our CLI - // must be accessed with "./cli foo bar". - Commands map[string]Command - - // HelpFunc is the function called to generate the generic help - // text that is shown if help must be shown for the CLI that doesn't - // pertain to a specific command - HelpFunc HelpFunc - - // HelpWriter is the Writer where the help text is outputted to. If - // not specified, it will default to Stderr - HelpWriter io.Writer -} - -// Default CLI configurations -func DefaultConfig() *Config { - config := &Config{ - Name: "app", - Version: "", - HelpFunc: DefaultHelpFunc("app"), - HelpWriter: os.Stderr, - Commands: map[string]Command{}, - } - return config -} - -func (c *Config) Merge(b *Config) *Config { - - if b == nil { - return c - } - - result := *c - - if b.Name != "" { - result.Name = b.Name - } - if b.Version != "" { - result.Version = b.Version - } - if b.Commands != nil { - result.Commands = b.Commands - } - if b.HelpFunc != nil { - result.HelpFunc = b.HelpFunc - } else { - if b.Name != "" { - result.HelpFunc = DefaultHelpFunc(b.Name) - } - } - if b.HelpWriter != nil { - result.HelpWriter = b.HelpWriter - } - - return &result -} diff --git a/pkg/cli/help.go b/pkg/cli/help.go deleted file mode 100644 index 5205480..0000000 --- a/pkg/cli/help.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "bytes" - "fmt" - "sort" - "strings" -) - -// HelpFunc is the type of the function that is responsible for generating -// the help output when the CLI must show the general help text -type HelpFunc func(map[string]Command) string - -// DefaultHelpFunc is the default function for generating help output -func DefaultHelpFunc(app string) HelpFunc { - return func(commands map[string]Command) string { - var buf bytes.Buffer - buf.WriteString(fmt.Sprintf( - "Usage: %s [--version] [--help] []\n\n", - app)) - buf.WriteString("Available commands:\n") - - // Get the list of keys so we can sort them, and also get the maximum - // key length so they can be aligned properly. - keys := make([]string, 0, len(commands)) - maxKeyLen := 0 - for key := range commands { - if len(key) > maxKeyLen { - maxKeyLen = len(key) - } - keys = append(keys, key) - } - sort.Strings(keys) - - for _, key := range keys { - command, ok := commands[key] - if !ok { - panic("command not found: " + key) - } - - key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key))) - buf.WriteString(fmt.Sprintf(" %s %s\n", key, command.Synopsis())) - } - - return buf.String() - } -} - -// FilteredHelpFunc will filter the commands to only include the keys -// in the include parameter. -func FilteredHelpFunc(include []string, f HelpFunc) HelpFunc { - return func(commands map[string]Command) string { - set := make(map[string]struct{}) - for _, k := range include { - set[k] = struct{}{} - } - - filtered := make(map[string]Command) - for k, f := range commands { - if _, ok := set[k]; ok { - filtered[k] = f - } - } - - return f(filtered) - } -} diff --git a/pkg/cli/router.go b/pkg/cli/router.go deleted file mode 100644 index 4e50285..0000000 --- a/pkg/cli/router.go +++ /dev/null @@ -1,153 +0,0 @@ -package cli - -import ( - "errors" - "strings" -) - -// Router ... -type Router struct { - commands map[string]Command -} - -// NewRouter creates a new router. -func NewRouter() *Router { - return &Router{ - commands: map[string]Command{}, - } -} - -// AddCommand ... -func (r *Router) AddCommand(n string, cmd Command) error { - r.commands[n] = cmd - return nil -} - -// GetCommand ... -func (r *Router) GetCommand(n string) (Command, error) { - cmd, ok := r.commands[n] - if !ok { - return nil, errors.New("command not found ") - } - return cmd, nil -} - -// AddMissingParents ensures that every registered command -// has a parent. -func (r *Router) AddMissingParents(genCmd func() Command) { - - missing := []string{} - - for key := range r.commands { - - idx := strings.LastIndex(key, " ") - - // Ignore top-level commands - if idx == -1 { - continue - } - - key = key[:idx] - - // Ignore commands for which a parent already exists - if _, ok := r.commands[key]; ok { - continue - } - - // If the command has no parent, update the map of missing commands - missing = append(missing, key) - } - - // Fill missing commands with mocks - for _, key := range missing { - r.AddCommand(key, genCmd()) - } - -} - -// GetSubcommands returns a map containing all commands -// which are below a given prefix -func (r *Router) GetSubcommands(prefix string) map[string]Command { - // If prefix is not empty, make sure it ends in ' ' - if prefix != "" && prefix[len(prefix)-1] != ' ' { - prefix += " " - } - - var keys []string - for k := range r.commands { - if strings.HasPrefix(k, prefix) { - if !strings.Contains(k[len(prefix):], " ") { - // Ignore any sub-sub keys, i.e. "foo bar baz" when we want "foo bar" - keys = append(keys, k) - } - } - } - - // For each of the keys return that in the map - result := make(map[string]Command, len(keys)) - for _, k := range keys { - cmd, err := r.GetCommand(k) - if err != nil { - panic("not found: " + k) - } - result[k] = cmd - } - - return result -} - -// GetParent returns the parent of this subcommand, if there is one. -// Otherwise, "" is returned. -func (r *Router) GetParent(n string) string { - if n == "" { - return n - } - - // Clear any trailing spaces and find the last space - n = strings.TrimRight(n, " ") - idx := strings.LastIndex(n, " ") - - if idx == -1 { - return "" - } - - return n[:idx] -} - -// GetLongestPrefix return the longest prefix match among all commands -// registered in the router, considering the query string passed as argument -func (r *Router) GetLongestPrefix(s string) (string, interface{}, bool) { - - keys := make([]string, 0, len(r.commands)) - for k := range r.commands { - keys = append(keys, k) - } - - scores := make([]int, len(keys)) - for i, key := range keys { - for j := range s { - if j >= len(key) { - break - } - if s[:j] != key[:j] { - break - } - scores[i]++ - } - } - - max := 0 - key := "" - for i, score := range scores { - if score > max { - max = score - key = keys[i] - } - } - - if key != "" { - return key, r.commands[key], true - } - - return key, nil, false -} diff --git a/pkg/cli/ui.go b/pkg/cli/ui.go deleted file mode 100644 index a7bbbbc..0000000 --- a/pkg/cli/ui.go +++ /dev/null @@ -1,60 +0,0 @@ -package cli - -import ( - "fmt" - "io" - - "github.com/fatih/color" -) - -// UI is an interface for interacting with the terminal, or "interface" -// of a CLI. This abstraction doesn't have to be used, but helps provide -// a simple, layerable way to manage user interactions. -type UI interface { - // Input uses the query to ask users for input. The response is - // returned as the given string, or an error. - // Input(string) (string, error) - - // Output is called for normal standard output. - Output(string) - - // Info is called for information related to the previous output. - // In general this may be the exact same as Output, but this gives - // Ui implementors some flexibility with output formats. - Info(string) - - // Error is used for any error messages that might appear on standard - // error. - Error(string) - - // Warn is used for any warning messages that might appear on standard - // error. - Warn(string) -} - -// BasicUI is an implementation of UI that outputs to the given writer. -type SimpleUI struct { - Reader io.Reader - Writer io.Writer - ErrorWriter io.Writer -} - -func (u *SimpleUI) Error(message string) { - w := u.Writer - if u.ErrorWriter != nil { - w = u.ErrorWriter - } - color.New(color.FgRed).Fprint(w, message, "\n") -} - -func (u *SimpleUI) Info(message string) { - u.Output(message) -} - -func (u *SimpleUI) Output(message string) { - fmt.Fprint(u.Writer, message, "\n") -} - -func (u *SimpleUI) Warn(message string) { - color.New(color.FgHiYellow).Fprint(u.Writer, message, "\n") -} diff --git a/pkg/concurrent/map.go b/pkg/concurrent/map.go deleted file mode 100644 index bcf052d..0000000 --- a/pkg/concurrent/map.go +++ /dev/null @@ -1,76 +0,0 @@ -package concurrent - -import ( - "sync" -) - -// Map type that can be safely shared between -// goroutines that require read/write access to a map -type Map struct { - sync.RWMutex - items map[string]interface{} -} - -func NewMap() *Map { - return &Map{ - items: map[string]interface{}{}, - } -} - -// Concurrent map item -type MapItem struct { - Key string - Value interface{} -} - -// Sets a key in a concurrent map -func (cm *Map) Set(key string, value interface{}) { - cm.Lock() - defer cm.Unlock() - cm.items[key] = value - -} - -// Gets a key from a concurrent map -func (cm *Map) Get(key string) (interface{}, bool) { - cm.RLock() - defer cm.RUnlock() - value, ok := cm.items[key] - return value, ok -} - -// Returns the length of a concurrent map -func (cm *Map) Len() int { - cm.RLock() - defer cm.RUnlock() - return len(cm.items) -} - -// Deletes a key from a concurrent map -func (cm *Map) Delete(key string) { - cm.Lock() - defer cm.Unlock() - delete(cm.items, key) -} - -// Iterates over the items in a concurrent map -// Each item is sent over a channel, so that we can iterate -// over the map using the builtin range keyword -func (cm *Map) Iter() <-chan MapItem { - c := make(chan MapItem) - f := func() { - snapshot := map[string]MapItem{} - cm.RLock() - for k, v := range cm.items { - snapshot[k] = MapItem{Key: k, Value: v} - } - cm.RUnlock() - for _, item := range snapshot { - c <- item - } - close(c) - } - go f() - - return c -} diff --git a/pkg/http/config.go b/pkg/http/config.go deleted file mode 100644 index bf221fd..0000000 --- a/pkg/http/config.go +++ /dev/null @@ -1,47 +0,0 @@ -package http - -import ( - log "github.com/seashell/drago/pkg/log" -) - -// Config contains configurations for the http module -type Config struct { - // Server bind address in the form host:port - BindAddress string - - // Handlers contains a mapping of paths to http.HandlerFunc - Handlers map[string]Handler - - // Handlers contains middleware functions to be applied to handlers - Middleware []Middleware - - // Logger - Logger log.Logger -} - -// DefaultConfig : -func DefaultConfig() *Config { - return &Config{ - BindAddress: "0.0.0.0:9876", - Handlers: map[string]Handler{}, - Middleware: []Middleware{}, - } -} - -// Merge : -func (s *Config) Merge(b *Config) *Config { - result := *s - if b.BindAddress != "" { - result.BindAddress = b.BindAddress - } - if b.Logger != nil { - result.Logger = b.Logger - } - if b.Handlers != nil { - result.Handlers = b.Handlers - } - if b.Middleware != nil { - result.Middleware = b.Middleware - } - return &result -} diff --git a/pkg/http/handler.go b/pkg/http/handler.go deleted file mode 100644 index 09c7c5a..0000000 --- a/pkg/http/handler.go +++ /dev/null @@ -1,14 +0,0 @@ -package http - -import ( - "net/http" -) - -// Handler : -type Handler interface { - Handle(http.ResponseWriter, *http.Request) (interface{}, error) -} - -// HandlerFunc is a custom HTTP handler function that returns a struct -// and an error that will be encoded and returned to the client -type HandlerFunc func(http.ResponseWriter, *http.Request) (interface{}, error) diff --git a/pkg/http/http.go b/pkg/http/http.go deleted file mode 100644 index 3b6848f..0000000 --- a/pkg/http/http.go +++ /dev/null @@ -1,117 +0,0 @@ -package http - -import ( - "encoding/json" - "fmt" - "net" - "net/http" - "strings" - - "github.com/seashell/drago/pkg/log" -) - -// Error : -type Error interface { - error - Code() int -} - -// Server : -type Server struct { - config *Config - logger log.Logger - listener net.Listener - listenerCh chan struct{} - mux *http.ServeMux -} - -// NewServer : -func NewServer(config *Config) (*Server, error) { - - config = DefaultConfig().Merge(config) - - listener, err := net.Listen("tcp", config.BindAddress) - if err != nil { - return nil, fmt.Errorf("error starting HTTP listener: %v", err) - } - - server := &Server{ - config: config, - listener: listener, - logger: config.Logger, - listenerCh: make(chan struct{}), - mux: http.NewServeMux(), - } - - for pattern, handler := range config.Handlers { - - fcn := httpHandlerFunc(handler) - - // Apply custom middleware - for _, m := range server.config.Middleware { - fcn = m(fcn) - } - - fcn = http.StripPrefix(strings.TrimSuffix(pattern, "/"), fcn).ServeHTTP - - server.mux.HandleFunc(pattern, fcn) - } - - httpServer := http.Server{ - Addr: server.listener.Addr().String(), - Handler: server.mux, - } - - go func() { - defer close(server.listenerCh) - httpServer.Serve(server.listener) - }() - - server.logger.Debugf("http server started at %s", httpServer.Addr) - - return server, nil -} - -// httpHandlerFunc converts a custom handler func to http.HandlerFunc -func httpHandlerFunc(handler Handler) http.HandlerFunc { - - f := func(rw http.ResponseWriter, req *http.Request) { - - fcn := handler.Handle - - // Invoke custom handler - out, err := fcn(rw, req) - - if err != nil { - code := http.StatusInternalServerError - if err, ok := err.(Error); ok { - code = err.Code() - encoded := encode(err) - rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(code) - rw.Write(encoded) - return - } - } - - if out != nil { - encoded := encode(out) - rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(http.StatusOK) - rw.Write(encoded) - } else { - rw.WriteHeader(http.StatusNoContent) - } - - } - - return f -} - -func encode(in interface{}) []byte { - encoded, err := json.Marshal(in) - if err != nil { - panic(err) - } - return encoded -} diff --git a/pkg/http/middleware.go b/pkg/http/middleware.go deleted file mode 100644 index bf83b7e..0000000 --- a/pkg/http/middleware.go +++ /dev/null @@ -1,6 +0,0 @@ -package http - -import "net/http" - -// Middleware : -type Middleware func(http.HandlerFunc) http.HandlerFunc diff --git a/pkg/log/log.go b/pkg/log/log.go deleted file mode 100644 index ec2c1b8..0000000 --- a/pkg/log/log.go +++ /dev/null @@ -1,23 +0,0 @@ -package log - -// Fields : Log fields -type Fields map[string]interface{} - -// Options contains logging options which are -// common to any logger -type LoggerOptions struct { - Prefix string - Level string -} - -// Logger : Logger interface -type Logger interface { - Debugf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Warnf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) - Fatalf(format string, args ...interface{}) - Panicf(format string, args ...interface{}) - WithFields(fields Fields) Logger - WithName(name string) Logger -} diff --git a/pkg/log/logrus/logrus.go b/pkg/log/logrus/logrus.go deleted file mode 100644 index c562845..0000000 --- a/pkg/log/logrus/logrus.go +++ /dev/null @@ -1,145 +0,0 @@ -package logrus - -import ( - "os" - - log "github.com/seashell/drago/pkg/log" - logrus "github.com/sirupsen/logrus" -) - -const ( - Info = "INFO" - Warn = "WARN" - Debug = "DEBUG" - Error = "ERROR" - Fatal = "FATAL" -) - -type logEntry struct { - entry *logrus.Entry -} - -type logger struct { - config Config - logger *logrus.Logger -} - -// Config contains the configuration for the logger adapter -type Config struct { - log.LoggerOptions -} - -// NewLoggerAdapter creates a new Logger adapter -func NewLoggerAdapter(config Config) (log.Logger, error) { - level, err := logrus.ParseLevel(config.Level) - if err != nil { - return nil, err - } - - formatter := &logrus.TextFormatter{} - - l := &logrus.Logger{ - Out: os.Stdout, - Level: level, - Formatter: formatter, - } - - return &logger{config: config, logger: l}, nil -} - -// Debugf : -func (l *logger) Debugf(format string, args ...interface{}) { - l.logger.Debugf(l.config.Prefix+format, args...) -} - -// Infof : -func (l *logger) Infof(format string, args ...interface{}) { - l.logger.Infof(l.config.Prefix+format, args...) -} - -// Warnf : -func (l *logger) Warnf(format string, args ...interface{}) { - l.logger.Warnf(l.config.Prefix+format, args...) -} - -// Errorf : -func (l *logger) Errorf(format string, args ...interface{}) { - l.logger.Errorf(l.config.Prefix+format, args...) -} - -// Fatalf : -func (l *logger) Fatalf(format string, args ...interface{}) { - l.logger.Fatalf(l.config.Prefix+format, args...) -} - -// Panicf : -func (l *logger) Panicf(format string, args ...interface{}) { - l.logger.Panicf(l.config.Prefix+format, args...) -} - -// WithFields : -func (l *logger) WithFields(fields log.Fields) log.Logger { - return &logEntry{ - entry: l.logger.WithFields(convertToLogrusFields(fields)), - } -} - -// WithName : -func (l *logger) WithName(name string) log.Logger { - return &logEntry{ - entry: l.logger.WithField("name", name), - } -} - -// Debugf : -func (l *logEntry) Debugf(format string, args ...interface{}) { - l.entry.Debugf(format, args...) -} - -// Infof : -func (l *logEntry) Infof(format string, args ...interface{}) { - l.entry.Infof(format, args...) -} - -// Warnf : -func (l *logEntry) Warnf(format string, args ...interface{}) { - l.entry.Warnf(format, args...) -} - -// Errorf : -func (l *logEntry) Errorf(format string, args ...interface{}) { - l.entry.Errorf(format, args...) -} - -// Fatalf : -func (l *logEntry) Fatalf(format string, args ...interface{}) { - l.entry.Fatalf(format, args...) -} - -// Panicf : -func (l *logEntry) Panicf(format string, args ...interface{}) { - l.entry.Panicf(format, args...) -} - -// WithFields : -func (l *logEntry) WithFields(fields log.Fields) log.Logger { - return &logEntry{ - entry: l.entry.WithFields(convertToLogrusFields(fields)), - } -} - -// WithName : -func (l *logEntry) WithName(name string) log.Logger { - return &logEntry{ - entry: l.entry.WithField("name", name), - } -} - -func convertToLogrusFields(fields log.Fields) logrus.Fields { - logrusFields := logrus.Fields{} - for index, val := range fields { - logrusFields[index] = val - } - - return logrusFields -} diff --git a/pkg/log/simple/simple.go b/pkg/log/simple/simple.go deleted file mode 100644 index 939bf44..0000000 --- a/pkg/log/simple/simple.go +++ /dev/null @@ -1,82 +0,0 @@ -package simple - -import ( - "fmt" - - log "github.com/seashell/drago/pkg/log" -) - -const ( - Info = "INFO" - Warn = "WARN" - Debug = "DEBUG" - Error = "ERROR" - Fatal = "FATAL" -) - -type logger struct { - config Config - name string - fields map[string]interface{} -} - -// Config contains the configuration for the logger adapter -type Config struct { - log.LoggerOptions -} - -// NewLoggerAdapter creates a new Logger adapter -func NewLoggerAdapter(config Config) (log.Logger, error) { - return &logger{config: config}, nil -} - -// Debugf : -func (l *logger) Debugf(format string, args ...interface{}) { - fmt.Printf("[DEBUG] "+l.name+": "+l.config.Prefix+format+"\n", args...) -} - -// Infof : -func (l *logger) Infof(format string, args ...interface{}) { - fmt.Printf("[INFO] "+l.name+": "+l.config.Prefix+format+"\n", args...) -} - -// Warnf : -func (l *logger) Warnf(format string, args ...interface{}) { - fmt.Printf("[WARN] "+l.name+": "+l.config.Prefix+format+"\n", args...) -} - -// Errorf : -func (l *logger) Errorf(format string, args ...interface{}) { - fmt.Printf("[ERROR] "+l.name+": "+l.config.Prefix+format+"\n", args...) -} - -// Fatalf : -func (l *logger) Fatalf(format string, args ...interface{}) { - fmt.Printf("[FATAL] "+l.name+": "+l.config.Prefix+format+"\n", args...) -} - -// Panicf : -func (l *logger) Panicf(format string, args ...interface{}) { - fmt.Printf("[PANIC] "+l.name+": "+l.config.Prefix+format+"\n", args...) -} - -// WithFields : -func (l *logger) WithFields(fields log.Fields) log.Logger { - - nl := &logger{ - fields: l.fields, - } - - for k, v := range fields { - nl.fields[k] = v - } - - return nl -} - -// WithName : -func (l *logger) WithName(name string) log.Logger { - return &logger{ - name: name, - } -} diff --git a/pkg/log/zap/zap.go b/pkg/log/zap/zap.go deleted file mode 100644 index 9af2155..0000000 --- a/pkg/log/zap/zap.go +++ /dev/null @@ -1,126 +0,0 @@ -package zap - -import ( - "fmt" - - log "github.com/seashell/drago/pkg/log" - zap "go.uber.org/zap" - zapcore "go.uber.org/zap/zapcore" -) - -const ( - Info = "INFO" - Warn = "WARN" - Debug = "DEBUG" - Error = "ERROR" - Fatal = "FATAL" -) - -type logger struct { - config Config - logger *zap.Logger -} - -// Config contains the configuration for the logger adapter -type Config struct { - log.LoggerOptions -} - -// NewLoggerAdapter creates a new zap Logger adapter -func NewLoggerAdapter(config Config) (log.Logger, error) { - - level, err := parseZapLevel(config.Level) - if err != nil { - return nil, err - } - - c := zap.Config{ - Encoding: "json", - Level: zap.NewAtomicLevelAt(level), - EncoderConfig: zap.NewProductionEncoderConfig(), - OutputPaths: []string{"stdout"}, - ErrorOutputPaths: []string{"stderr"}, - } - - l, err := c.Build() - if err != nil { - return nil, err - } - - return &logger{config: config, logger: l.WithOptions(zap.AddCallerSkip(1))}, nil -} - -// Debugf : -func (l *logger) Debugf(format string, args ...interface{}) { - s := fmt.Sprintf(format, args...) - l.logger.Debug(s) -} - -// Infof : -func (l *logger) Infof(format string, args ...interface{}) { - s := fmt.Sprintf(l.config.Prefix+format, args...) - l.logger.Info(s) -} - -// Warnf : -func (l *logger) Warnf(format string, args ...interface{}) { - s := fmt.Sprintf(l.config.Prefix+format, args...) - l.logger.Warn(s) -} - -// Errorf : -func (l *logger) Errorf(format string, args ...interface{}) { - s := fmt.Sprintf(l.config.Prefix+format, args...) - l.logger.Error(s) -} - -// Fatalf : -func (l *logger) Fatalf(format string, args ...interface{}) { - s := fmt.Sprintf(l.config.Prefix+format, args...) - l.logger.Fatal(s) -} - -// Panicf : -func (l *logger) Panicf(format string, args ...interface{}) { - s := fmt.Sprintf(l.config.Prefix+format, args...) - l.logger.Panic(s) -} - -// WithFields : -func (l *logger) WithFields(fields log.Fields) log.Logger { - return &logger{ - logger: l.logger.With(convertToZapFields(fields)...), - } -} - -// WithName : -func (l *logger) WithName(name string) log.Logger { - return &logger{ - logger: l.logger.With(zap.String("name", name)), - } -} - -func parseZapLevel(l string) (zapcore.Level, error) { - switch l { - case Info: - return zap.InfoLevel, nil - case Warn: - return zap.WarnLevel, nil - case Debug: - return zap.DebugLevel, nil - case Error: - return zap.ErrorLevel, nil - case Fatal: - return zap.FatalLevel, nil - default: - return 0, fmt.Errorf("unknown logging level: %s", l) - } -} - -func convertToZapFields(fields log.Fields) []zap.Field { - zapFields := []zap.Field{} - for index, val := range fields { - zapFields = append(zapFields, zap.Any(index, val)) - } - return zapFields -} diff --git a/pkg/radix/README.md b/pkg/radix/README.md deleted file mode 100644 index de035bf..0000000 --- a/pkg/radix/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# radix - -Simple implementation of radix trees in Go. - -A radix tree is a space-optimized/compressed version of a standard [trie](https://en.wikipedia.org/wiki/Trie), with every node that is a single child being merged with their parent. Unlike regular tries, the edges of a radix tree can hold strings, not only single characters. - -### References -[Wikipedia article about Radix trees](https://en.wikipedia.org/wiki/Radix_tree) -[Compressing radix trees without too many tears](https://medium.com/basecs/compressing-radix-trees-without-too-many-tears-a2e658adb9a0) - -### Related projects -[armon/go-radix](https://github.com/armon/go-radix) -[asergeyev/nradix/](https://github.com/asergeyev/nradix/) -[zmap/go-iptree](https://github.com/zmap/go-iptree) \ No newline at end of file diff --git a/pkg/radix/node.go b/pkg/radix/node.go deleted file mode 100644 index e82c52c..0000000 --- a/pkg/radix/node.go +++ /dev/null @@ -1,54 +0,0 @@ -package radix - -import "sort" - -type node struct { - leaf *leaf - edges []*edge -} - -type leaf struct { - key string - value interface{} -} - -func (n *node) isLeaf() bool { - return n.leaf != nil -} - -func (n *node) addEdge(e *edge) { - n.edges = append(n.edges, e) - n.sortEdges() -} - -func (n *node) deleteEdge(s string) { - // Apply binary search, since our edges sorted - idx := sort.Search(len(n.edges), func(i int) bool { - return n.edges[i].label >= s - }) - n.edges = append(n.edges[:idx], n.edges[idx+1:]...) -} - -func (n *node) sortEdges() { - sort.Slice(n.edges, func(i, j int) bool { - return n.edges[i].label < n.edges[j].label - }) -} - -// getEdgeWithLongestCommonPrefix takes a query string 's' and finds -// amongst all edges in the node the longest prefix they have in -// common with 's', returning both the prefix and the edge. -// -// Example: -// -// query = "abcdef", edges = [{"aaa"},{"abcde"},{"de"},{"bde"}] -// -// output = "ab", {"abcde"} -func (n *node) getEdgeWithLongestCommonPrefix(s string) (string, *edge) { - for _, e := range n.edges { - if p := longestCommonPrefix(s, e.label); p != "" { - return p, e - } - } - return "", nil -} diff --git a/pkg/radix/radix.go b/pkg/radix/radix.go deleted file mode 100644 index f4477f3..0000000 --- a/pkg/radix/radix.go +++ /dev/null @@ -1,301 +0,0 @@ -package radix - -import ( - "fmt" - "strings" -) - -// Tree implements a compressed radix tree, a -// space-optimized/compressed version of a standard trie, -// with every node that is a single child being merged -// with their parent. Unlike regular tries, the edges -// of a radix tree can hold strings, not only single -// characters. -type Tree struct { - root *node - size int -} - -// WalkFcn ... -type WalkFcn func(string, interface{}) bool - -// NewTree creates and return a new radix tree -func NewTree() *Tree { - return &Tree{ - size: 0, - root: &node{ - edges: []*edge{}, - }, - } -} - -type edge struct { - label string - node *node -} - -// Set adds or updates a leaf node prefixed by 'k'. -func (t *Tree) Set(k string, v interface{}) error { - - n := t.root - search := k - - for { - - if len(search) == 0 { - // If the current node is a leaf, update its value - if n.isLeaf() { - n.leaf.value = v - return nil - } - // Otherwise, insert a leaf - n.leaf, t.size = &leaf{key: k, value: v}, t.size+1 - return nil - } - - // Find longest common prefix between the search string and all edges. - if p, e := n.getEdgeWithLongestCommonPrefix(search); p != "" { - - // The common prefix corresponds to the matching - // edge label, and we simply walk down this edge. - if p == e.label { - n, search = e.node, strings.TrimPrefix(search, p) - continue - } - - // Otherwise, we found a common prefix shorter than the - // matching edge label, and the node must be split. - nn := &node{} - nn.addEdge(&edge{ - label: strings.TrimPrefix(e.label, p), - node: e.node, - }) - e.node, e.label = nn, p - n, search = e.node, strings.TrimPrefix(search, p) - } - - // If no prefix match was found, crete a new leaf node - // and add an edge to it. - ln := &node{ - leaf: &leaf{key: k, value: v}, - } - - n.addEdge(&edge{ - label: search, - node: ln, - }) - - t.size++ - break - } - - return nil -} - -// Get searches the tree and returns the value for the -// leaf node whose prefix exactly matches the key passed -// as argument. Additionaly, a bool is returned to indicate -// if a match was found or not. -func (t *Tree) Get(k string) (interface{}, bool) { - - n := t.root - search := k - - for { - - if len(search) == 0 { - if n.isLeaf() { - return n.leaf.value, true - } - return nil, false - } - - if p, e := n.getEdgeWithLongestCommonPrefix(search); p != "" { - if p == e.label { - n, search = e.node, strings.TrimPrefix(search, p) - continue - } - } - - break - } - - return nil, false -} - -// GetClosest searches the tree and returns the value for the -// leaf node corresponding to the longest prefix match to the -// key passed as argument. Besides the value itself, the longest -// prefix and a bool indicating whether or not a match was found -// are also returned. -func (t *Tree) GetClosest(k string) (string, interface{}, bool) { - - n := t.root - search := k - - prefix := "" - var prevLeaf *leaf - - for { - - if len(search) == 0 { - return prefix, prevLeaf.value, true - } - - if n.isLeaf() { - prevLeaf = n.leaf - } - - if p, e := n.getEdgeWithLongestCommonPrefix(search); p != "" { - if p == e.label { - prefix += e.label - n, search = e.node, strings.TrimPrefix(search, p) - continue - } - } - - break - } - - if prevLeaf != nil { - return prefix, prevLeaf.value, true - } - - return "", nil, false -} - -// Delete removes the leaf node prefixed by 'k' from the tree. -func (t *Tree) Delete(k string) error { - - n := t.root - search := k - - var prevEdge *edge - - for { - - if p, e := n.getEdgeWithLongestCommonPrefix(search); p != "" { - - if p == e.label { - - // Check if the next node is our target prior - // to walking down to it. - if len(strings.TrimPrefix(search, p)) == 0 { - - if e.node.isLeaf() { - - e.node.leaf, t.size = nil, t.size-1 - - // If the next node has no edges, remove it. - if len(e.node.edges) == 0 { - n.deleteEdge(e.label) - } - - // If the next node has a single edge, it is not needed, - // and we can merge the edges. - if len(e.node.edges) == 1 { - e.label = e.label + e.node.edges[0].label - e.node = e.node.edges[0].node - } - - // If the current node is neither a leaf node nor the - // root, and contains a single edge, it can too be merged - // with its parent. - if !n.isLeaf() && n != t.root && len(n.edges) == 1 { - prevEdge.label = prevEdge.label + n.edges[0].label - prevEdge.node = n.edges[0].node - } - - } else { - return nil - } - } - n, prevEdge, search = e.node, e, strings.TrimPrefix(search, p) - continue - } - } - - break - } - return nil -} - -// Walk ... -func (t *Tree) Walk(f WalkFcn) { - walkNode(t.root, f) -} - -func walkNode(n *node, f WalkFcn) bool { - - if n.isLeaf() && f(n.leaf.key, n.leaf.value) { - return true - } - - for _, e := range n.edges { - if walkNode(e.node, f) { - return true - } - } - - return false -} - -// Size returns the number of leaves in the radix tree. -func (t *Tree) Size() int { - return t.size -} - -// String returns a string representation of the tree -// which is useful for debugging. -func (t *Tree) String() string { - return treeString(t.root, 0) -} - -// TODO: make padding adaptive, and based on the prefix length. -func treeString(n *node, lvl int) string { - - s := "" - p := 14 - - if len(n.edges) == 0 { - return s - } - - for _, e := range n.edges { - padding := strings.Repeat(" ", lvl*p) - s += fmt.Sprintf("\n%s|", padding) - s += fmt.Sprintf("\n%s|----%s----(%v)", padding, e.label, e.node.leaf) - s += treeString(e.node, lvl+1) - } - - return s -} - -// longestCommonPrefix finds the longest common prefix of the input strings. -// It compares by bytes instead of runes (Unicode code points). -// It's up to the caller to do Unicode normalization if desired -// (e.g. see golang.org/x/text/unicode/norm). -// Credits: https://rosettacode.org/wiki/Longest_common_prefix -func longestCommonPrefix(l ...string) string { - switch len(l) { - case 0: - return "" - case 1: - return l[0] - } - min, max := l[0], l[0] - for _, s := range l[1:] { - switch { - case s < min: - min = s - case s > max: - max = s - } - } - for i := 0; i < len(min) && i < len(max); i++ { - if min[i] != max[i] { - return min[:i] - } - } - return min -} diff --git a/pkg/radix/radix_test.go b/pkg/radix/radix_test.go deleted file mode 100644 index 65dbf25..0000000 --- a/pkg/radix/radix_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package radix - -import ( - "testing" -) - -func TestSet(t *testing.T) { - - tree := NewTree() - - tree.Set("abc", "1") - tree.Set("def", "2") - tree.Set("abd", "3") -} - -func TestLen(t *testing.T) { - - tree := NewTree() - - tree.Set("abc", "1") - tree.Set("def", "2") - tree.Set("abd", "3") - - s := tree.Size() - if s != 3 { - t.Fatalf("tree.Size() failed, expected %d, have %d", 3, s) - } -} - -func TestGet(t *testing.T) { - - tree := NewTree() - - tree.Set("abc", "1") - tree.Set("def", "2") - tree.Set("abd", "3") - - if v, found := tree.Get("def"); !found { - t.Fatalf("tree.Get() failed, no results returned") - } else { - if v != "2" { - t.Fatalf("tree.Get() failed, expected %v, have %v", "2", v) - } - } - - if v, found := tree.Get("abd"); !found { - t.Fatalf("tree.Get() failed, no results returned") - } else { - if v != "3" { - t.Fatalf("tree.Get() failed, expected %v, have %v", "3", v) - } - } - - if v, found := tree.Get("ab"); found { - t.Fatalf("tree.Get() failed, no results expected, have %v", v) - } - -} - -func TestDelete(t *testing.T) { - - tree := NewTree() - - tree.Set("a", "1") - tree.Set("ab", "2") - tree.Set("acd", "3") - tree.Set("acdx", "4") - - err := tree.Delete("acd") - if err != nil { - t.Fatalf("tree.Delete() failed: %v", err) - } - if s := tree.Size(); s != 3 { - t.Fatalf("tree.Delete() failed, expected %d, have %d", 3, s) - } -} - -func TestGetClosest(t *testing.T) { - - tree := NewTree() - - tree.Set("a", "1") - tree.Set("ab", "2") - tree.Set("acd", "3") - tree.Set("acdx", "4") - - longest, value, found := tree.GetClosest("acde") - if !found { - t.Fatalf("tree.GetClosest() failed: no results found") - } - if longest != "acd" { - t.Fatalf("tree.GetClosest() failed, expected longest prefix to be %v, have %v", "acd", longest) - } - if value != "3" { - t.Fatalf("tree.GetClosest() failed, expected closest value to be %v, have %v", "3", value) - } -} - -func TestString(t *testing.T) { - tree := NewTree() - - tree.Set("a", "1") - tree.Set("ab", "2") - tree.Set("acd", "3") - tree.Set("acdx", "4") - - s := tree.String() - - t.Log(s) -} diff --git a/pkg/rpc/codec.go b/pkg/rpc/codec.go deleted file mode 100644 index 8a3fe8e..0000000 --- a/pkg/rpc/codec.go +++ /dev/null @@ -1,120 +0,0 @@ -package rpc - -import ( - "bufio" - "io" - "net/rpc" - - "github.com/vmihailenco/msgpack" -) - -// NewMsgpackServerCodec : -func NewMsgpackServerCodec(conn io.ReadWriteCloser) rpc.ServerCodec { - buf := bufio.NewWriter(conn) - return &msgpackServerCodec{ - rwc: conn, - dec: msgpack.NewDecoder(conn), - enc: msgpack.NewEncoder(buf), - encBuf: buf, - } -} - -// NewMsgpackClientCodec : -func NewMsgpackClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec { - encBuf := bufio.NewWriter(conn) - return &msgpackClientCodec{ - rwc: conn, - dec: msgpack.NewDecoder(conn), - enc: msgpack.NewEncoder(encBuf), - encBuf: encBuf, - } -} - -type msgpackServerCodec struct { - rwc io.ReadWriteCloser - dec *msgpack.Decoder - enc *msgpack.Encoder - encBuf *bufio.Writer - closed bool -} - -func (c *msgpackServerCodec) ReadRequestHeader(r *rpc.Request) error { - return c.dec.Decode(r) -} - -func (c *msgpackServerCodec) ReadRequestBody(body interface{}) error { - var err error - - if body == nil { - body, err = c.dec.DecodeInterface() - return err - } - - return c.dec.Decode(body) -} - -func (c *msgpackServerCodec) WriteResponse(r *rpc.Response, body interface{}) (err error) { - - if err = c.enc.Encode(r); err != nil { - if c.encBuf.Flush() == nil { - // Msgpack couldn't encode the header. Should not happen, so if it does, - // shut down the connection to signal that the connection is broken. - c.Close() - } - return - } - if err = c.enc.Encode(body); err != nil { - if c.encBuf.Flush() == nil { - // Was a msgpack problem encoding the body but the header has been written. - // Shut down the connection to signal that the connection is broken. - c.Close() - } - return - } - return c.encBuf.Flush() -} - -func (c *msgpackServerCodec) Close() error { - if c.closed { - return nil - } - c.closed = true - return c.rwc.Close() -} - -type msgpackClientCodec struct { - rwc io.ReadWriteCloser - dec *msgpack.Decoder - enc *msgpack.Encoder - encBuf *bufio.Writer -} - -func (c *msgpackClientCodec) WriteRequest(r *rpc.Request, body interface{}) (err error) { - if err = c.enc.Encode(r); err != nil { - return - } - if err = c.enc.Encode(body); err != nil { - return - } - return c.encBuf.Flush() -} - -func (c *msgpackClientCodec) ReadResponseHeader(r *rpc.Response) error { - return c.dec.Decode(r) -} - -func (c *msgpackClientCodec) ReadResponseBody(body interface{}) error { - - var err error - - if body == nil { - body, err = c.dec.DecodeInterface() - return err - } - - return c.dec.Decode(body) -} - -func (c *msgpackClientCodec) Close() error { - return c.rwc.Close() -} diff --git a/pkg/rpc/config.go b/pkg/rpc/config.go deleted file mode 100644 index 5dd976a..0000000 --- a/pkg/rpc/config.go +++ /dev/null @@ -1,71 +0,0 @@ -package rpc - -import ( - "time" - - log "github.com/seashell/drago/pkg/log" -) - -type ServerConfig struct { - //BindAddress - BindAddress string - - // Logger - Logger log.Logger - - // Receivers - Receivers map[string]interface{} -} - -func DefaultConfig() *ServerConfig { - return &ServerConfig{ - BindAddress: "0.0.0.0:8081", - Receivers: map[string]interface{}{}, - } -} - -func (s *ServerConfig) Merge(b *ServerConfig) *ServerConfig { - result := *s - if b.BindAddress != "" { - result.BindAddress = b.BindAddress - } - if b.Logger != nil { - result.Logger = b.Logger - } - if b.Receivers != nil { - result.Receivers = b.Receivers - } - return &result -} - -type ClientConfig struct { - // Logger - Logger log.Logger - - // URL of the Drago server (e.g. http://127.0.0.1:8081). - Address string - - // Timeout when dialing. - DialTimeout time.Duration -} - -func DefaultClientConfig() *ClientConfig { - return &ClientConfig{ - Address: "0.0.0.0:8081", - DialTimeout: 2 * time.Second, - } -} - -func (c *ClientConfig) Merge(b *ClientConfig) *ClientConfig { - result := *c - if b.Address != "" { - result.Address = b.Address - } - if b.Logger != nil { - result.Logger = b.Logger - } - if b.DialTimeout != 0 { - result.DialTimeout = b.DialTimeout - } - return &result -} diff --git a/pkg/rpc/rpc.go b/pkg/rpc/rpc.go deleted file mode 100644 index b4e3ef2..0000000 --- a/pkg/rpc/rpc.go +++ /dev/null @@ -1,93 +0,0 @@ -package rpc - -import ( - "fmt" - "net" - "net/rpc" - - log "github.com/seashell/drago/pkg/log" -) - -// Server : -type Server struct { - config *ServerConfig - logger log.Logger - listener net.Listener - listenerCh chan struct{} - rpcServer *rpc.Server -} - -// NewServer : -func NewServer(config *ServerConfig) (*Server, error) { - - config = DefaultConfig().Merge(config) - - // Use tls.Listen for serving with TLS - listener, err := net.Listen("tcp", config.BindAddress) - if err != nil { - return nil, fmt.Errorf("error starting rpc listener: %v", err) - } - - server := &Server{ - rpcServer: rpc.NewServer(), - config: config, - listener: listener, - logger: config.Logger, - listenerCh: make(chan struct{}), - } - - for name, receiver := range config.Receivers { - server.rpcServer.RegisterName(name, receiver) - } - - go func() { - for { - // TODO: Handle signals - select { - default: - } - conn, _ := listener.Accept() - cdc := NewMsgpackServerCodec(conn) - go func() { - server.rpcServer.ServeCodec(cdc) - }() - } - }() - - server.logger.Debugf("rpc server started at %s", config.BindAddress) - - return server, nil -} - -// Client : -type Client struct { - config *ClientConfig - logger log.Logger - client *rpc.Client -} - -// NewClient : -func NewClient(config *ClientConfig) (*Client, error) { - config = DefaultClientConfig().Merge(config) - - c := &Client{ - config: config, - logger: config.Logger, - } - - // Use tls.Dial for connection with TLS - conn, err := net.DialTimeout("tcp", config.Address, config.DialTimeout) - if err != nil { - return nil, err - } - - cdc := NewMsgpackClientCodec(conn) - c.client = rpc.NewClientWithCodec(cdc) - - return c, nil -} - -// Call is used to make an RPC call to the server -func (c *Client) Call(method string, args interface{}, reply interface{}) error { - return c.client.Call(method, args, reply) -} diff --git a/pkg/string/string.go b/pkg/string/string.go deleted file mode 100644 index 8d46132..0000000 --- a/pkg/string/string.go +++ /dev/null @@ -1,13 +0,0 @@ -package string - -import "strings" - -// SliceToString : Takes a slice containing strings and returns a comma-separated string -func SliceToString(s []string) string { - return strings.Join(s, ",") -} - -// StringToSlice : Takes a comma-separated string and returns a slice -func StringToSlice(s string) []string { - return strings.Fields(strings.Replace(s, ",", " ", -1)) -} diff --git a/pkg/util/util.go b/pkg/util/util.go deleted file mode 100644 index 2bc43f7..0000000 --- a/pkg/util/util.go +++ /dev/null @@ -1,17 +0,0 @@ -package util - -func StrToPtr(s string) *string { - return &s -} - -func BoolToPtr(b bool) *bool { - return &b -} - -func IntToPtr(i int) *int { - return &i -} - -func Uint16ToPtr(i uint16) *uint16 { - return &i -} diff --git a/pkg/uuid/uuid.go b/pkg/uuid/uuid.go deleted file mode 100644 index 90d6f54..0000000 --- a/pkg/uuid/uuid.go +++ /dev/null @@ -1,19 +0,0 @@ -package uuid - -import ( - "crypto/rand" - "fmt" -) - -func Generate() string { - buf := make([]byte, 16) - if _, err := rand.Read(buf); err != nil { - panic(fmt.Errorf("failed to read random bytes: %v", err)) - } - return fmt.Sprintf("%x-%x-%x-%x-%x", - buf[0:4], - buf[4:6], - buf[6:8], - buf[8:10], - buf[10:16]) -} diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go deleted file mode 100644 index 97303e0..0000000 --- a/pkg/validator/validator.go +++ /dev/null @@ -1,31 +0,0 @@ -package validator - -import ( - "context" - "regexp" - - "github.com/go-playground/validator/v10" -) - -func dashedAlphanumValidator(fl validator.FieldLevel) bool { - re := regexp.MustCompile("^[a-z0-9][a-z0-9_-]*$") - - return re.MatchString(fl.Field().String()) -} - -type Validator struct { - v *validator.Validate -} - -func New(ctx context.Context) (*Validator, error) { - v := validator.New() - v.RegisterValidation("dashed-alphanumeric", dashedAlphanumValidator) - - return &Validator{ - v: v, - }, nil -} - -func (v Validator) Validate(i interface{}) error { - return v.v.Struct(i) -} diff --git a/plugin/admission/admission.go b/plugin/admission/admission.go deleted file mode 100644 index ef95eea..0000000 --- a/plugin/admission/admission.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "log" - "net/rpc" -) - -// AdmissionPlugin : -type AdmissionPlugin struct { - logger log.Logger - rpcServer *rpc.Server -} - -// Config : -type Config struct { -} - -// NewAdmissionPlugin : Creates a new admission plugin object parameterized according to the provided configurations. -func NewAdmissionPlugin(config *Config) (*AdmissionPlugin, error) { - p := &AdmissionPlugin{} - return p, nil -} - -func main() { - _, err := NewAdmissionPlugin(&Config{}) - if err != nil { - panic(err) - } -} diff --git a/plugin/lease/lease.go b/plugin/lease/lease.go deleted file mode 100644 index 3678a0e..0000000 --- a/plugin/lease/lease.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "log" - "net/rpc" -) - -// LeasePlugin : -type LeasePlugin struct { - logger log.Logger - rpcServer *rpc.Server -} - -// Config : -type Config struct { -} - -// NewLeasePlugin : Creates a new lease plugin object parameterized according to the provided configurations. -func NewLeasePlugin(config *Config) (*LeasePlugin, error) { - p := &LeasePlugin{} - return p, nil -} - -func main() { - _, err := NewLeasePlugin(&Config{}) - if err != nil { - panic(err) - } -} diff --git a/plugin/mesh/mesh.go b/plugin/mesh/mesh.go deleted file mode 100644 index 70dcf60..0000000 --- a/plugin/mesh/mesh.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "net/rpc" - - log "github.com/seashell/drago/pkg/log" -) - -// TODO: Implementation of the drago.Plugin interface, with services exposed through RPC -// Whenever a new interface is created (event InterfaceCreated), this plugin interacts with the -// Drago RPC API in order to Link this interface with every other publicly exposed interface in -// the same network, thus creating a mesh overlay. - -// MeshPlugin : -type MeshPlugin struct { - logger log.Logger - rpcServer *rpc.Server -} - -// Config : -type Config struct { -} - -// NewMeshPlugin : Creates a new mesh plugin object parameterized according to the provided configurations. -func NewMeshPlugin(config *Config) (*MeshPlugin, error) { - p := &MeshPlugin{} - return p, nil -} - -func main() { - _, err := NewMeshPlugin(&Config{}) - if err != nil { - panic(err) - } -} diff --git a/plugin/notification/notification.go b/plugin/notification/notification.go deleted file mode 100644 index 5837fc8..0000000 --- a/plugin/notification/notification.go +++ /dev/null @@ -1,35 +0,0 @@ -package notification - -import ( - "net/rpc" - - log "github.com/seashell/drago/pkg/log" -) - -// TODO: Implementation of the drago.Plugin interface, with services exposed through RPC -// Whenever an event occurs in the Drago server (e.g. a new node is registered, or becomes online), -// this plugin interacts with the Drago RPC API in order to obtain information about this event -// and notify users via email, Telegram, slack, etc. - -// NotificationPlugin : -type NotificationPlugin struct { - logger log.Logger - rpcServer *rpc.Server -} - -// Config : -type Config struct { -} - -// NewNotificationPlugin : Creates a new mesh plugin object parameterized according to the provided configurations. -func NewNotificationPlugin(config *Config) (*NotificationPlugin, error) { - p := &NotificationPlugin{} - return p, nil -} - -func main() { - _, err := NewNotificationPlugin(&Config{}) - if err != nil { - panic(err) - } -} diff --git a/plugin/plugin.go b/plugin/plugin.go deleted file mode 100644 index 92db671..0000000 --- a/plugin/plugin.go +++ /dev/null @@ -1,3 +0,0 @@ -package plugin - -type Plugin interface{} diff --git a/ui/.babelrc b/ui/.babelrc deleted file mode 100644 index ed431dd..0000000 --- a/ui/.babelrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "presets": [ - "@babel/preset-react" - ] -} \ No newline at end of file diff --git a/ui/.eslintrc b/ui/.eslintrc deleted file mode 100644 index 803ef3e..0000000 --- a/ui/.eslintrc +++ /dev/null @@ -1,37 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": ["react-app","plugin:react/recommended", "airbnb", "prettier", "prettier/react"], - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 12, - "sourceType": "module" - }, - "plugins": ["react"], - "rules": { - "no-unused-vars": "warn", - "react/jsx-curly-brace-presence": "off", - "no-param-reassign": "off", - "no-use-before-define": "off", - "no-nested-ternary": "off", - "react/jsx-filename-extension": "off", - "import/no-extraneous-dependencies": "off", - "jsx-a11y/anchor-is-valid": "off", - "react-hooks/exhaustive-deps": "off", - "react/jsx-props-no-spreading": "off", - "react/destructuring-assignment": "off", - "react/state-in-constructor": "off", - "react/static-property-placement":"off" - }, - "settings": { - "import/resolver": { - "webpack": { - "config": "config/webpack/eslint.js" - } - } - } -} diff --git a/ui/.prettierrc b/ui/.prettierrc deleted file mode 100644 index 8ae29fc..0000000 --- a/ui/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "semi": false, - "parser": "babel", - "printWidth": 100, - "arrowParens": "always", - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": false -} \ No newline at end of file diff --git a/ui/.vscode/settings.json b/ui/.vscode/settings.json deleted file mode 100644 index be51019..0000000 --- a/ui/.vscode/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "eslint.workingDirectories": ["./ui/"], - "eslint.alwaysShowStatus": true, - "eslint.packageManager": "yarn", - "eslint.run": "onType", - - "prettier.prettierPath": "./node_modules/prettier", - "prettier.configPath": "./.prettierrc", - "prettier.packageManager": "yarn", - - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true, - "source.fixAll.eslint": true, - "source.fixAll.stylelint": true - } - }, - - } - \ No newline at end of file diff --git a/ui/README.md b/ui/README.md deleted file mode 100644 index d715959..0000000 --- a/ui/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# Drago UI - -A responsive web UI for the Drago server. - -The project was bootstraped with [Create React App](https://github.com/facebook/create-react-app), and NOT ejected. We preferred to use the very convenient `react-app-rewired` and `customize-cra` modules to allow for arbitrary overwrites of the Webpack and Babel settings. - -## Directory structure - -Babel, ESLint, and Webpack configurations should be kept within the `/config` directory or in the their corresponding configuration files at the project root (`.eslintrc`, `.babelrc`). - -We suggest the organization of components in a three-level hierarchy: - -- Presentational components, stored in `/src/components`; -- Higher-order components, stored in `/src/containers`; and -- Complete views, stored in `/views`. - -All style-related configurations, including themes, are stored in the `/src/styles` directory. - -All assets should be put into the `/src/assets` directory, and separated according to their type (icons, illustrations, etc). - -Finally, configurations of the Apollo client, including its caching structure, goes into the `/src/graphql` directory. - -## Available Scripts - -In the project directory, you can run: - -### `npm run fakeserver` - -Runs a graphql server which provides the application with fake data for development and testing purposes. -You can edit the schema in the file `schema.faker.graphql`. - -### `npm run start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. - -### `npm run test` - -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting - -### Analyzing the Bundle Size - -This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size - -### Making a Progressive Web App - -This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app - -### Advanced Configuration - -This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration - -### Deployment - -This section has moved here: https://facebook.github.io/create-react-app/docs/deployment - -### `npm run build` fails to minify - -This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/ui/build/.keep b/ui/build/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/config/rewired/index.js b/ui/config/rewired/index.js deleted file mode 100644 index 4702531..0000000 --- a/ui/config/rewired/index.js +++ /dev/null @@ -1,17 +0,0 @@ -const path = require('path') - -const { override, useBabelRc, useEslintRc, addWebpackResolve } = require('customize-cra') - -const rewireStyledComponents = require('react-app-rewire-styled-components') -const resolve = require('../webpack/resolve') - -module.exports = { - webpack: override( - addWebpackResolve(resolve), - // eslint-disable-next-line react-hooks/rules-of-hooks - // useBabelRc(path.resolve(__dirname, '..', '..', '.babelrc')), - // eslint-disable-next-line react-hooks/rules-of-hooks - // useEslintRc(path.resolve(__dirname, '..', '..', '.eslintrc')), - (config, env) => rewireStyledComponents(config, env, {}) - ), -} diff --git a/ui/config/webpack/eslint.js b/ui/config/webpack/eslint.js deleted file mode 100644 index 926ded3..0000000 --- a/ui/config/webpack/eslint.js +++ /dev/null @@ -1,14 +0,0 @@ -const path = require('path') - -const resolve = require('./resolve') - -module.exports = { - entry: ['../src/index'], - output: { - path: path.join(__dirname, 'dist'), - filename: 'bundle.js', - publicPath: '/static/', - }, - plugins: [], - resolve, -} diff --git a/ui/config/webpack/resolve.js b/ui/config/webpack/resolve.js deleted file mode 100644 index 0dd8edc..0000000 --- a/ui/config/webpack/resolve.js +++ /dev/null @@ -1,13 +0,0 @@ -const path = require('path') - -module.exports = { - alias: { - _assets: path.resolve(__dirname, '..', '..', 'src/assets/'), - _components: path.resolve(__dirname, '..', '..', 'src/components/'), - _containers: path.resolve(__dirname, '..', '..', 'src/containers/'), - _modals: path.resolve(__dirname, '..', '..', 'src/modals/'), - _graphql: path.resolve(__dirname, '..', '..', 'src/graphql/'), - _utils: path.resolve(__dirname, '..', '..', 'src/utils/'), - _views: path.resolve(__dirname, '..', '..', 'src/views/'), - }, -} diff --git a/ui/jsconfig.json b/ui/jsconfig.json deleted file mode 100644 index 6f374bf..0000000 --- a/ui/jsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "experimentalDecorators": true, - "baseUrl": "src", - "paths": { - "_components/*": ["components/*"], - "_containers/*": ["containers/*"], - "_modals/*": ["modals/*"], - "_views/*": ["views/*"], - "_graphql/*": ["graphql/*"], - "_utils/*": ["utils/*"], - "_assets/*": ["assets/*"] - } - }, - "exclude": ["node_modules", "dist", "dist-server"] -} - \ No newline at end of file diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index fd5ff09..0000000 --- a/ui/package.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "name": "drago-ui", - "version": "0.2.0", - "homepage": "/ui/", - "private": true, - "scripts": { - "start": "react-app-rewired start", - "build": "react-app-rewired build", - "eject": "react-scripts eject" - }, - "dependencies": { - "@apollo/client": "^3.3.7", - "@reach/router": "^1.3.1", - "@tippyjs/react": "^4.0.5", - "apollo-boost": "^0.4.9", - "apollo-link-context": "^1.0.20", - "apollo-link-rest": "^0.8.0-beta.0", - "d3": "^5.16.0", - "d3-force-3d": "^2.1.0", - "faker": "^5.3.1", - "formik": "^2.1.4", - "graphql": "^14.3.1", - "graphql-anywhere": "^4.2.7", - "graphql-tag": "^2.10.1", - "loglevel": "^1.6.6", - "moment": "^2.24.0", - "optimism": "^0.12.1", - "pluralize": "^8.0.0", - "prop-types": "^15.7.2", - "qrcode.react": "^1.0.0", - "react": "17.0.1", - "react-apollo-network-status": "^5.0.1", - "react-avatar": "^3.6.0", - "react-awesome-reveal": "^3.7.0", - "react-confirm-alert": "^2.6.1", - "react-d3-graph": "^2.4.1", - "react-dnd": "^9.0.1", - "react-dom": "^17.0.1", - "react-force-graph": "^1.34.1", - "react-graceful-image": "^1.2.9", - "react-hotkeys-hook": "^1.4.0", - "react-moment": "^0.9.2", - "react-placeholder": "^3.0.2", - "react-portal": "^4.2.0", - "react-router-dom": "^5.0.1", - "react-scripts": "4.0.1", - "react-select": "^3.1.0", - "react-sortable-hoc": "^1.9.1", - "react-tag-autocomplete": "^5.11.1", - "react-toastify": "^5.5.0", - "react-use": "^14.2.0", - "react-use-localstorage": "^3.5.3", - "react-virtualized-auto-sizer": "^1.0.4", - "react-window": "^1.8.6", - "react-window-infinite-loader": "^1.0.5", - "recharts": "^1.6.2", - "recompose": "^0.30.0", - "styled-components": "^5.1.1", - "styled-react-modal": "^2.0.0", - "styled-reset": "^4.1.6", - "styled-system": "^5.1.5", - "supercluster": "^6.0.1", - "validator": "^13.0.0", - "yup": "^0.29.1" - }, - "devDependencies": { - "babel-plugin-styled-components": "^1.10.1", - "customize-cra": "^1.0.0", - "eslint": "^7.19.0", - "eslint-config-airbnb": "^18.2.1", - "eslint-config-prettier": "^7.2.0", - "eslint-import-resolver-webpack": "^0.13.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-react": "^7.22.0", - "eslint-plugin-react-hooks": "^4.2.0", - "miragejs": "^0.1.40", - "prettier": "^2.0.5", - "react-app-rewire-styled-components": "^3.0.2", - "react-app-rewired": "^2.1.8" - }, - "config-overrides-path": "config/rewired/index.js", - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico deleted file mode 100644 index b5fa034..0000000 Binary files a/ui/public/favicon.ico and /dev/null differ diff --git a/ui/public/index.html b/ui/public/index.html deleted file mode 100644 index 208d0b5..0000000 --- a/ui/public/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - Drago - - - -
- - - diff --git a/ui/public/manifest.json b/ui/public/manifest.json deleted file mode 100644 index 542968d..0000000 --- a/ui/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "drago", - "name": "drago", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#fafbfc" -} diff --git a/ui/public/stylesheet.css b/ui/public/stylesheet.css deleted file mode 100644 index 2d0ce1e..0000000 --- a/ui/public/stylesheet.css +++ /dev/null @@ -1,523 +0,0 @@ -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-Black.woff2') format('woff2'), - url('static/fonts/Roboto-Black.woff') format('woff'); - font-weight: 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-Bold.woff2') format('woff2'), - url('static/fonts/Roboto-Bold.woff') format('woff'); - font-weight: bold; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-BlackItalic.woff2') format('woff2'), - url('static/fonts/Roboto-BlackItalic.woff') format('woff'); - font-weight: 900; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-BoldItalic.woff2') format('woff2'), - url('static/fonts/Roboto-BoldItalic.woff') format('woff'); - font-weight: bold; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-Light.woff2') format('woff2'), - url('static/fonts/Roboto-Light.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-Italic.woff2') format('woff2'), - url('static/fonts/Roboto-Italic.woff') format('woff'); - font-weight: normal; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-LightItalic.woff2') format('woff2'), - url('static/fonts/Roboto-LightItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-Medium.woff2') format('woff2'), - url('static/fonts/Roboto-Medium.woff') format('woff'); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-MediumItalic.woff2') format('woff2'), - url('static/fonts/Roboto-MediumItalic.woff') format('woff'); - font-weight: 500; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-Regular.woff2') format('woff2'), - url('static/fonts/Roboto-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-Thin.woff2') format('woff2'), - url('static/fonts/Roboto-Thin.woff') format('woff'); - font-weight: 100; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('static/fonts/Roboto-ThinItalic.woff2') format('woff2'), - url('static/fonts/Roboto-ThinItalic.woff') format('woff'); - font-weight: 100; - font-style: italic; - font-display: swap; -} - - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-Black.woff2') format('woff2'), - url('static/fonts/Montserrat-Black.woff') format('woff'); - font-weight: 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-BlackItalic.woff2') format('woff2'), - url('static/fonts/Montserrat-BlackItalic.woff') format('woff'); - font-weight: 900; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-Bold.woff2') format('woff2'), - url('static/fonts/Montserrat-Bold.woff') format('woff'); - font-weight: bold; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-BoldItalic.woff2') format('woff2'), - url('static/fonts/Montserrat-BoldItalic.woff') format('woff'); - font-weight: bold; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-ExtraBoldItalic.woff2') format('woff2'), - url('static/fonts/Montserrat-ExtraBoldItalic.woff') format('woff'); - font-weight: 800; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-ExtraBold.woff2') format('woff2'), - url('static/fonts/Montserrat-ExtraBold.woff') format('woff'); - font-weight: 800; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-ExtraLight.woff2') format('woff2'), - url('static/fonts/Montserrat-ExtraLight.woff') format('woff'); - font-weight: 200; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-ExtraLightItalic.woff2') format('woff2'), - url('static/fonts/Montserrat-ExtraLightItalic.woff') format('woff'); - font-weight: 200; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-Italic.woff2') format('woff2'), - url('static/fonts/Montserrat-Italic.woff') format('woff'); - font-weight: normal; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-Medium.woff2') format('woff2'), - url('static/fonts/Montserrat-Medium.woff') format('woff'); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-Light.woff2') format('woff2'), - url('static/fonts/Montserrat-Light.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-LightItalic.woff2') format('woff2'), - url('static/fonts/Montserrat-LightItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-MediumItalic.woff2') format('woff2'), - url('static/fonts/Montserrat-MediumItalic.woff') format('woff'); - font-weight: 500; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-Regular.woff2') format('woff2'), - url('static/fonts/Montserrat-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-ThinItalic.woff2') format('woff2'), - url('static/fonts/Montserrat-ThinItalic.woff') format('woff'); - font-weight: 100; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-SemiBoldItalic.woff2') format('woff2'), - url('static/fonts/Montserrat-SemiBoldItalic.woff') format('woff'); - font-weight: 600; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-Thin.woff2') format('woff2'), - url('static/fonts/Montserrat-Thin.woff') format('woff'); - font-weight: 100; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('static/fonts/Montserrat-SemiBold.woff2') format('woff2'), - url('static/fonts/Montserrat-SemiBold.woff') format('woff'); - font-weight: 600; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-BlackItalic.woff2') format('woff2'), - url('static/fonts/Raleway-BlackItalic.woff') format('woff'); - font-weight: 900; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-Black.woff2') format('woff2'), - url('static/fonts/Raleway-Black.woff') format('woff'); - font-weight: 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-ExtraBoldItalic.woff2') format('woff2'), - url('static/fonts/Raleway-ExtraBoldItalic.woff') format('woff'); - font-weight: 800; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-BoldItalic.woff2') format('woff2'), - url('static/fonts/Raleway-BoldItalic.woff') format('woff'); - font-weight: bold; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-ExtraBold.woff2') format('woff2'), - url('static/fonts/Raleway-ExtraBold.woff') format('woff'); - font-weight: 800; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-Bold.woff2') format('woff2'), - url('static/fonts/Raleway-Bold.woff') format('woff'); - font-weight: bold; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-ExtraLightItalic.woff2') format('woff2'), - url('static/fonts/Raleway-ExtraLightItalic.woff') format('woff'); - font-weight: 200; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-Italic.woff2') format('woff2'), - url('static/fonts/Raleway-Italic.woff') format('woff'); - font-weight: normal; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-ExtraLight.woff2') format('woff2'), - url('static/fonts/Raleway-ExtraLight.woff') format('woff'); - font-weight: 200; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-Light.woff2') format('woff2'), - url('static/fonts/Raleway-Light.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-Medium.woff2') format('woff2'), - url('static/fonts/Raleway-Medium.woff') format('woff'); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-LightItalic.woff2') format('woff2'), - url('static/fonts/Raleway-LightItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-ThinItalic.woff2') format('woff2'), - url('static/fonts/Raleway-ThinItalic.woff') format('woff'); - font-weight: 100; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-MediumItalic.woff2') format('woff2'), - url('static/fonts/Raleway-MediumItalic.woff') format('woff'); - font-weight: 500; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-Thin.woff2') format('woff2'), - url('static/fonts/Raleway-Thin.woff') format('woff'); - font-weight: 100; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-Regular.woff2') format('woff2'), - url('static/fonts/Raleway-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-SemiBoldItalic.woff2') format('woff2'), - url('static/fonts/Raleway-SemiBoldItalic.woff') format('woff'); - font-weight: 600; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('static/fonts/Raleway-SemiBold.woff2') format('woff2'), - url('static/fonts/Raleway-SemiBold.woff') format('woff'); - font-weight: 600; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: 'Lato'; - src: url('static/fonts/Lato-Bold.woff2') format('woff2'), - url('static/fonts/Lato-Bold.woff') format('woff'); - font-weight: bold; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('static/fonts/Lato-BoldItalic.woff2') format('woff2'), - url('static/fonts/Lato-BoldItalic.woff') format('woff'); - font-weight: bold; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('static/fonts/Lato-Black.woff2') format('woff2'), - url('static/fonts/Lato-Black.woff') format('woff'); - font-weight: 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('static/fonts/Lato-BlackItalic.woff2') format('woff2'), - url('static/fonts/Lato-BlackItalic.woff') format('woff'); - font-weight: 900; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('static/fonts/Lato-Light.woff2') format('woff2'), - url('static/fonts/Lato-Light.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('static/fonts/Lato-Italic.woff2') format('woff2'), - url('static/fonts/Lato-Italic.woff') format('woff'); - font-weight: normal; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('static/fonts/Lato-LightItalic.woff2') format('woff2'), - url('static/fonts/Lato-LightItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Lato Hairline'; - src: url('static/fonts/Lato-Hairline.woff2') format('woff2'), - url('static/fonts/Lato-Hairline.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('static/fonts/Lato-Regular.woff2') format('woff2'), - url('static/fonts/Lato-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato Hairline'; - src: url('static/fonts/Lato-HairlineItalic.woff2') format('woff2'), - url('static/fonts/Lato-HairlineItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} diff --git a/ui/src/assets/fonts/Lato-Black.woff b/ui/src/assets/fonts/Lato-Black.woff deleted file mode 100644 index 51a0ce0..0000000 Binary files a/ui/src/assets/fonts/Lato-Black.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Black.woff2 b/ui/src/assets/fonts/Lato-Black.woff2 deleted file mode 100644 index e4826d5..0000000 Binary files a/ui/src/assets/fonts/Lato-Black.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-BlackItalic.woff b/ui/src/assets/fonts/Lato-BlackItalic.woff deleted file mode 100644 index 5317666..0000000 Binary files a/ui/src/assets/fonts/Lato-BlackItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-BlackItalic.woff2 b/ui/src/assets/fonts/Lato-BlackItalic.woff2 deleted file mode 100644 index a0e66ea..0000000 Binary files a/ui/src/assets/fonts/Lato-BlackItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Bold.woff b/ui/src/assets/fonts/Lato-Bold.woff deleted file mode 100644 index d37d3b3..0000000 Binary files a/ui/src/assets/fonts/Lato-Bold.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Bold.woff2 b/ui/src/assets/fonts/Lato-Bold.woff2 deleted file mode 100644 index 50274ad..0000000 Binary files a/ui/src/assets/fonts/Lato-Bold.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-BoldItalic.woff b/ui/src/assets/fonts/Lato-BoldItalic.woff deleted file mode 100644 index 7f6ea4e..0000000 Binary files a/ui/src/assets/fonts/Lato-BoldItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-BoldItalic.woff2 b/ui/src/assets/fonts/Lato-BoldItalic.woff2 deleted file mode 100644 index ccf622b..0000000 Binary files a/ui/src/assets/fonts/Lato-BoldItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Hairline.woff b/ui/src/assets/fonts/Lato-Hairline.woff deleted file mode 100644 index 876d5a6..0000000 Binary files a/ui/src/assets/fonts/Lato-Hairline.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Hairline.woff2 b/ui/src/assets/fonts/Lato-Hairline.woff2 deleted file mode 100644 index 61f7e7d..0000000 Binary files a/ui/src/assets/fonts/Lato-Hairline.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-HairlineItalic.woff b/ui/src/assets/fonts/Lato-HairlineItalic.woff deleted file mode 100644 index 6606fc3..0000000 Binary files a/ui/src/assets/fonts/Lato-HairlineItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-HairlineItalic.woff2 b/ui/src/assets/fonts/Lato-HairlineItalic.woff2 deleted file mode 100644 index e0cc55d..0000000 Binary files a/ui/src/assets/fonts/Lato-HairlineItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Italic.woff b/ui/src/assets/fonts/Lato-Italic.woff deleted file mode 100644 index c612f2e..0000000 Binary files a/ui/src/assets/fonts/Lato-Italic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Italic.woff2 b/ui/src/assets/fonts/Lato-Italic.woff2 deleted file mode 100644 index f8e6622..0000000 Binary files a/ui/src/assets/fonts/Lato-Italic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Light.woff b/ui/src/assets/fonts/Lato-Light.woff deleted file mode 100644 index 3f53bab..0000000 Binary files a/ui/src/assets/fonts/Lato-Light.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Light.woff2 b/ui/src/assets/fonts/Lato-Light.woff2 deleted file mode 100644 index 3f01ef9..0000000 Binary files a/ui/src/assets/fonts/Lato-Light.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-LightItalic.woff b/ui/src/assets/fonts/Lato-LightItalic.woff deleted file mode 100644 index 602971e..0000000 Binary files a/ui/src/assets/fonts/Lato-LightItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-LightItalic.woff2 b/ui/src/assets/fonts/Lato-LightItalic.woff2 deleted file mode 100644 index cedfaed..0000000 Binary files a/ui/src/assets/fonts/Lato-LightItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Regular.woff b/ui/src/assets/fonts/Lato-Regular.woff deleted file mode 100644 index 9fb190c..0000000 Binary files a/ui/src/assets/fonts/Lato-Regular.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Lato-Regular.woff2 b/ui/src/assets/fonts/Lato-Regular.woff2 deleted file mode 100644 index aff3c2f..0000000 Binary files a/ui/src/assets/fonts/Lato-Regular.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Black.woff b/ui/src/assets/fonts/Montserrat-Black.woff deleted file mode 100644 index 0dc52fb..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Black.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Black.woff2 b/ui/src/assets/fonts/Montserrat-Black.woff2 deleted file mode 100644 index d896b53..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Black.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-BlackItalic.woff b/ui/src/assets/fonts/Montserrat-BlackItalic.woff deleted file mode 100644 index da0a9ab..0000000 Binary files a/ui/src/assets/fonts/Montserrat-BlackItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-BlackItalic.woff2 b/ui/src/assets/fonts/Montserrat-BlackItalic.woff2 deleted file mode 100644 index 929daa4..0000000 Binary files a/ui/src/assets/fonts/Montserrat-BlackItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Bold.woff b/ui/src/assets/fonts/Montserrat-Bold.woff deleted file mode 100644 index 2831f43..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Bold.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Bold.woff2 b/ui/src/assets/fonts/Montserrat-Bold.woff2 deleted file mode 100644 index 9a55d0b..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Bold.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-BoldItalic.woff b/ui/src/assets/fonts/Montserrat-BoldItalic.woff deleted file mode 100644 index 97dcf02..0000000 Binary files a/ui/src/assets/fonts/Montserrat-BoldItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-BoldItalic.woff2 b/ui/src/assets/fonts/Montserrat-BoldItalic.woff2 deleted file mode 100644 index a49920c..0000000 Binary files a/ui/src/assets/fonts/Montserrat-BoldItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ExtraBold.woff b/ui/src/assets/fonts/Montserrat-ExtraBold.woff deleted file mode 100644 index e623971..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ExtraBold.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ExtraBold.woff2 b/ui/src/assets/fonts/Montserrat-ExtraBold.woff2 deleted file mode 100644 index 9e788ba..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ExtraBold.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff b/ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff deleted file mode 100644 index 63335ce..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff2 b/ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff2 deleted file mode 100644 index 11012c8..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ExtraLight.woff b/ui/src/assets/fonts/Montserrat-ExtraLight.woff deleted file mode 100644 index 00105fa..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ExtraLight.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ExtraLight.woff2 b/ui/src/assets/fonts/Montserrat-ExtraLight.woff2 deleted file mode 100644 index 467d5e7..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ExtraLight.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff b/ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff deleted file mode 100644 index 13707e5..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff2 b/ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff2 deleted file mode 100644 index 00eecf6..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Italic.woff b/ui/src/assets/fonts/Montserrat-Italic.woff deleted file mode 100644 index 3ffb13c..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Italic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Italic.woff2 b/ui/src/assets/fonts/Montserrat-Italic.woff2 deleted file mode 100644 index a799858..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Italic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Light.woff b/ui/src/assets/fonts/Montserrat-Light.woff deleted file mode 100644 index d22588c..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Light.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Light.woff2 b/ui/src/assets/fonts/Montserrat-Light.woff2 deleted file mode 100644 index 2b24135..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Light.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-LightItalic.woff b/ui/src/assets/fonts/Montserrat-LightItalic.woff deleted file mode 100644 index 4adcd92..0000000 Binary files a/ui/src/assets/fonts/Montserrat-LightItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-LightItalic.woff2 b/ui/src/assets/fonts/Montserrat-LightItalic.woff2 deleted file mode 100644 index 7b03815..0000000 Binary files a/ui/src/assets/fonts/Montserrat-LightItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Medium.woff b/ui/src/assets/fonts/Montserrat-Medium.woff deleted file mode 100644 index 9144383..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Medium.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Medium.woff2 b/ui/src/assets/fonts/Montserrat-Medium.woff2 deleted file mode 100644 index 0992471..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Medium.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-MediumItalic.woff b/ui/src/assets/fonts/Montserrat-MediumItalic.woff deleted file mode 100644 index 25534ea..0000000 Binary files a/ui/src/assets/fonts/Montserrat-MediumItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-MediumItalic.woff2 b/ui/src/assets/fonts/Montserrat-MediumItalic.woff2 deleted file mode 100644 index b5e32bd..0000000 Binary files a/ui/src/assets/fonts/Montserrat-MediumItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Regular.woff b/ui/src/assets/fonts/Montserrat-Regular.woff deleted file mode 100644 index f350b73..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Regular.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Regular.woff2 b/ui/src/assets/fonts/Montserrat-Regular.woff2 deleted file mode 100644 index 33906e8..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Regular.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-SemiBold.woff b/ui/src/assets/fonts/Montserrat-SemiBold.woff deleted file mode 100644 index b8f6047..0000000 Binary files a/ui/src/assets/fonts/Montserrat-SemiBold.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-SemiBold.woff2 b/ui/src/assets/fonts/Montserrat-SemiBold.woff2 deleted file mode 100644 index d037f8f..0000000 Binary files a/ui/src/assets/fonts/Montserrat-SemiBold.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff b/ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff deleted file mode 100644 index ca0c78a..0000000 Binary files a/ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff2 b/ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff2 deleted file mode 100644 index 7fcbaf0..0000000 Binary files a/ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Thin.woff b/ui/src/assets/fonts/Montserrat-Thin.woff deleted file mode 100644 index b846271..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Thin.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-Thin.woff2 b/ui/src/assets/fonts/Montserrat-Thin.woff2 deleted file mode 100644 index 3a4a258..0000000 Binary files a/ui/src/assets/fonts/Montserrat-Thin.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ThinItalic.woff b/ui/src/assets/fonts/Montserrat-ThinItalic.woff deleted file mode 100644 index b042b28..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ThinItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Montserrat-ThinItalic.woff2 b/ui/src/assets/fonts/Montserrat-ThinItalic.woff2 deleted file mode 100644 index 14bf8ea..0000000 Binary files a/ui/src/assets/fonts/Montserrat-ThinItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Black.woff b/ui/src/assets/fonts/Raleway-Black.woff deleted file mode 100644 index 26fa891..0000000 Binary files a/ui/src/assets/fonts/Raleway-Black.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Black.woff2 b/ui/src/assets/fonts/Raleway-Black.woff2 deleted file mode 100644 index bb3bfc6..0000000 Binary files a/ui/src/assets/fonts/Raleway-Black.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-BlackItalic.woff b/ui/src/assets/fonts/Raleway-BlackItalic.woff deleted file mode 100644 index e56b44d..0000000 Binary files a/ui/src/assets/fonts/Raleway-BlackItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-BlackItalic.woff2 b/ui/src/assets/fonts/Raleway-BlackItalic.woff2 deleted file mode 100644 index bb8e88e..0000000 Binary files a/ui/src/assets/fonts/Raleway-BlackItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Bold.woff b/ui/src/assets/fonts/Raleway-Bold.woff deleted file mode 100644 index 0bc9e50..0000000 Binary files a/ui/src/assets/fonts/Raleway-Bold.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Bold.woff2 b/ui/src/assets/fonts/Raleway-Bold.woff2 deleted file mode 100644 index 5e68842..0000000 Binary files a/ui/src/assets/fonts/Raleway-Bold.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-BoldItalic.woff b/ui/src/assets/fonts/Raleway-BoldItalic.woff deleted file mode 100644 index 9e0bf3f..0000000 Binary files a/ui/src/assets/fonts/Raleway-BoldItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-BoldItalic.woff2 b/ui/src/assets/fonts/Raleway-BoldItalic.woff2 deleted file mode 100644 index b72c621..0000000 Binary files a/ui/src/assets/fonts/Raleway-BoldItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ExtraBold.woff b/ui/src/assets/fonts/Raleway-ExtraBold.woff deleted file mode 100644 index 5524d00..0000000 Binary files a/ui/src/assets/fonts/Raleway-ExtraBold.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ExtraBold.woff2 b/ui/src/assets/fonts/Raleway-ExtraBold.woff2 deleted file mode 100644 index f9fa3cd..0000000 Binary files a/ui/src/assets/fonts/Raleway-ExtraBold.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff b/ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff deleted file mode 100644 index cdf5659..0000000 Binary files a/ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff2 b/ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff2 deleted file mode 100644 index 2a36154..0000000 Binary files a/ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ExtraLight.woff b/ui/src/assets/fonts/Raleway-ExtraLight.woff deleted file mode 100644 index 9aa98d5..0000000 Binary files a/ui/src/assets/fonts/Raleway-ExtraLight.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ExtraLight.woff2 b/ui/src/assets/fonts/Raleway-ExtraLight.woff2 deleted file mode 100644 index a4347fb..0000000 Binary files a/ui/src/assets/fonts/Raleway-ExtraLight.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ExtraLightItalic.woff b/ui/src/assets/fonts/Raleway-ExtraLightItalic.woff deleted file mode 100644 index 813c288..0000000 Binary files a/ui/src/assets/fonts/Raleway-ExtraLightItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ExtraLightItalic.woff2 b/ui/src/assets/fonts/Raleway-ExtraLightItalic.woff2 deleted file mode 100644 index 379b0b8..0000000 Binary files a/ui/src/assets/fonts/Raleway-ExtraLightItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Italic.woff b/ui/src/assets/fonts/Raleway-Italic.woff deleted file mode 100644 index 55aae53..0000000 Binary files a/ui/src/assets/fonts/Raleway-Italic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Italic.woff2 b/ui/src/assets/fonts/Raleway-Italic.woff2 deleted file mode 100644 index bae13aa..0000000 Binary files a/ui/src/assets/fonts/Raleway-Italic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Light.woff b/ui/src/assets/fonts/Raleway-Light.woff deleted file mode 100644 index f79d19f..0000000 Binary files a/ui/src/assets/fonts/Raleway-Light.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Light.woff2 b/ui/src/assets/fonts/Raleway-Light.woff2 deleted file mode 100644 index d77a3fc..0000000 Binary files a/ui/src/assets/fonts/Raleway-Light.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-LightItalic.woff b/ui/src/assets/fonts/Raleway-LightItalic.woff deleted file mode 100644 index 022bce9..0000000 Binary files a/ui/src/assets/fonts/Raleway-LightItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-LightItalic.woff2 b/ui/src/assets/fonts/Raleway-LightItalic.woff2 deleted file mode 100644 index 9478123..0000000 Binary files a/ui/src/assets/fonts/Raleway-LightItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Medium.woff b/ui/src/assets/fonts/Raleway-Medium.woff deleted file mode 100644 index 5dd6cd9..0000000 Binary files a/ui/src/assets/fonts/Raleway-Medium.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Medium.woff2 b/ui/src/assets/fonts/Raleway-Medium.woff2 deleted file mode 100644 index baa9e16..0000000 Binary files a/ui/src/assets/fonts/Raleway-Medium.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-MediumItalic.woff b/ui/src/assets/fonts/Raleway-MediumItalic.woff deleted file mode 100644 index e09a8ba..0000000 Binary files a/ui/src/assets/fonts/Raleway-MediumItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-MediumItalic.woff2 b/ui/src/assets/fonts/Raleway-MediumItalic.woff2 deleted file mode 100644 index 5ee08c9..0000000 Binary files a/ui/src/assets/fonts/Raleway-MediumItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Regular.woff b/ui/src/assets/fonts/Raleway-Regular.woff deleted file mode 100644 index 76bd588..0000000 Binary files a/ui/src/assets/fonts/Raleway-Regular.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Regular.woff2 b/ui/src/assets/fonts/Raleway-Regular.woff2 deleted file mode 100644 index 6aa914a..0000000 Binary files a/ui/src/assets/fonts/Raleway-Regular.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-SemiBold.woff b/ui/src/assets/fonts/Raleway-SemiBold.woff deleted file mode 100644 index ae798f9..0000000 Binary files a/ui/src/assets/fonts/Raleway-SemiBold.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-SemiBold.woff2 b/ui/src/assets/fonts/Raleway-SemiBold.woff2 deleted file mode 100644 index fdace67..0000000 Binary files a/ui/src/assets/fonts/Raleway-SemiBold.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-SemiBoldItalic.woff b/ui/src/assets/fonts/Raleway-SemiBoldItalic.woff deleted file mode 100644 index a453e19..0000000 Binary files a/ui/src/assets/fonts/Raleway-SemiBoldItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-SemiBoldItalic.woff2 b/ui/src/assets/fonts/Raleway-SemiBoldItalic.woff2 deleted file mode 100644 index 0027607..0000000 Binary files a/ui/src/assets/fonts/Raleway-SemiBoldItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Thin.woff b/ui/src/assets/fonts/Raleway-Thin.woff deleted file mode 100644 index 077e05a..0000000 Binary files a/ui/src/assets/fonts/Raleway-Thin.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-Thin.woff2 b/ui/src/assets/fonts/Raleway-Thin.woff2 deleted file mode 100644 index 4828595..0000000 Binary files a/ui/src/assets/fonts/Raleway-Thin.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ThinItalic.woff b/ui/src/assets/fonts/Raleway-ThinItalic.woff deleted file mode 100644 index c2ffab6..0000000 Binary files a/ui/src/assets/fonts/Raleway-ThinItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Raleway-ThinItalic.woff2 b/ui/src/assets/fonts/Raleway-ThinItalic.woff2 deleted file mode 100644 index 92ebe51..0000000 Binary files a/ui/src/assets/fonts/Raleway-ThinItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Black.woff b/ui/src/assets/fonts/Roboto-Black.woff deleted file mode 100644 index d77f5a1..0000000 Binary files a/ui/src/assets/fonts/Roboto-Black.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Black.woff2 b/ui/src/assets/fonts/Roboto-Black.woff2 deleted file mode 100644 index 8dff3b1..0000000 Binary files a/ui/src/assets/fonts/Roboto-Black.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-BlackItalic.woff b/ui/src/assets/fonts/Roboto-BlackItalic.woff deleted file mode 100644 index 943e97e..0000000 Binary files a/ui/src/assets/fonts/Roboto-BlackItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-BlackItalic.woff2 b/ui/src/assets/fonts/Roboto-BlackItalic.woff2 deleted file mode 100644 index 7f51191..0000000 Binary files a/ui/src/assets/fonts/Roboto-BlackItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Bold.woff b/ui/src/assets/fonts/Roboto-Bold.woff deleted file mode 100644 index 51f1e89..0000000 Binary files a/ui/src/assets/fonts/Roboto-Bold.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Bold.woff2 b/ui/src/assets/fonts/Roboto-Bold.woff2 deleted file mode 100644 index 8d1ec3a..0000000 Binary files a/ui/src/assets/fonts/Roboto-Bold.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-BoldItalic.woff b/ui/src/assets/fonts/Roboto-BoldItalic.woff deleted file mode 100644 index d916f94..0000000 Binary files a/ui/src/assets/fonts/Roboto-BoldItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-BoldItalic.woff2 b/ui/src/assets/fonts/Roboto-BoldItalic.woff2 deleted file mode 100644 index ebca4c7..0000000 Binary files a/ui/src/assets/fonts/Roboto-BoldItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Italic.woff b/ui/src/assets/fonts/Roboto-Italic.woff deleted file mode 100644 index 8e72e8d..0000000 Binary files a/ui/src/assets/fonts/Roboto-Italic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Italic.woff2 b/ui/src/assets/fonts/Roboto-Italic.woff2 deleted file mode 100644 index 1714014..0000000 Binary files a/ui/src/assets/fonts/Roboto-Italic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Light.woff b/ui/src/assets/fonts/Roboto-Light.woff deleted file mode 100644 index eec3617..0000000 Binary files a/ui/src/assets/fonts/Roboto-Light.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Light.woff2 b/ui/src/assets/fonts/Roboto-Light.woff2 deleted file mode 100644 index 0e0efd0..0000000 Binary files a/ui/src/assets/fonts/Roboto-Light.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-LightItalic.woff b/ui/src/assets/fonts/Roboto-LightItalic.woff deleted file mode 100644 index b6b2525..0000000 Binary files a/ui/src/assets/fonts/Roboto-LightItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-LightItalic.woff2 b/ui/src/assets/fonts/Roboto-LightItalic.woff2 deleted file mode 100644 index b3300d9..0000000 Binary files a/ui/src/assets/fonts/Roboto-LightItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Medium.woff b/ui/src/assets/fonts/Roboto-Medium.woff deleted file mode 100644 index b4f0629..0000000 Binary files a/ui/src/assets/fonts/Roboto-Medium.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Medium.woff2 b/ui/src/assets/fonts/Roboto-Medium.woff2 deleted file mode 100644 index 02bf764..0000000 Binary files a/ui/src/assets/fonts/Roboto-Medium.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-MediumItalic.woff b/ui/src/assets/fonts/Roboto-MediumItalic.woff deleted file mode 100644 index ca56ca3..0000000 Binary files a/ui/src/assets/fonts/Roboto-MediumItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-MediumItalic.woff2 b/ui/src/assets/fonts/Roboto-MediumItalic.woff2 deleted file mode 100644 index 098ab8a..0000000 Binary files a/ui/src/assets/fonts/Roboto-MediumItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Regular.woff b/ui/src/assets/fonts/Roboto-Regular.woff deleted file mode 100644 index b070d8e..0000000 Binary files a/ui/src/assets/fonts/Roboto-Regular.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Regular.woff2 b/ui/src/assets/fonts/Roboto-Regular.woff2 deleted file mode 100644 index 30370cf..0000000 Binary files a/ui/src/assets/fonts/Roboto-Regular.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Thin.woff b/ui/src/assets/fonts/Roboto-Thin.woff deleted file mode 100644 index bc18032..0000000 Binary files a/ui/src/assets/fonts/Roboto-Thin.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-Thin.woff2 b/ui/src/assets/fonts/Roboto-Thin.woff2 deleted file mode 100644 index c535309..0000000 Binary files a/ui/src/assets/fonts/Roboto-Thin.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-ThinItalic.woff b/ui/src/assets/fonts/Roboto-ThinItalic.woff deleted file mode 100644 index 8863946..0000000 Binary files a/ui/src/assets/fonts/Roboto-ThinItalic.woff and /dev/null differ diff --git a/ui/src/assets/fonts/Roboto-ThinItalic.woff2 b/ui/src/assets/fonts/Roboto-ThinItalic.woff2 deleted file mode 100644 index 069ad7b..0000000 Binary files a/ui/src/assets/fonts/Roboto-ThinItalic.woff2 and /dev/null differ diff --git a/ui/src/assets/fonts/index.css b/ui/src/assets/fonts/index.css deleted file mode 100644 index d9815b5..0000000 --- a/ui/src/assets/fonts/index.css +++ /dev/null @@ -1,523 +0,0 @@ -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-Black.woff2') format('woff2'), - url('./Roboto-Black.woff') format('woff'); - font-weight: 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-Bold.woff2') format('woff2'), - url('./Roboto-Bold.woff') format('woff'); - font-weight: bold; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-BlackItalic.woff2') format('woff2'), - url('./Roboto-BlackItalic.woff') format('woff'); - font-weight: 900; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-BoldItalic.woff2') format('woff2'), - url('./Roboto-BoldItalic.woff') format('woff'); - font-weight: bold; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-Light.woff2') format('woff2'), - url('./Roboto-Light.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-Italic.woff2') format('woff2'), - url('./Roboto-Italic.woff') format('woff'); - font-weight: normal; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-LightItalic.woff2') format('woff2'), - url('./Roboto-LightItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-Medium.woff2') format('woff2'), - url('./Roboto-Medium.woff') format('woff'); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-MediumItalic.woff2') format('woff2'), - url('./Roboto-MediumItalic.woff') format('woff'); - font-weight: 500; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-Regular.woff2') format('woff2'), - url('./Roboto-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-Thin.woff2') format('woff2'), - url('./Roboto-Thin.woff') format('woff'); - font-weight: 100; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Roboto'; - src: url('./Roboto-ThinItalic.woff2') format('woff2'), - url('./Roboto-ThinItalic.woff') format('woff'); - font-weight: 100; - font-style: italic; - font-display: swap; -} - - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-Black.woff2') format('woff2'), - url('./Montserrat-Black.woff') format('woff'); - font-weight: 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-BlackItalic.woff2') format('woff2'), - url('./Montserrat-BlackItalic.woff') format('woff'); - font-weight: 900; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-Bold.woff2') format('woff2'), - url('./Montserrat-Bold.woff') format('woff'); - font-weight: bold; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-BoldItalic.woff2') format('woff2'), - url('./Montserrat-BoldItalic.woff') format('woff'); - font-weight: bold; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-ExtraBoldItalic.woff2') format('woff2'), - url('./Montserrat-ExtraBoldItalic.woff') format('woff'); - font-weight: 800; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-ExtraBold.woff2') format('woff2'), - url('./Montserrat-ExtraBold.woff') format('woff'); - font-weight: 800; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-ExtraLight.woff2') format('woff2'), - url('./Montserrat-ExtraLight.woff') format('woff'); - font-weight: 200; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-ExtraLightItalic.woff2') format('woff2'), - url('./Montserrat-ExtraLightItalic.woff') format('woff'); - font-weight: 200; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-Italic.woff2') format('woff2'), - url('./Montserrat-Italic.woff') format('woff'); - font-weight: normal; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-Medium.woff2') format('woff2'), - url('./Montserrat-Medium.woff') format('woff'); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-Light.woff2') format('woff2'), - url('./Montserrat-Light.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-LightItalic.woff2') format('woff2'), - url('./Montserrat-LightItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-MediumItalic.woff2') format('woff2'), - url('./Montserrat-MediumItalic.woff') format('woff'); - font-weight: 500; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-Regular.woff2') format('woff2'), - url('./Montserrat-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-ThinItalic.woff2') format('woff2'), - url('./Montserrat-ThinItalic.woff') format('woff'); - font-weight: 100; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-SemiBoldItalic.woff2') format('woff2'), - url('./Montserrat-SemiBoldItalic.woff') format('woff'); - font-weight: 600; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-Thin.woff2') format('woff2'), - url('./Montserrat-Thin.woff') format('woff'); - font-weight: 100; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Montserrat'; - src: url('./Montserrat-SemiBold.woff2') format('woff2'), - url('./Montserrat-SemiBold.woff') format('woff'); - font-weight: 600; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-BlackItalic.woff2') format('woff2'), - url('./Raleway-BlackItalic.woff') format('woff'); - font-weight: 900; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-Black.woff2') format('woff2'), - url('./Raleway-Black.woff') format('woff'); - font-weight: 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-ExtraBoldItalic.woff2') format('woff2'), - url('./Raleway-ExtraBoldItalic.woff') format('woff'); - font-weight: 800; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-BoldItalic.woff2') format('woff2'), - url('./Raleway-BoldItalic.woff') format('woff'); - font-weight: bold; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-ExtraBold.woff2') format('woff2'), - url('./Raleway-ExtraBold.woff') format('woff'); - font-weight: 800; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-Bold.woff2') format('woff2'), - url('./Raleway-Bold.woff') format('woff'); - font-weight: bold; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-ExtraLightItalic.woff2') format('woff2'), - url('./Raleway-ExtraLightItalic.woff') format('woff'); - font-weight: 200; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-Italic.woff2') format('woff2'), - url('./Raleway-Italic.woff') format('woff'); - font-weight: normal; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-ExtraLight.woff2') format('woff2'), - url('./Raleway-ExtraLight.woff') format('woff'); - font-weight: 200; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-Light.woff2') format('woff2'), - url('./Raleway-Light.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-Medium.woff2') format('woff2'), - url('./Raleway-Medium.woff') format('woff'); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-LightItalic.woff2') format('woff2'), - url('./Raleway-LightItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-ThinItalic.woff2') format('woff2'), - url('./Raleway-ThinItalic.woff') format('woff'); - font-weight: 100; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-MediumItalic.woff2') format('woff2'), - url('./Raleway-MediumItalic.woff') format('woff'); - font-weight: 500; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-Thin.woff2') format('woff2'), - url('./Raleway-Thin.woff') format('woff'); - font-weight: 100; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-Regular.woff2') format('woff2'), - url('./Raleway-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-SemiBoldItalic.woff2') format('woff2'), - url('./Raleway-SemiBoldItalic.woff') format('woff'); - font-weight: 600; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Raleway'; - src: url('./Raleway-SemiBold.woff2') format('woff2'), - url('./Raleway-SemiBold.woff') format('woff'); - font-weight: 600; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: 'Lato'; - src: url('./Lato-Bold.woff2') format('woff2'), - url('./Lato-Bold.woff') format('woff'); - font-weight: bold; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('./Lato-BoldItalic.woff2') format('woff2'), - url('./Lato-BoldItalic.woff') format('woff'); - font-weight: bold; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('./Lato-Black.woff2') format('woff2'), - url('./Lato-Black.woff') format('woff'); - font-weight: 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('./Lato-BlackItalic.woff2') format('woff2'), - url('./Lato-BlackItalic.woff') format('woff'); - font-weight: 900; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('./Lato-Light.woff2') format('woff2'), - url('./Lato-Light.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('./Lato-Italic.woff2') format('woff2'), - url('./Lato-Italic.woff') format('woff'); - font-weight: normal; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('./Lato-LightItalic.woff2') format('woff2'), - url('./Lato-LightItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Lato Hairline'; - src: url('./Lato-Hairline.woff2') format('woff2'), - url('./Lato-Hairline.woff') format('woff'); - font-weight: 300; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato'; - src: url('./Lato-Regular.woff2') format('woff2'), - url('./Lato-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Lato Hairline'; - src: url('./Lato-HairlineItalic.woff2') format('woff2'), - url('./Lato-HairlineItalic.woff') format('woff'); - font-weight: 300; - font-style: italic; - font-display: swap; -} diff --git a/ui/src/assets/icons/back.svg b/ui/src/assets/icons/back.svg deleted file mode 100644 index 4f20fcb..0000000 --- a/ui/src/assets/icons/back.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/ui/src/assets/icons/bell.svg b/ui/src/assets/icons/bell.svg deleted file mode 100644 index d8cc44a..0000000 --- a/ui/src/assets/icons/bell.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/ui/src/assets/icons/buoy.svg b/ui/src/assets/icons/buoy.svg deleted file mode 100644 index 5790ab0..0000000 --- a/ui/src/assets/icons/buoy.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/ui/src/assets/icons/connection-small.svg b/ui/src/assets/icons/connection-small.svg deleted file mode 100644 index 4077736..0000000 --- a/ui/src/assets/icons/connection-small.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/src/assets/icons/connection.svg b/ui/src/assets/icons/connection.svg deleted file mode 100644 index 275e5ab..0000000 --- a/ui/src/assets/icons/connection.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/assets/icons/dots.svg b/ui/src/assets/icons/dots.svg deleted file mode 100644 index 7c9a54c..0000000 --- a/ui/src/assets/icons/dots.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/ui/src/assets/icons/down.svg b/ui/src/assets/icons/down.svg deleted file mode 100644 index c4e1b48..0000000 --- a/ui/src/assets/icons/down.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/ui/src/assets/icons/error.svg b/ui/src/assets/icons/error.svg deleted file mode 100644 index 20f5bdb..0000000 --- a/ui/src/assets/icons/error.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/ui/src/assets/icons/external-link.svg b/ui/src/assets/icons/external-link.svg deleted file mode 100644 index 73eb8dd..0000000 --- a/ui/src/assets/icons/external-link.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ui/src/assets/icons/host.svg b/ui/src/assets/icons/host.svg deleted file mode 100644 index da76a54..0000000 --- a/ui/src/assets/icons/host.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/ui/src/assets/icons/index.js b/ui/src/assets/icons/index.js deleted file mode 100644 index 4dc6f19..0000000 --- a/ui/src/assets/icons/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import { ReactComponent as Back } from './back.svg' -import { ReactComponent as Bell } from './bell.svg' -import { ReactComponent as Buoy } from './buoy.svg' -import { ReactComponent as Connection } from './connection.svg' -import { ReactComponent as ConnectionSmall } from './connection-small.svg' -import { ReactComponent as Dots } from './dots.svg' -import { ReactComponent as ArrowDown } from './down.svg' -import { ReactComponent as Error } from './error.svg' -import { ReactComponent as ExternalLink } from './external-link.svg' -import { ReactComponent as Host } from './host.svg' -import { ReactComponent as Interface } from './interface.svg' -import { ReactComponent as Key } from './key.svg' -import { ReactComponent as Label } from './label.svg' -import { ReactComponent as Leave } from './leave.svg' -import { ReactComponent as ArrowLeft } from './left.svg' -import { ReactComponent as Link } from './link.svg' -import { ReactComponent as Logo } from './logo.svg' -import { ReactComponent as Network } from './network.svg' -import { ReactComponent as Plus } from './plus.svg' -import { ReactComponent as ArrowRight } from './right.svg' -import { ReactComponent as Search } from './search.svg' -import { ReactComponent as Success } from './success.svg' -import { ReactComponent as Times } from './times.svg' -import { ReactComponent as Trash } from './trash.svg' -import { ReactComponent as Unknown } from './unknown.svg' -import { ReactComponent as ArrowUp } from './up.svg' -import { ReactComponent as Warning } from './warning.svg' - -export default { - Logo, - ArrowUp, - ArrowDown, - ExternalLink, - ArrowLeft, - ArrowRight, - Plus, - Times, - Dots, - Leave, - Search, - Label, - Buoy, - Bell, - Connection, - ConnectionSmall, - Key, - Success, - Error, - Warning, - Unknown, - Host, - Interface, - Network, - Link, - Trash, - Back, -} diff --git a/ui/src/assets/icons/interface.svg b/ui/src/assets/icons/interface.svg deleted file mode 100644 index 296495f..0000000 --- a/ui/src/assets/icons/interface.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/ui/src/assets/icons/key.svg b/ui/src/assets/icons/key.svg deleted file mode 100644 index 2cacb72..0000000 --- a/ui/src/assets/icons/key.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ui/src/assets/icons/label.svg b/ui/src/assets/icons/label.svg deleted file mode 100644 index e416499..0000000 --- a/ui/src/assets/icons/label.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/src/assets/icons/leave.svg b/ui/src/assets/icons/leave.svg deleted file mode 100644 index 9837ae4..0000000 --- a/ui/src/assets/icons/leave.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/src/assets/icons/left.svg b/ui/src/assets/icons/left.svg deleted file mode 100644 index 2b86d72..0000000 --- a/ui/src/assets/icons/left.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/ui/src/assets/icons/link.svg b/ui/src/assets/icons/link.svg deleted file mode 100644 index 2d48825..0000000 --- a/ui/src/assets/icons/link.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/src/assets/icons/network.svg b/ui/src/assets/icons/network.svg deleted file mode 100644 index 4b4b4ab..0000000 --- a/ui/src/assets/icons/network.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/src/assets/icons/plus.svg b/ui/src/assets/icons/plus.svg deleted file mode 100644 index 4e485e8..0000000 --- a/ui/src/assets/icons/plus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/src/assets/icons/refresh.svg b/ui/src/assets/icons/refresh.svg deleted file mode 100644 index 983e60e..0000000 --- a/ui/src/assets/icons/refresh.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/assets/icons/right.svg b/ui/src/assets/icons/right.svg deleted file mode 100644 index 20875df..0000000 --- a/ui/src/assets/icons/right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/src/assets/icons/search.svg b/ui/src/assets/icons/search.svg deleted file mode 100644 index 880c6c0..0000000 --- a/ui/src/assets/icons/search.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/src/assets/icons/success.svg b/ui/src/assets/icons/success.svg deleted file mode 100644 index 3ff330f..0000000 --- a/ui/src/assets/icons/success.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/assets/icons/times.svg b/ui/src/assets/icons/times.svg deleted file mode 100644 index 55a71bd..0000000 --- a/ui/src/assets/icons/times.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/ui/src/assets/icons/trash.svg b/ui/src/assets/icons/trash.svg deleted file mode 100644 index f47ebb7..0000000 --- a/ui/src/assets/icons/trash.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ui/src/assets/icons/unknown.svg b/ui/src/assets/icons/unknown.svg deleted file mode 100644 index d665c3a..0000000 --- a/ui/src/assets/icons/unknown.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/ui/src/assets/icons/up.svg b/ui/src/assets/icons/up.svg deleted file mode 100644 index 1b6109a..0000000 --- a/ui/src/assets/icons/up.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/ui/src/assets/icons/warning.svg b/ui/src/assets/icons/warning.svg deleted file mode 100644 index 5ad96b1..0000000 --- a/ui/src/assets/icons/warning.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/ui/src/assets/illustrations/empty.svg b/ui/src/assets/illustrations/empty.svg deleted file mode 100644 index 332aaca..0000000 --- a/ui/src/assets/illustrations/empty.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/ui/src/assets/illustrations/error.svg b/ui/src/assets/illustrations/error.svg deleted file mode 100644 index a911e1e..0000000 --- a/ui/src/assets/illustrations/error.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ui/src/assets/illustrations/index.js b/ui/src/assets/illustrations/index.js deleted file mode 100644 index c61006a..0000000 --- a/ui/src/assets/illustrations/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import { ReactComponent as EmptyIllustration } from './empty.svg' -import { ReactComponent as ErrorIllustration } from './error.svg' -import { ReactComponent as NotFoundIllustration } from './not-found.svg' -import { ReactComponent as UnauthorizedIllustration } from './unauthorized.svg' - -export default { - NotFound: NotFoundIllustration, - Error: ErrorIllustration, - Empty: EmptyIllustration, - Unauthorized: UnauthorizedIllustration, -} diff --git a/ui/src/assets/illustrations/not-found.svg b/ui/src/assets/illustrations/not-found.svg deleted file mode 100644 index 34bdbb9..0000000 --- a/ui/src/assets/illustrations/not-found.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/ui/src/assets/illustrations/unauthorized.svg b/ui/src/assets/illustrations/unauthorized.svg deleted file mode 100644 index 7b9d8c1..0000000 --- a/ui/src/assets/illustrations/unauthorized.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ui/src/assets/index.js b/ui/src/assets/index.js deleted file mode 100644 index a8abc2a..0000000 --- a/ui/src/assets/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import icons from './icons' -import illustrations from './illustrations' - -export { icons, illustrations } diff --git a/ui/src/components/avatar/index.js b/ui/src/components/avatar/index.js deleted file mode 100644 index 031db73..0000000 --- a/ui/src/components/avatar/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { layout, space } from 'styled-system' - -import ReactAvatar, { ConfigProvider } from 'react-avatar' - -const StyledReactAvatar = styled(ReactAvatar)` - ${layout} - ${space} -` - -const Avatar = props => ( - - - -) - -export default Avatar diff --git a/ui/src/components/back-link/index.js b/ui/src/components/back-link/index.js deleted file mode 100644 index 09e4342..0000000 --- a/ui/src/components/back-link/index.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import styled from 'styled-components' -import { icons } from '_assets/' -import Box from '_components/box' -import Icon from '_components/icon' -import Link from '_components/link' -import Text from '_components/text' - -const Container = styled(Box)` - width: max-content; - opacity: 0.3; - :hover { - opacity: 1; - } -` - -const BackLink = ({ text, to, ...props }) => ( - - - - } color="foreground2" size="14px" /> - - {text} - - - - -) - -BackLink.propTypes = { - text: PropTypes.string.isRequired, - to: PropTypes.string.isRequired, -} - -export default BackLink diff --git a/ui/src/components/box/index.js b/ui/src/components/box/index.js deleted file mode 100644 index b14a0e7..0000000 --- a/ui/src/components/box/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components' -import { border, borderStyle, color, flexbox, grid, layout, shadow, space } from 'styled-system' -import { containers } from '../../styles' - -const Box = styled.div` - ${grid} - ${space} - ${color} - ${layout} - ${flexbox} - ${border} - ${containers} - ${borderStyle} - ${shadow} -` - -Box.defaultProps = { - border: 'none', - height: 'auto', - display: 'flex', -} - -export default Box diff --git a/ui/src/components/button/index.js b/ui/src/components/button/index.js deleted file mode 100644 index ad5b1ec..0000000 --- a/ui/src/components/button/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import styled from 'styled-components' - -import { buttonStyle, shadow, layout, typography, border, space, color } from 'styled-system' - -const Button = styled.button` - font-family: 'Lato'; - font-weight: bold; - letter-spacing: 0.04em; - border: none; - - :disabled { - opacity: 0.4; - background: #ddd; - border-color: #ddd; - color: #666; - box-shadow: none; - cursor: not-allowed; - } - - &:hover { - filter: brightness(95%); - transition: all 0.7s ease; - } - - border-radius: 1px; - - ${buttonStyle} - ${typography} - ${shadow} - ${layout} - ${space} - ${border} - ${color} -` - -Button.defaultProps = { - variant: 'primary', - width: 'max-content', - px: '24px', - height: '50px', - type: 'button', -} - -export default Button diff --git a/ui/src/components/collapse/index.js b/ui/src/components/collapse/index.js deleted file mode 100644 index c0a2772..0000000 --- a/ui/src/components/collapse/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState } from 'react' -import PropTypes from 'prop-types' -import styled from 'styled-components' -import { space } from 'styled-system' -import { icons } from '_assets' - -const Header = styled.div.attrs({ - role: 'button', -})` - cursor: pointer; - display: flex; - align-items: center; - justify-content: space-between; - ${space} -` - -Header.defaultProps = { - paddingTop: 2, - paddingBottom: 2, -} - -const Content = styled.div` - ${space} -` - -const Collapse = ({ title, children, ...props }) => { - const [isCollapseOpen, setCollapseOpen] = useState(props.isOpen) - - const handleHeaderClick = () => { - setCollapseOpen(!isCollapseOpen) - } - - return ( - <> -
- {title} - {isCollapseOpen ? : } -
- {isCollapseOpen && {children}} - - ) -} - -Collapse.propTypes = { - header: PropTypes.node, - title: PropTypes.node.isRequired, - isOpen: PropTypes.bool, - children: PropTypes.node, -} - -Collapse.defaultProps = { - header: null, - children: undefined, - isOpen: false, -} - -export default Collapse diff --git a/ui/src/components/confirmation-dialog/dialog.js b/ui/src/components/confirmation-dialog/dialog.js deleted file mode 100644 index 46c4148..0000000 --- a/ui/src/components/confirmation-dialog/dialog.js +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react' -import { Slide } from 'react-awesome-reveal' -import styled from 'styled-components' -import Box from '_components/box' -import Button from '_components/button' -import Text from '_components/text' - -const Container = styled(Box).attrs({ - bg: 'white', - border: '1px solid neutralLighter', -})` - flex-direction: column; - - border-radius: 4px; - overflow: hidden; - - width: 100%; - max-width: 80%; - height: 200px; - - // Tablets (portrait) - @media (min-width: 768px) and (max-width: 1024px) { - width: auto; - max-width: 600px; - - min-width: 420px; - min-height: 240px; - } - - // Laptops and above - @media (min-width: 1280px) { - width: auto; - max-width: 48px; - - min-width: 420px; - min-height: 240px; - } -` - -const ButtonsArea = styled(Box)` - justify-content: stretch; - height: 60px; - * + * { - border-left: 1px solid ${(props) => props.theme.colors.neutralLighter} !important; - } -` - -const StyledButton = styled(Button)` - border-radius: 0; - background: transparent; - border-top: 1px solid ${(props) => props.theme.colors.neutralLighter} !important; - :hover { - background: ${(props) => props.theme.colors.white}; - } -` - -// eslint-disable-next-line react/prop-types -const Dialog = ({ title, details, isDestructive, onConfirm, onCancel }) => ( - - - - - {title} - - - {details} - - - - - Cancel - - - Confirm - - - - -) - -export default Dialog diff --git a/ui/src/components/confirmation-dialog/index.js b/ui/src/components/confirmation-dialog/index.js deleted file mode 100644 index ee8080a..0000000 --- a/ui/src/components/confirmation-dialog/index.js +++ /dev/null @@ -1,76 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useContext, useState } from 'react' -import { Portal } from 'react-portal' -import Modal from 'styled-react-modal' -import Dialog from './dialog' - -const ConfirmationDialogContext = React.createContext() - -export const useConfirmationDialog = () => useContext(ConfirmationDialogContext) - -const defaultOptions = { - title: 'Are you sure?', - details: 'This cannot be undone', - isDestructive: false, - onConfirm: () => {}, - onCancel: () => {}, -} - -const ConfirmationDialogProvider = ({ children }) => { - const [isOpen, setOpen] = useState(false) - const [options, setOptions] = useState(defaultOptions) - - const onConfirmButtonClick = () => { - options.onConfirm() - closeConfirmationDialog() - } - - const onCancelButtonClick = () => { - options.onCancel() - closeConfirmationDialog() - } - - const openConfirmationDialog = () => { - setOpen(true) - } - - const closeConfirmationDialog = () => { - setOpen(false) - } - - return ( - <> - { - setOptions(defaultOptions) - if (typeof opts === 'object' && opts !== null) { - const merged = Object.assign(options, opts) - setOptions(merged) - } - openConfirmationDialog() - }, - }} - > - {children} - - - - - - - - ) -} - -ConfirmationDialogProvider.propTypes = { - children: PropTypes.node.isRequired, -} - -export default ConfirmationDialogProvider diff --git a/ui/src/components/empty-state/index.js b/ui/src/components/empty-state/index.js deleted file mode 100644 index a8d4586..0000000 --- a/ui/src/components/empty-state/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import styled from 'styled-components' -import { illustrations } from '_assets/' -import Box from '_components/box' -import Text from '_components/text' - -const EmptyStateContainer = styled(Box).attrs({ - border: 'thin', -})` - > svg { - height: 160px; - width: auto; - } - padding: 48px; - flex-direction: column; - align-items: center; - justify-content: center; - background-color: ${(props) => props.theme.colors.white}; - border-color: ${(props) => props.theme.colors.neutralLighter}; -` - -const EmptyState = ({ image, title, description, extra, ...props }) => ( - - {image} - - {title} - - - {description} - - {extra} - -) - -EmptyState.propTypes = { - image: PropTypes.node, - title: PropTypes.string, - description: PropTypes.string, - extra: PropTypes.node, -} - -EmptyState.defaultProps = { - image: , - title: 'No results found', - description: `Oops! It seems that this query has not returned any resources.`, - extra: null, -} - -export default EmptyState diff --git a/ui/src/components/error-state/index.js b/ui/src/components/error-state/index.js deleted file mode 100644 index b9eb04f..0000000 --- a/ui/src/components/error-state/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import styled from 'styled-components' -import { illustrations } from '_assets/' -import Box from '_components/box' -import Text from '_components/text' - -const ErrorStateContainer = styled(Box)` - svg { - height: 160px; - width: auto; - } - padding: 20px; - flex-direction: column; - align-items: center; - justify-content: center; - background-color: ${(props) => props.theme.colors.background1}; -` - -const ErrorState = ({ title, description, extra }) => ( - - - - {title} - - - {description} - - {extra} - -) - -ErrorState.propTypes = { - title: PropTypes.string, - description: PropTypes.string, - extra: PropTypes.node, -} - -ErrorState.defaultProps = { - title: 'Something is not right', - description: 'It seems that an error has occurred. Check your network and try again.', - extra: null, -} - -export default ErrorState diff --git a/ui/src/components/flex/index.js b/ui/src/components/flex/index.js deleted file mode 100644 index af708e0..0000000 --- a/ui/src/components/flex/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import styled from 'styled-components' -import { flex, layout, grid } from 'styled-system' - -const Flex = styled.div` - display: flex; - ${grid} - ${flex} - ${layout} -` - -export default Flex diff --git a/ui/src/components/headers/index.js b/ui/src/components/headers/index.js deleted file mode 100644 index 65e1603..0000000 --- a/ui/src/components/headers/index.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import styled from 'styled-components' - -import Avatar from '_components/avatar' -import Text from '_components/text' -import Box from '_components/box' - -const StyledBox = styled(Box).attrs({ - height: '120px', - display: 'flex', - alignItems: 'center', - padding: 0, - mb: 3, -})` - > :first-child { - margin-right: 16px; - } -` - -const ProjectHeader = props => ( - - -
- some-project - This projects does something really well -
-
-) - -const NodeHeader = props => ( - - xyZ761kgVK - This projects does something really well - -) - -export { ProjectHeader, NodeHeader } diff --git a/ui/src/components/icon-button/index.js b/ui/src/components/icon-button/index.js deleted file mode 100644 index c0c9376..0000000 --- a/ui/src/components/icon-button/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import styled, { css } from 'styled-components' -import { border, borderStyle, layout, space } from 'styled-system' - -export const StyledButton = styled.button` - display: flex; - align-items: center; - justify-content: center; - - border: none; - box-shadow: none; - background: none; - box-sizing: content-box; - padding: 8px; - border-radius: 50%; - outline: none; - - flex-shrink: 0; - width: ${(props) => props.size}; - height: ${(props) => props.size}; - - :disabled { - border-color: ${(props) => props.theme.colors.border1}; - color: ${(props) => props.theme.colors.border1}; - box-shadow: none; - cursor: not-allowed; - svg { - fill: ${(props) => props.theme.colors.border1}; - } - :hover { - background: none; - } - } - - svg { - width: 100%; - height: auto; - fill: ${(props) => - props.color ? props.theme.colors[props.color] : props.theme.colors.foreground3}; - } - - ${(props) => - props.hoverEffect && - css` - :hover { - background: ${props.theme.colors.background2}; - } - `} - - ${layout} - ${space} - ${border} - ${borderStyle} -` - -const IconButton = ({ icon, ...props }) => {icon} - -IconButton.propTypes = { - icon: PropTypes.node, - size: PropTypes.string, - color: PropTypes.string, -} - -IconButton.defaultProps = { - icon: undefined, - color: 'foreground2', - size: '24px', -} - -export default IconButton diff --git a/ui/src/components/icon/index.js b/ui/src/components/icon/index.js deleted file mode 100644 index 80e3bd7..0000000 --- a/ui/src/components/icon/index.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import styled, { css } from 'styled-components' -import { color, border } from 'styled-system' - -import Box from '_components/box' - -export const Container = styled(Box)` - display: flex; - align-items: center; - justify-content: center; - - border: none; - box-shadow: none; - background: none; - box-sizing: border-box; - outline: none; - - flex-shrink: 0; - width: ${(props) => props.size}; - height: ${(props) => props.size}; - - svg { - z-index: 1; - width: 100%; - height: auto; - ${(props) => - props.color && - css` - path { - fill: ${props.theme.colors[props.color]}; - } - `} - } - ${border} - ${color} -` - -const Icon = ({ icon, ...props }) => {icon} - -Icon.propTypes = { - icon: PropTypes.node, - size: PropTypes.string, - color: PropTypes.string, -} - -Icon.defaultProps = { - icon: undefined, - color: undefined, - size: '24px', -} - -export default Icon diff --git a/ui/src/components/inputs/number-input/index.js b/ui/src/components/inputs/number-input/index.js deleted file mode 100644 index d35d6c8..0000000 --- a/ui/src/components/inputs/number-input/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import styled from 'styled-components' -import { layout, space } from 'styled-system' - -const NumberInput = styled.input.attrs({ - type: 'number', -})` - box-sizing: border-box; - - border: none; - border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; - - padding-bottom: 4px; - - font-family: Lato; - font-size: 16px; - - width: 100%; - - :focus { - border-bottom: 1px solid ${(props) => props.theme.colors.primary}; - } - :disabled { - background: inherit; - opacity: 0.5; - border: none; - } - :invalid { - border-bottom: 1px solid ${(props) => props.theme.colors.danger}; - } - ::placeholder { - color: ${(props) => props.theme.colors.neutralLight}; - } - - ::-webkit-outer-spin-button, - ::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - - input[type='number'] { - -moz-appearance: textfield; /* Firefox */ - } - - ${space} - ${layout} -` - -export default NumberInput diff --git a/ui/src/components/inputs/search-input/index.js b/ui/src/components/inputs/search-input/index.js deleted file mode 100644 index 34df4f1..0000000 --- a/ui/src/components/inputs/search-input/index.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { border, color, layout, space } from 'styled-system' -import { icons } from '_assets/' - -const StyledIcon = styled(icons.Search).attrs({ - width: 22, -})` - fill: ${({ theme: { colors } }) => colors.neutral}; - margin: auto; - position: absolute; - left: 8px; - top: 50%; - transform: translateY(-50%); - z-index: 1; -` - -const Container = styled.div` - position: relative; - height: 40px; - ${layout} - ${space} - ${border} - ${color} -` - -const StyledInput = styled.input` - box-sizing: border-box; - padding-left: 32px; - border: 1px solid ${(props) => props.theme.colors.neutralLighter}; - border-radius: 24px; - font-size: 14px; - width: 100%; - height: 100%; - background: ${(props) => props.theme.colors.background1}; - color: ${(props) => props.theme.colors.foreground1}; - - :focus { - border: 1px solid ${(props) => props.theme.colors.primary}; - } - :disabled { - background: inherit; - opacity: 0.5; - border: none; - } - :invalid { - border: 1px solid ${(props) => props.theme.colors.danger}; - } - ${space} - ${layout} -` - -// eslint-disable-next-line react/prop-types -const SearchInput = ({ placeholder, onChange, ...props }) => { - const handleChange = (e) => { - onChange(e.target.value) - } - - return ( - - - - - ) -} - -export default SearchInput diff --git a/ui/src/components/inputs/select-input/index.js b/ui/src/components/inputs/select-input/index.js deleted file mode 100644 index a47b813..0000000 --- a/ui/src/components/inputs/select-input/index.js +++ /dev/null @@ -1,107 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react' -import Select from 'react-select' -import styled from 'styled-components' -import { layout, space } from 'styled-system' - -export const OptionContainer = styled.div` - display: flex; - cursor: pointer; - height: 24px; - padding: 8px; - align-items: center; - :hover { - background-color: ${(props) => props.theme.colors.background2}; - } -` - -const DefaultOptionComponent = ({ innerRef, innerProps, ...props }) => ( - - {props.data.label} - -) - -const DefaultSingleValueComponent = ({ innerRef, innerProps, ...props }) => ( -
- {props.data.label} -
-) - -const StyledSelect = styled(Select).attrs({ - classNamePrefix: 'select', - styles: { - control: (base) => ({ - ...base, - border: 0, - boxShadow: 'none', - }), - }, -})` - border: 1px solid ${(props) => props.theme.colors.neutralLighter}; - height: 48px; - width: 100%; - ${layout} - ${space} - - .select__control { - border: none; - cursor: pointer; - color: ${(props) => props.theme.colors.neutral}; - background-color: ${(props) => props.theme.colors.neutralLightest}; - - height: 100%; - .select__value-container { - padding-left: 12px; - height: 100%; - .select__placeholder { - } - } - :hover { - border: none; - background-color: ${(props) => props.theme.colors.neutralLightest}; - } - } - - .select__menu { - z-index: 2; - color: ${(props) => props.theme.colors.neutral}; - background-color: ${(props) => props.theme.colors.neutralLightest}; - } - - .select__control--is-focused { - box-shadow: none; - border: none; - } - - .select__control--is-disabled { - opacity: 0.5; - cursor: not-allowed; - pointer-events: none; - } - - .select__control--is-focused.select__control--menu-is-open { - box-shadow: none; - border-radius: 2px; - border: 1px solid ${(props) => props.theme.colors.primary}; - :hover { - border: 1px solid ${(props) => props.theme.colors.primary}; - } - } -` - -const SelectInput = ({ optionComponent, singleValueComponent, disabled, ...props }) => ( - -) - -SelectInput.defaultProps = { - disabled: false, - optionComponent: DefaultOptionComponent, - singleValueComponent: DefaultSingleValueComponent, - options: [], -} - -export default SelectInput diff --git a/ui/src/components/inputs/tags-input/index.js b/ui/src/components/inputs/tags-input/index.js deleted file mode 100644 index e577c53..0000000 --- a/ui/src/components/inputs/tags-input/index.js +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react' -import { components } from 'react-select' -import CreatableSelect from 'react-select/creatable' -import styled from 'styled-components' - -const MultiValueLabel = ({ data, ...props }) => ( - {data.label} -) - -const StyledSelect = styled(CreatableSelect).attrs({ - classNamePrefix: 'select', - style: { - control: (base) => ({ - ...base, - border: 0, - boxShadow: 'none', - }), - }, -})` - border: none; - border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; - width: 100%; - - .select__menu { - display: none; - } - - .select__control { - border: none; - border-radius: 0; - height: 100%; - min-height: none; - .select__value-container { - padding-left: ${(props) => (props.hasIcon ? '32px' : '2px')}; - .select__placeholder { - color: ${(props) => props.theme.colors.neutralLight}; - } - } - :hover { - border: none; - } - } - - .select__menu { - z-index: 2; - } - - .select__options { - display: hidden; - } - - .select__control--is-focused { - box-shadow: none; - border: none; - } - - .select__control--is-focused.select__control--menu-is-open { - box-shadow: none; - border: none; - border-bottom: 1px solid ${(props) => props.theme.colors.primary}; - :hover { - border-bottom: 1px solid ${(props) => props.theme.colors.primary}; - } - } - - .select__value-container { - cursor: text; - } - - .select__multi-value__remove { - cursor: pointer; - color: ${(props) => props.theme.colors.primary}; - :hover { - color: ${(props) => props.theme.colors.primary}; - background: ${(props) => props.theme.colors.neutral}; - } - } -` - -const TagsInput = ({ ...props }) => ( - -) - -TagsInput.defaultProps = {} - -export default TagsInput diff --git a/ui/src/components/inputs/text-input/index.js b/ui/src/components/inputs/text-input/index.js deleted file mode 100644 index 9a604c7..0000000 --- a/ui/src/components/inputs/text-input/index.js +++ /dev/null @@ -1,42 +0,0 @@ -import styled from 'styled-components' -import { border, layout, space } from 'styled-system' - -const TextInput = styled.input.attrs({ - type: 'text', - autocomplete: 'off', -})` - box-sizing: border-box; - - border: none; - border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; - - padding-bottom: 4px; - - background-color: ${(props) => props.theme.colors.white}; - color: ${(props) => props.theme.colors.neutralDarker}; - - font-family: Lato; - font-size: 16px; - - width: 100%; - ::placeholder { - color: ${(props) => props.theme.colors.neutralLight}; - } - :focus { - border-bottom: 1px solid ${(props) => props.theme.colors.primary}; - } - :disabled { - opacity: 0.5; - cursor: not-allowed; - border: none; - } - :invalid { - border-bottom: 1px solid ${(props) => props.theme.colors.danger}; - } - - ${border} - ${space} - ${layout} -` - -export default TextInput diff --git a/ui/src/components/key-value/index.js b/ui/src/components/key-value/index.js deleted file mode 100644 index dd6310d..0000000 --- a/ui/src/components/key-value/index.js +++ /dev/null @@ -1,123 +0,0 @@ -import { useFormik } from 'formik' -import PropTypes from 'prop-types' -import React, { useEffect } from 'react' -import styled from 'styled-components' -import * as Yup from 'yup' -import { icons } from '_assets/' -import Box from '_components/box' -import Button from '_components/button' -import IconButton from '_components/icon-button' -import TextInput from '_components/inputs/text-input' -import List from '_components/list' -import Text from '_components/text' - -const Container = styled(Box)` - flex-direction: column; -` - -const RowsContainer = styled(List)` - > * { - :not(:last-child) { - border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; - } - } -` - -const KeyValue = ({ isEditable, rows, onDelete, onAdd, ...props }) => { - const formik = useFormik({ - enableReinitialize: true, - initialValues: { - key: '', - value: '', - }, - validationSchema: Yup.object().shape({ - key: Yup.string().required(), - value: Yup.string(), - }), - validateOnChange: true, - validateOnMount: true, - validateOnBlur: true, - }) - - useEffect(() => { - formik.validateForm() - }, []) - - const handleAddRow = (k, v) => { - onAdd(k, v) - formik.setValues({ key: '', value: '' }) - formik.validateForm() - } - - const handleDeleteRow = (k) => { - onDelete(k) - } - - return ( - - {isEditable && ( - - - - - - )} - - {rows.map((el) => ( - - - {el.key} - - - {el.value} - - } - size="20px" - hoverEffect - ml={2} - onClick={() => handleDeleteRow(el.key)} - /> - - ))} - - - ) -} - -KeyValue.propTypes = { - rows: PropTypes.arrayOf( - PropTypes.shape({ - key: PropTypes.string, - value: PropTypes.string, - }) - ), - isEditable: PropTypes.bool, - onAdd: PropTypes.func, - onDelete: PropTypes.func, -} - -KeyValue.defaultProps = { - rows: [], - isEditable: true, - onAdd: () => {}, - onDelete: () => {}, -} - -export default KeyValue diff --git a/ui/src/components/link/index.js b/ui/src/components/link/index.js deleted file mode 100644 index 5866d7d..0000000 --- a/ui/src/components/link/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import PropTypes from 'prop-types' -import styled from 'styled-components' -import { color, typography, border, space, layout } from 'styled-system' - -import { Link } from '@reach/router' - -const StyledLink = styled(Link)` - display: inline; - - font-family: Lato !important; - - :link { - text-decoration: none; - cursor: pointer; - } - - :visited { - text-decoration: inherit; - cursor: auto; - } - - color: inherit; - - :hover { - ${(props) => props.hoverStyle} - } - - &[aria-current] { - ${(props) => props.activeStyle} - } - - ${color} - ${space} - ${layout} - ${border} - ${typography} -` - -StyledLink.propTypes = { - href: PropTypes.string, - to: PropTypes.string.isRequired, -} - -export default StyledLink diff --git a/ui/src/components/list/index.js b/ui/src/components/list/index.js deleted file mode 100644 index 78d990b..0000000 --- a/ui/src/components/list/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import styled from 'styled-components' -import { layout, space, flexbox } from 'styled-system' - -const List = styled.ul` - overflow: hidden; - overflow-y: auto; - - ::-webkit-scrollbar { - background: transparent; - width: 4px; - height: 4px; - } - - ::-webkit-scrollbar-button { - display: none; - } - - ::-webkit-scrollbar-track { - background-color: transparent; - } - - ::-webkit-scrollbar-track-piece { - background-color: transparent; - } - - ::-webkit-scrollbar-thumb { - background-color: #ececf0; - border-radius: 2px; - } - - ::-webkit-scrollbar-corner { - display: none; - } - - ::-webkit-resizer { - display: none; - } - - ${flexbox} - ${layout} - ${space} -` - -export default List diff --git a/ui/src/components/nav/index.js b/ui/src/components/nav/index.js deleted file mode 100644 index 098c914..0000000 --- a/ui/src/components/nav/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import styled from 'styled-components' - -import Box from '_components/box' -import Link from '_components/link' - -export const Nav = styled(Box).attrs({ - width: '100%', - borderBottom: 'medium', - borderColor: 'neutralLighter', -})`` - -export const HorizontalNavLink = styled(Link).attrs(({ theme }) => ({ - py: 3, - px: 2, - color: 'neutral', - fontSize: '14px', - fontWeight: '500', - marginBottom: '-2px', - activeStyle: { - color: theme.colors.primaryDark, - 'border-bottom': `2px solid ${theme.colors.primary}`, - }, - hoverStyle: { 'border-bottom': `2px solid ${theme.colors.neutralDark}` }, -}))` - :first-child { - margin-left: 0; - } -` - -export const VerticalNavLink = styled(Link).attrs((props) => ({ - py: 2, - color: 'neutral', - activeStyle: { color: props.theme.colors.primary }, -}))`` diff --git a/ui/src/components/network-select-input/index.js b/ui/src/components/network-select-input/index.js deleted file mode 100644 index 14dd3af..0000000 --- a/ui/src/components/network-select-input/index.js +++ /dev/null @@ -1,93 +0,0 @@ -/* eslint-disable react/prop-types */ -import PropTypes from 'prop-types' -import React from 'react' -import styled from 'styled-components' -import { icons } from '_assets/' -import Box from '_components/box' -import Icon from '_components/icon' -import SelectInput, { OptionContainer } from '_components/inputs/select-input' -import Text from '_components/text' - -const StyledText = styled(Text).attrs({ - textStyle: 'body', -})` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -` - -const NetworkOptionComponent = ({ innerRef, innerProps, ...props }) => ( - - } size="32px" color="neutral" /> - - {props.data.name} - - {props.data.addressRange} - - - -) - -const NetworkSingleValueComponent = ({ innerRef, innerProps, ...props }) => ( - - } size="32px" color="neutral" /> - - {props.data.name} - - {props.data.addressRange} - - - -) - -const NetworkSelectInput = ({ selectedId, networks, onChange, placeholder, ...props }) => { - const handleChange = (option) => { - onChange(option.id) - } - - const network = networks.find((el) => el.ID === selectedId) - - const options = networks.map((el) => ({ - id: el.ID, - name: el.Name, - addressRange: el.AddressRange, - })) - - const value = network - ? { id: network.ID, name: network.Name, addressRange: network.AddressRange } - : undefined - - return ( - - ) -} - -NetworkSelectInput.propTypes = { - networks: PropTypes.arrayOf( - PropTypes.shape({ - ID: PropTypes.string, - Name: PropTypes.string, - AddressRange: PropTypes.string, - }) - ), - selectedId: PropTypes.string, - onChange: PropTypes.func, - placeholder: PropTypes.string, -} - -NetworkSelectInput.defaultProps = { - networks: [], - selectedId: undefined, - onChange: () => {}, - placeholder: 'Select network...', -} - -export default NetworkSelectInput diff --git a/ui/src/components/node-select-input/index.js b/ui/src/components/node-select-input/index.js deleted file mode 100644 index 62acf81..0000000 --- a/ui/src/components/node-select-input/index.js +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint-disable react/prop-types */ -import PropTypes from 'prop-types' -import React from 'react' -import styled from 'styled-components' -import { icons } from '_assets/' -import Box from '_components/box' -import Icon from '_components/icon' -import SelectInput, { OptionContainer } from '_components/inputs/select-input' -import Text from '_components/text' - -const StyledText = styled(Text).attrs({ - textStyle: 'body', -})` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -` - -const NodeOptionComponent = ({ innerRef, innerProps, ...props }) => ( - - } size="32px" color="neutral" /> - - {props.data.name} - - {props.data.id} - - - -) - -const NodeSingleValueComponent = ({ innerRef, innerProps, ...props }) => ( - - } size="32px" color="neutral" /> - - {props.data.name} - - {props.data.id} - - - -) - -const NodeSelectInput = ({ selectedId, nodes, onChange, placeholder, ...props }) => { - const handleChange = (option) => { - onChange(option.id) - } - - const node = nodes.find((el) => el.ID === selectedId) - - const options = nodes.map((el) => ({ - id: el.ID, - name: el.Name, - addressRange: el.AddressRange, - })) - - const value = node ? { id: node.ID, name: node.Name } : undefined - - return ( - - ) -} - -NodeSelectInput.propTypes = { - nodes: PropTypes.arrayOf( - PropTypes.shape({ - ID: PropTypes.string, - Name: PropTypes.string, - }) - ), - placeholder: PropTypes.string, - selectedId: PropTypes.string, - onChange: PropTypes.func, -} - -NodeSelectInput.defaultProps = { - nodes: [], - selectedId: undefined, - placeholder: 'Select node...', - onChange: () => {}, -} - -export default NodeSelectInput diff --git a/ui/src/components/popover/index.js b/ui/src/components/popover/index.js deleted file mode 100644 index f68e1f5..0000000 --- a/ui/src/components/popover/index.js +++ /dev/null @@ -1,119 +0,0 @@ -import Tippy from '@tippyjs/react' -import PropTypes from 'prop-types' -import React from 'react' -import styled from 'styled-components' -import 'tippy.js/dist/tippy.css' - -const StyledTippy = styled(Tippy)` - background: transparent; - box-shadow: ${(props) => props.theme.shadows.medium}; - padding: 0; - - width: max-content; - height: max-content; - - .tippy-content { - padding: 0; - } - - &[data-placement^='${(props) => props.placement}'] > .tippy-arrow { - :before { - content: ''; - position: absolute; - ${(props) => props.placement === 'bottom' && 'top: -9px'}; - ${(props) => props.placement === 'top' && 'top: 9px'}; - left: 0; - border: 8px solid transparent; - border-${(props) => props.placement}-color: ${(props) => - props.theme.colors[props.defaultArrowColor] || props.theme.colors.neutralLighter}; - transform-origin: center ${(props) => props.placement}; - ${(props) => props.placement === 'bottom' && 'transform: translateY(-50%)'}; - ${(props) => props.placement === 'top' && 'transform: translateY(50%)'}; - width: 0; - height:0; - } - :after { - content: ''; - position: absolute; - ${(props) => props.placement === 'bottom' && 'top: -7px'}; - ${(props) => props.placement === 'top' && 'top: 7px'}; - left: 0; - border: 8px solid transparent; - border-${(props) => props.placement}-color: ${(props) => - props.theme.colors[props.defaultArrowColor] || props.theme.colors.white}; - transform-origin: center ${(props) => props.placement}; - ${(props) => props.placement === 'bottom' && 'transform: translateY(-50%)'}; - ${(props) => props.placement === 'top' && 'transform: translateY(50%)'}; - width: 0; - height:0; - } -` - -const Popover = ({ - visible, - trigger, - arrow, - hideOnClick, - duration, - delay, - sticky, - touchHold, - interactive, - boundary, - children, - content, - placement, - ...props -}) => ( - -
{children}
-
-) - -Popover.propTypes = { - visible: PropTypes.bool, - trigger: PropTypes.string, - duration: PropTypes.arrayOf(PropTypes.number), - delay: PropTypes.arrayOf(PropTypes.number), - children: PropTypes.node.isRequired, - content: PropTypes.node.isRequired, - arrow: PropTypes.bool, - defaultArrowColor: PropTypes.string, - placement: PropTypes.string, - sticky: PropTypes.bool, - hideOnClick: PropTypes.bool, - touchHold: PropTypes.bool, - interactive: PropTypes.bool, - boundary: PropTypes.string, -} - -Popover.defaultProps = { - visible: undefined, - trigger: 'click', - hideOnClick: true, - duration: [500, 200], - placement: 'bottom', - delay: [100, 10], - arrow: true, - sticky: true, - touchHold: true, - interactive: true, - defaultArrowColor: 'background1', - boundary: 'viewport', -} - -export default Popover diff --git a/ui/src/components/separator/index.js b/ui/src/components/separator/index.js deleted file mode 100644 index 999ca57..0000000 --- a/ui/src/components/separator/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types' -import styled, { css } from 'styled-components' -import { color, layout, space } from 'styled-system' - -const Separator = styled.span` - ${(props) => - props.vertical - ? css` - width: 1px; - height: 100%; - ` - : css` - height: 1px; - width: 100%; - `}; - display: block; - ${layout} - ${space} - ${color} -` - -Separator.propTypes = { - vertical: PropTypes.bool, -} - -Separator.defaultProps = { - vertical: false, - bg: 'neutralLighter', -} - -export default Separator diff --git a/ui/src/components/spinner/dragon.js b/ui/src/components/spinner/dragon.js deleted file mode 100644 index f350892..0000000 --- a/ui/src/components/spinner/dragon.js +++ /dev/null @@ -1,62 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import styled, { keyframes } from 'styled-components' -import { layout } from 'styled-system' - -const rotate = keyframes` - 0% { - transform: rotate(0); - } - 100% { - transform: rotate(-360deg); - } -` - -const Overlay = styled.div` - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: grid; - align-items: center; - justify-items: center; -` - -const Container = styled.div` - ${layout} - - #dragon { - will-change: transform; - transform-origin: center center; - animation: ${rotate} 0.8s infinite linear; - } -` - -const Dragon = ({ size, color, ...props }) => ( - - - - - - - - - -) - -Dragon.propTypes = { - size: PropTypes.number, - color: PropTypes.string, -} - -Dragon.defaultProps = { - size: 90, - color: '#eee', -} - -export default Dragon diff --git a/ui/src/components/spinner/index.js b/ui/src/components/spinner/index.js deleted file mode 100644 index 36d743d..0000000 --- a/ui/src/components/spinner/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import Jellyfish from './jellyfish' -import Dragon from './dragon' - -export { Dragon, Jellyfish } diff --git a/ui/src/components/spinner/jellyfish.js b/ui/src/components/spinner/jellyfish.js deleted file mode 100644 index 887c8b4..0000000 --- a/ui/src/components/spinner/jellyfish.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react' -import styled, { keyframes } from 'styled-components' -import { layout } from 'styled-system' -import PropTypes from 'prop-types' - -const fall = keyframes` - 100% { - transform: translateY(0); - } -` - -const swim = keyframes` - 0% { - transform: scaleX(1) translateX(10px); - } - 49.99999% { - transform: scaleX(1) translateX(0); - } - 50% { - transform: scaleX(-1) translateX(0); - } - 100% { - transform: scaleX(-1) translateX(-10px); - } -` - -const Overlay = styled.div` - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: grid; - align-items: center; - justify-items: center; -` - -const Container = styled.div` - ${layout} - - #bubbles-top-layer { - will-change: transform; - transform: translateY(-200px); - animation: ${fall} 1.5s infinite linear; - } - - #bubbles-bottom-layer { - will-change: transform; - transform: translateY(-200px); - animation: ${fall} 1.5s infinite linear; - } - - #jellyfish #body { - will-change: transform; - transform-origin: center center; - transform: scaleX(-1); - animation: ${swim} 0.35s infinite linear; - } -` - -const Jellyfish = ({ withFace, size, color, ...props }) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - {withFace && ( - <> - - - - - )} - - - - -) - -Jellyfish.propTypes = { - withFace: PropTypes.bool, - size: PropTypes.number, - color: PropTypes.string, -} - -Jellyfish.defaultProps = { - withFace: false, - size: 90, - color: '#eee', -} - -export default Jellyfish diff --git a/ui/src/components/steps/index.js b/ui/src/components/steps/index.js deleted file mode 100644 index 6d4edde..0000000 --- a/ui/src/components/steps/index.js +++ /dev/null @@ -1,38 +0,0 @@ -import styled from 'styled-components' -import { space, layout, grid, flex } from 'styled-system' - -export const StepsContainer = styled.div` - display: flex; - counter-reset: step-counter; - - > * { - opacity: 0.3; - :nth-child(-n+${props => props.currentStep}){ - opacity: 1; - } - } - - ${space} - ${layout} - ${grid} - ${flex} -` - -export const Step = styled.p` - color: ${props => props.theme.colors.primary}; - display: flex; - align-items: center; - :before { - content: counter(step-counter); - display: flex; - align-items: center; - justify-content: center; - margin-right: 8px; - border-radius: 50%; - counter-increment: step-counter; - height: 28px; - width: 28px; - border: 1px solid ${props => props.theme.colors.primary}; - } - z-index: -1; -` diff --git a/ui/src/components/text/index.js b/ui/src/components/text/index.js deleted file mode 100644 index f34d709..0000000 --- a/ui/src/components/text/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import styled from 'styled-components' -import { color, fontSize, fontWeight, layout, space, textAlign, textStyle } from 'styled-system' - -const Text = styled.div` - strong { - font-weight: bold; - } - ${textStyle} - ${textAlign} - ${space} - ${layout} - ${fontSize} - ${fontWeight} - ${color} -` - -export default Text diff --git a/ui/src/components/toast/index.js b/ui/src/components/toast/index.js deleted file mode 100644 index ff24883..0000000 --- a/ui/src/components/toast/index.js +++ /dev/null @@ -1,67 +0,0 @@ -import { ToastContainer as BaseToastContainer } from 'react-toastify' -import 'react-toastify/dist/ReactToastify.min.css' -import styled from 'styled-components' -import IconButton from '_components/icon-button' - -export const ToastContainer = styled(BaseToastContainer).attrs({ - position: 'top-right', - hideProgressBar: true, - toastClassName: 'toast', - bodyClassName: 'body', -})` - margin: 0; - padding: 0; - width: max-content; - height: max-content; - margin-top: 100px; - background: transparent; - .toast { - padding: 0; - margin: 0; - background: none; - overflow: visible; - margin-bottom: 16px; - } -` -export const ToastBody = styled.div` - display: flex; - align-items: center; - min-height: 96px; - width: 400px; - overflow: visible; - position: relative; - border-radius: 2px; - min-height: 86px; - width: 390px; - border: 2px solid ${(props) => props.theme.colors[props.color]}; - background: ${(props) => props.theme.colors.white}; -` - -export const IconContainer = styled.div` - position: absolute; - left: -10px; - top: -10px; -` - -export const ToastContent = styled.p` - padding: 10px; - padding-left: 24px; - padding-right: 32px; - display: block; - line-break: anywhere; - color: ${(props) => props.theme.colors[props.color]}; -` - -export const DismissButton = styled(IconButton).attrs({ - size: '11px', -})` - position: absolute; - right: 4px; - top: 4px; -` - -export const CloseIcon = styled(IconButton)` - position: absolute; - right: 10px; - top: 10px; -` diff --git a/ui/src/components/tooltip/index.js b/ui/src/components/tooltip/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/components/unauthorized-state/index.js b/ui/src/components/unauthorized-state/index.js deleted file mode 100644 index 42d1698..0000000 --- a/ui/src/components/unauthorized-state/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import styled from 'styled-components' - -import Box from '_components/box' -import Text from '_components/text' - -import { illustrations } from '_assets/' - -const UnaurhorizedStateContainer = styled(Box).attrs({ - border: 'none', - height: '300px', -})` - svg { - height: 300px; - width: auto; - } - padding: 20px; - flex-direction: column; - align-items: center; - justify-content: center; -` - -const UnaurhorizedState = ({ title, description }) => ( - - - - {title} - - - {description} - - -) - -UnaurhorizedState.propTypes = { - title: PropTypes.string, - description: PropTypes.string, -} - -UnaurhorizedState.defaultProps = { - title: 'Forbidden', - description: `Oops! It seems that you don't have enough permissions to access this resource`, -} - -export default UnaurhorizedState diff --git a/ui/src/containers/footer/index.js b/ui/src/containers/footer/index.js deleted file mode 100644 index 7bb573b..0000000 --- a/ui/src/containers/footer/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { grid, space, color } from 'styled-system' - -const Container = styled.div` - background: white; - border-top: 1px solid #f1f1f1; - - display: flex; - justify-content: center; - - align-items: center; - * + * { - margin-left: 12px; - } - ${space} - ${grid} - - z-index: 99; -` - -const StyledLink = styled.a` - font-size: 14px; - text-decoration: none; - &:hover { - color: ${(props) => props.theme.colors.primary}; - } - ${color} -` - -StyledLink.defaultProps = { - color: 'neutralDarker', -} - -const Footer = (props) => ( - - Support - Docs - -) - -Footer.defaultProps = { - padding: 0, -} - -export default Footer diff --git a/ui/src/containers/header/index.js b/ui/src/containers/header/index.js deleted file mode 100644 index 617c526..0000000 --- a/ui/src/containers/header/index.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { grid, space, color, shadow, border } from 'styled-system' - -import Brand from '_containers/side-nav/brand' -import Flex from '_components/flex' -import Link from '_components/link' - -export const Container = styled.div` - display: flex; - - align-items: center; - justify-content: space-between; - - border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; - position: fixed; - - top: 0; - right: 0; - left: 0; - - z-index: 199; - - ${border} - ${shadow} - ${color} - ${space} - ${grid} -` - -Container.defaultProps = { - backgroundColor: 'white', - boxShadow: 'light', - border: 'dark', -} - -const StyledLink = styled(Link)` - margin: auto; - padding-right: 18px; - :hover { - color: ${({ theme }) => theme.colors.neutralDarker}; - } -` - -const Header = (props) => ( - - - - - ACL Tokens - - - -) - -Header.defaultProps = { - padding: 3, -} - -export default Header diff --git a/ui/src/containers/side-nav/brand.js b/ui/src/containers/side-nav/brand.js deleted file mode 100644 index dd7388e..0000000 --- a/ui/src/containers/side-nav/brand.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { colorStyle } from 'styled-system' -import { Link } from '@reach/router' -import { ReactComponent as Logo } from '_assets/icons/logo.svg' - -const StyledLink = styled(Link)` - position: relative; - - height: auto; - - display: flex; - align-items: center; - - svg { - fill: ${(props) => props.theme.colors.primary}; - opacity: 0.3; - } - &:hover { - svg { - opacity: 1; - transition: all 0.4s linear; - } - } - ${colorStyle} -` - -const Brand = (props) => ( - - - -) - -export default Brand diff --git a/ui/src/containers/side-nav/index.js b/ui/src/containers/side-nav/index.js deleted file mode 100644 index a714504..0000000 --- a/ui/src/containers/side-nav/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { CollapsibleSection, Container, NavLink } from './styled' - -const SideNav = (props) => ( - - - Networks - Clients - - -) - -SideNav.defaultProps = { - colors: 'darkHighContrast', -} - -export default SideNav diff --git a/ui/src/containers/side-nav/styled.js b/ui/src/containers/side-nav/styled.js deleted file mode 100644 index f9d17e2..0000000 --- a/ui/src/containers/side-nav/styled.js +++ /dev/null @@ -1,115 +0,0 @@ -import styled from 'styled-components' -import { color, colorStyle, grid, layout, space } from 'styled-system' -import Box from '_components/box' -import Collapse from '_components/collapse' -import Link from '_components/link' -import Separator from '_components/separator' - -export const Container = styled.div` - ${grid} - ${space} - ${layout} - ${colorStyle} - - position: fixed; - width: 200px; - - display: flex; - flex-direction: column; - padding-top: 100px; - - top: 0; - left: 0; - bottom: 0; - - border-right: 1px solid ${(props) => props.theme.colors.neutralLighter}; - background: transparent; - - z-index: 9; - - display: none; - - // Laptops and above - @media (min-width: 1280px) { - display: flex; - } -` - -export const StyledSeparator = styled(Separator).attrs({ - bg: 'border1', - my: 3, -})`` - -export const NavButton = styled(Box)` - * { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - display: flex; - align-items: center; - cursor: pointer; - - font-size: 16px; - color: ${({ theme }) => theme.colors.primary}; - :hover { - border-right: 3px solid !important; - border-color: ${({ theme }) => theme.colors.primary}!important; - } - - padding-top: 8px; - padding-bottom: 8px; - - border-right: ${(props) => (props.isCurrent ? '3px solid' : 'none')}; - border-color: ${(props) => (props.isCurrent ? props.theme.colors.primary : '')}; - - ${color} - ${space} - ${layout} -` - -export const NavLink = styled(Link).attrs((props) => ({ - px: 3, - - getProps: ({ isPartiallyCurrent }) => ({ - style: { - borderRight: isPartiallyCurrent ? '3px solid' : 'none', - borderColor: isPartiallyCurrent ? props.theme.colors.primary : '', - }, - }), -}))` - * { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - display: flex; - align-items: center; - - font-size: 16px; - color: ${({ theme }) => theme.colors.primary}; - :hover { - border-right: 3px solid !important; - border-color: ${({ theme }) => theme.colors.primary}!important; - } - - padding-top: 8px; - padding-bottom: 8px; - - ${color} - ${space} - ${layout} -` - -export const CollapsibleSection = styled(Collapse).attrs({ - px: 3, - py: 2, -})` - color: ${(props) => props.theme.colors.neutralLight}; - font-weight: 500; - font-size: 0.76rem; - letter-spacing: 0.06rem; - text-transform: uppercase; -` diff --git a/ui/src/environment.js b/ui/src/environment.js deleted file mode 100644 index beb457a..0000000 --- a/ui/src/environment.js +++ /dev/null @@ -1,3 +0,0 @@ -export const DEBUG = process.env.REACT_APP_DEBUG || false - -export const REST_API_URL = process.env.REACT_APP_REST_API_URL || 'http://localhost:8080' diff --git a/ui/src/graphql/apollo-provider.js b/ui/src/graphql/apollo-provider.js deleted file mode 100644 index 09dea7c..0000000 --- a/ui/src/graphql/apollo-provider.js +++ /dev/null @@ -1,101 +0,0 @@ -import { ApolloProvider, throwServerError } from '@apollo/client' -import { ApolloClient } from 'apollo-boost' -import { InMemoryCache } from 'apollo-cache-inmemory' -import { ApolloLink } from 'apollo-link' -import { setContext } from 'apollo-link-context' -import { onError } from 'apollo-link-error' -import { RestLink } from 'apollo-link-rest' -import log from 'loglevel' -import PropTypes from 'prop-types' -import React from 'react' -import { createNetworkStatusNotifier } from 'react-apollo-network-status' -import { useToast } from '_utils/toast-provider' -import { REST_API_URL } from '../environment' -import { defaults } from './local-state' - -const { - link: networkStatusLink, -} = createNetworkStatusNotifier() - -export async function customFetch(requestInfo, init) { - const response = await fetch(requestInfo, init) - const res = response.clone() - - if (!res.ok) { - const body = await res.json() - if (response.status === 404) { - throwServerError(res, body, 'Not found error') - } - if (response.status === 500) { - throwServerError(res, body, `Internal error: ${body.Message}`) - } - } - return response -} - -export const CustomApolloProvider = ({ children }) => { - const { error } = useToast() - const errorLink = onError(({ graphQLErrors, networkError }) => { - if (graphQLErrors) { - graphQLErrors.forEach(({ message, locations, path }) => - log.error(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`) - ) - } - if (networkError) { - log.error('[Apollo Provider] Network error: ', networkError) - if (networkError.statusCode > 300 && networkError.statusCode !== 404) { - if (networkError.result && networkError.result.Message) { - error(`${networkError.result.Message}`) - } - } - // navigate('/ui/') - } - }) - - const withToken = setContext(() => ({ token: localStorage.getItem('drago.settings.acl.token') })) - - const authLink = new ApolloLink((operation, forward) => { - const { token } = operation.getContext() - if (token && token !== null) { - operation.setContext(({ headers }) => ({ - headers: { - ...headers, - 'X-Drago-Token': `${token}`, - }, - })) - } - return forward(operation) - }) - - const restLink = new RestLink({ - uri: REST_API_URL, - customFetch, - }) - - const cache = new InMemoryCache({ - dataIdFromObject: (object) => { - // eslint-disable-next-line no-underscore-dangle - switch (object.__typename) { - default: - return object.ID - } - }, - }) - cache.writeData(defaults) - - const client = new ApolloClient({ - link: networkStatusLink.concat(ApolloLink.from([withToken, errorLink, authLink, restLink])), - cache, - typeDefs: {}, - connectToDevTools: true, - queryDeduplication: true, - }) - - return {children} -} - -CustomApolloProvider.propTypes = { - children: PropTypes.node.isRequired, -} - -export default CustomApolloProvider diff --git a/ui/src/graphql/local-state.js b/ui/src/graphql/local-state.js deleted file mode 100644 index 80203c4..0000000 --- a/ui/src/graphql/local-state.js +++ /dev/null @@ -1,12 +0,0 @@ -const defaults = { - data: { - toggleState: true, - }, -} - -const resolvers = { - Query: {}, - Mutation: {}, -} - -export { defaults, resolvers } diff --git a/ui/src/graphql/mutations/index.js b/ui/src/graphql/mutations/index.js deleted file mode 100644 index 381fb17..0000000 --- a/ui/src/graphql/mutations/index.js +++ /dev/null @@ -1,81 +0,0 @@ -import gql from 'graphql-tag' - -export const CREATE_NETWORK = gql` - mutation createNetwork($name: String!, $addressRange: String!) { - createNetwork(input: { Name: $name, AddressRange: $addressRange }) - @rest(method: "POST", path: "/api/networks/", type: "Network") { - ID - } - } -` - -export const DELETE_NETWORK = gql` - mutation deleteNetwork($id: Int!) { - deleteNetwork(id: $id) - @rest(method: "DELETE", path: "/api/networks/{args.id}", type: "Network") { - ID - } - } -` - -export const CREATE_INTERFACE = gql` - mutation createInterface($name: String!, $addressRange: String!) { - createInterface(input: { NetworkID: $networkId, NodeID: $nodeId }) - @rest(method: "POST", path: "/api/interfaces/", type: "Interface") { - ID - } - } -` - -export const UPDATE_INTERFACE = gql` - mutation updateInterface( - $id: String! - $address: String! - $listenPort: Number! - $dns: String! - $mtu: String! - ) { - updateInterface( - id: $id - input: { id: $id, address: $address, listenPort: $listenPort, dns: $dns, mtu: $mtu } - ) @rest(method: "POST", path: "/api/interfaces/{args.id}", type: "Interface") { - ID - } - } -` - -export const DELETE_INTERFACE = gql` - mutation deleteInterface($id: Int!) { - deleteInterface(id: $id) - @rest(method: "DELETE", path: "/api/interfaces/{args.id}", type: "Interface") { - ID - } - } -` - -export const CREATE_CONNECTION = gql` - mutation createConnection($connection: Connection!) { - createConnection(input: $connection) - @rest(method: "POST", path: "/api/connections/", type: "Connection") { - ID - } - } -` - -export const UPDATE_CONNECTION = gql` - mutation updateConnection($id: String!, $connection: Connection!) { - updateConnection(id: $id, input: $connection) - @rest(method: "POST", path: "/api/connections/{args.id}", type: "Connection") { - ID - } - } -` - -export const DELETE_CONNECTION = gql` - mutation deleteConnection($id: Int!) { - deleteConnection(id: $id) - @rest(method: "DELETE", path: "/api/connections/{args.id}", type: "Connection") { - ID - } - } -` diff --git a/ui/src/graphql/queries/index.js b/ui/src/graphql/queries/index.js deleted file mode 100644 index 9e0fff1..0000000 --- a/ui/src/graphql/queries/index.js +++ /dev/null @@ -1,253 +0,0 @@ -import gql from 'graphql-tag' - -export const GET_SELF_TOKEN = gql` - query getSelf { - result: user @rest(path: "/api/acl/tokens/self", type: "Token") { - ID - Type - Name - Secret - Policies - CreatedAt - UpdatedAt - } - } -` - -export const GET_NETWORKS = gql` - query getNetworks { - result: networks @rest(type: "Network", path: "/api/networks/") { - ID - Name - AddressRange - InterfacesCount - ConnectionsCount - CreatedAt - UpdatedAt - } - } -` - -export const GET_NETWORK = gql` - query getNetwork($id: Int!) { - result: getNetwork(id: $id) @rest(type: "Network", path: "/api/networks/{args.id}") { - ID - Name - AddressRange - InterfacesCount - ConnectionsCount - CreatedAt - UpdatedAt - } - } -` - -export const GET_NETWORK_WITH_INTERFACES = gql` - query getNetwork($id: Int!) { - result: getNode(id: $id) @rest(type: "Network", path: "/api/networks/{args.id}") { - ID @export(as: "networkId") - Name - AddressRange - CreatedAt - UpdatedAt - Interfaces - @rest(type: "Interface", path: "/api/interfaces/?network={exportVariables.networkId}") { - ID - Name - HasPublicKey - NodeID @export(as: "nodeId") - Node @rest(type: "Node", path: "/api/nodes/{exportVariables.nodeId}/") { - ID - Name - Status - AdvertiseAddress - } - NetworkID - ConnectionsCount - Address - } - CreatedAt - UpdatedAt - } - } -` - -export const GET_NODES = gql` - query getNodes { - result: nodes @rest(type: "Node", path: "/api/nodes/") { - ID - Name - Status - InterfacesCount - ConnectionsCount - CreatedAt - UpdatedAt - } - } -` - -export const GET_NODE = gql` - query getNode($id: Int!) { - result: getNode(id: $id) @rest(type: "Node", path: "/api/nodes/{args.id}") { - ID - Name - AdvertiseAddress - Meta - Status - CreatedAt - UpdatedAt - } - } -` - -export const GET_NODE_WITH_INTERFACES = gql` - query getNode($id: Int!) { - result: getNode(id: $id) @rest(type: "Node", path: "/api/nodes/{args.id}") { - ID @export(as: "nodeId") - Name - Address - Status - Meta - Interfaces @rest(type: "Interface", path: "/api/interfaces/?node={exportVariables.nodeId}") { - ID - Name - Address - NodeID - ConnectionsCount - NetworkID @export(as: "networkId") - Network @rest(type: "Network", path: "/api/networks/{exportVariables.networkId}") { - ID - Name - AddressRange - } - } - CreatedAt - UpdatedAt - } - } -` - -// TODO: migrate interface-node aggregation to the -// server to avoid multiple requests -export const GET_PEERS = gql` - query getPeers($networkId: String!) { - result: getPeers(networkId: $networkId) - @rest(type: "Interface", path: "/api/interfaces/?network={args.networkId}") { - ID - Name - Address - NetworkID - NodeID @export(as: "nodeId") - Node @rest(type: "Node", path: "/api/nodes/{exportVariables.nodeId}") { - ID - Name - Address - } - CreatedAt - UpdatedAt - } - } -` - -export const GET_PEER = gql` - query getPeer($interfaceId: String!) { - result: getPeer(interfaceId: $interfaceId) - @rest(type: "Interface", path: "/api/interfaces/{args.interfaceId}") { - ID - Name - Address - NetworkID - NodeID @export(as: "nodeId") - Node @rest(type: "Node", path: "/api/nodes/{exportVariables.nodeId}") { - ID - Name - Address - } - CreatedAt - UpdatedAt - } - } -` - -export const GET_INTERFACES = gql` - query getInterfaces($nodeId: String!, $networkId: String!) { - result: getInterfaces(nodeId: $nodeId, networkId: $networkId) - @rest( - type: "Interface" - path: "/api/interfaces/?node={args.nodeId}&network={args.networkId}" - ) { - ID - PublicKey - HasPublicKey - Name - Address - ListenPort - DNS - NodeID - NetworkID @export(as: "networkId") - ConnectionsCount - CreatedAt - UpdatedAt - Network @rest(type: "Network", path: "/api/networks/{exportVariables.networkId}") { - ID - Name - AddressRange - } - } - } -` - -export const GET_INTERFACE = gql` - query getInterface($interfaceId: String!) { - result: getInterface(interfaceId: $interfaceId) - @rest(type: "Interface", path: "/api/interfaces/{args.interfaceId}") { - ID - Name - Address - PublicKey - ListenPort - NodeID - NetworkID - ConnectionsCount - HasPublicKey - CreatedAt - UpdatedAt - } - } -` - -export const GET_CONNECTIONS = gql` - query getConnections($interfaceId: String!, $nodeId: String!, $networkId: String!) { - result: getConnections(interfaceId: $interfaceId, nodeId: $nodeId, networkId: $networkId) - @rest( - type: "Connection" - path: "/api/connections/?interface={args.interfaceId}&node={args.nodeId}&network={args.networkId}" - ) { - ID - Peers - PersistentKeepalive - NetworkID @export(as: "networkId") - CreatedAt - UpdatedAt - Network @rest(type: "Network", path: "/api/networks/{exportVariables.networkId}") { - ID - Name - AddressRange - } - } - } -` - -export const GET_CONNECTION = gql` - query getConnection($connectionId: String!) { - result: getConnection(connectionId: $connectionId) - @rest(type: "Connection", path: "/api/connections/{args.connectionId}") { - ID - PeerSettings - PersistentKeepalive - NetworkID @export(as: "networkId") - CreatedAt - UpdatedAt - } - } -` diff --git a/ui/src/index.js b/ui/src/index.js deleted file mode 100644 index 00f8413..0000000 --- a/ui/src/index.js +++ /dev/null @@ -1,40 +0,0 @@ -import { Router } from '@reach/router' -import React from 'react' -import ReactDOM from 'react-dom' -import styled, { ThemeProvider } from 'styled-components' -import { BaseModalBackground, ModalProvider } from 'styled-react-modal' -import ConfirmationDialogProvider from '_components/confirmation-dialog' -import ApolloProvider from '_graphql/apollo-provider' -import ToastProvider from '_utils/toast-provider' -import App from '_views/app' -import NotFound from '_views/not-found' -import * as serviceWorker from './serviceWorker' -import { GlobalStyles, themes } from './styles' -import './assets/fonts/index.css' - -const theme = 'light' - -const ModalBackground = styled(BaseModalBackground)` - z-index: 999; -` - -ReactDOM.render( - - - - - - - - - - - - - - - , - document.getElementById('root') -) - -serviceWorker.unregister() diff --git a/ui/src/mock/commands.js b/ui/src/mock/commands.js deleted file mode 100644 index 904a14c..0000000 --- a/ui/src/mock/commands.js +++ /dev/null @@ -1,6 +0,0 @@ -import { handler } from './util' - -export default { - createNetwork: handler(() => null), - deleteNetwork: handler(() => null), -} diff --git a/ui/src/mock/factories.js b/ui/src/mock/factories.js deleted file mode 100644 index 41ba35e..0000000 --- a/ui/src/mock/factories.js +++ /dev/null @@ -1,35 +0,0 @@ -import faker from 'faker' -import { Factory, trait } from 'miragejs' - -export default { - token: Factory.extend({ - withTime: trait({ - createdAt: () => faker.date.recent(), - updatedAt: () => faker.date.recent(), - }), - }), - network: Factory.extend({ - withTime: trait({ - createdAt: () => faker.date.recent(), - updatedAt: () => faker.date.recent(), - }), - }), - node: Factory.extend({ - withTime: trait({ - createdAt: () => faker.date.recent(), - updatedAt: () => faker.date.recent(), - }), - }), - interface: Factory.extend({ - withTime: trait({ - createdAt: () => faker.date.recent(), - updatedAt: () => faker.date.recent(), - }), - }), - connection: Factory.extend({ - withTime: trait({ - createdAt: () => faker.date.recent(), - updatedAt: () => faker.date.recent(), - }), - }), -} diff --git a/ui/src/mock/identity.js b/ui/src/mock/identity.js deleted file mode 100644 index 8c86248..0000000 --- a/ui/src/mock/identity.js +++ /dev/null @@ -1,33 +0,0 @@ -import { v4 as uuidv4 } from 'uuid' - -export default class { - constructor() { - this.ids = new Set() - } - - // Returns a new unused unique identifier. - fetch() { - let uuid = uuidv4() - while (this.ids.has(uuid)) { - uuid = uuidv4() - } - - this.ids.add(uuid) - - return uuid - } - - // Registers an identifier as used. Must throw if identifier is already used. - set(id) { - if (this.ids.has(id)) { - throw new Error(`ID ${id} has already been used.`) - } - - this.ids.add(id) - } - - // Resets all used identifiers to unused. - reset() { - this.ids.clear() - } -} diff --git a/ui/src/mock/index.js b/ui/src/mock/index.js deleted file mode 100644 index 28d71a6..0000000 --- a/ui/src/mock/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import { createServer, RestSerializer } from 'miragejs' - -import seeds from './seeds' -import models from './models' -import routes from './routes' -import factories from './factories' - -import IdentityManager from './identity' - -export default () => { - createServer({ - identityManagers: { - application: IdentityManager, - }, - serializers: { - application: RestSerializer, - }, - models, - seeds, - routes() { - routes(this) - }, - factories, - }) -} diff --git a/ui/src/mock/models.js b/ui/src/mock/models.js deleted file mode 100644 index 5ae194e..0000000 --- a/ui/src/mock/models.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Model, belongsTo, hasMany } from 'miragejs' - -export default { - networks: Model.extend({}), - - nodes: Model.extend({ - interfaces: hasMany('interface'), - }), - - interfaces: Model.extend({ - node: belongsTo('node'), - network: belongsTo('network'), - connections: hasMany('connection'), - }), - - connections: Model.extend({ - from: belongsTo('interface'), - to: belongsTo('interface'), - }), -} diff --git a/ui/src/mock/queries.js b/ui/src/mock/queries.js deleted file mode 100644 index 46e3c9b..0000000 --- a/ui/src/mock/queries.js +++ /dev/null @@ -1,5 +0,0 @@ -import { handler } from './util' - -export default { - getSelf: handler((schema, request, self) => self.user.attrs), -} diff --git a/ui/src/mock/routes.js b/ui/src/mock/routes.js deleted file mode 100644 index ee5f2bf..0000000 --- a/ui/src/mock/routes.js +++ /dev/null @@ -1,19 +0,0 @@ -import { REST_API_URL } from '../environment' -import C from './commands' -import Q from './queries' - -const routes = (server) => { - const get = (path, handler) => server.get(`${REST_API_URL}${path}`, handler, { timing: 1000 }) - const post = (path, handler) => server.post(`${REST_API_URL}${path}`, handler, { timing: 2000 }) - - // Setup query routes - get('/api/self/token', Q.getSelf) - - // Setup command routes - post('/api/networks/', C.createNetwork) - - // Setup passthrough routes (to which requests will not be intercepted by Mirage) - // server.passthrough() -} - -export default routes diff --git a/ui/src/mock/seeds.js b/ui/src/mock/seeds.js deleted file mode 100644 index 08931ef..0000000 --- a/ui/src/mock/seeds.js +++ /dev/null @@ -1,19 +0,0 @@ -export default (server) => { - const create = (...args) => server.create(...args, 'withTime') - const networks = { - n1: create('network', { name: 'net1' }), - n2: create('network', { name: 'net2' }), - } - const nodes = { - n1: create('node', { name: 'node1' }), - n2: create('node', { name: 'node2' }), - } - const interfaces = { - i1: create('interface', { name: 'wg0', node: nodes.n1, network: networks.n1 }), - i2: create('interface', { name: 'wg0', node: nodes.n2, network: networks.n2 }), - } - // eslint-disable-next-line no-unused-vars - const connections = { - c1: create('interface', { from: interfaces.i1, to: interfaces.i2 }), - } -} diff --git a/ui/src/mock/util.js b/ui/src/mock/util.js deleted file mode 100644 index 9096332..0000000 --- a/ui/src/mock/util.js +++ /dev/null @@ -1,9 +0,0 @@ -export const handler = (f) => (schema, request) => { - const context = {} - const self = {} - request.body = JSON.parse(request.requestBody) - request.headers = request.requestHeaders - return f(schema, request, self, context) -} - -export default {} diff --git a/ui/src/modals/admit-node/index.js b/ui/src/modals/admit-node/index.js deleted file mode 100644 index 1935dd3..0000000 --- a/ui/src/modals/admit-node/index.js +++ /dev/null @@ -1,118 +0,0 @@ -import { useQuery } from '@apollo/client' -import PropTypes from 'prop-types' -import React, { useEffect, useState } from 'react' -import styled from 'styled-components' -import Modal from 'styled-react-modal' -import { icons } from '_assets/' -import Box from '_components/box' -import Button from '_components/button' -import IconButton from '_components/icon-button' -import SearchInput from '_components/inputs/search-input' -import List from '_components/list' -import Text from '_components/text' -import { GET_NODES } from '_graphql/queries' -import NodeCard from './node-card' - -const Container = styled(Box)` - height: 600px; - width: 400px; - background: ${(props) => props.theme.colors.white}; - border: 1px solid; - border-color: ${(props) => props.theme.colors.neutralLighter}; - border-radius: 4px; - padding: 32px; - flex-direction: column; - padding-bottom: 32px; - position: relative; -` - -const CloseButton = styled(IconButton).attrs({ - icon: , - size: '32px', - color: 'neutralLight', -})` - position: absolute; - top: 0; - right: 0; -` - -const AdmitNodeModal = ({ isOpen, onAdmit, onClose }) => { - const [searchString, setSearchString] = useState('') - const [selectedNode, setSelectedNode] = useState() - const getNodesQuery = useQuery(GET_NODES, {}) - - const handleNodeCardClick = (id) => { - setSelectedNode(selectedNode !== id ? id : undefined) - } - - const handleAdmitButtonClick = () => { - onAdmit(selectedNode) - onClose() - } - - useEffect(() => { - window.scrollTo(0, 0) - }, []) - - useEffect(() => { - setSearchString('') - setSelectedNode(undefined) - getNodesQuery.refetch() - }, [isOpen]) - - const networks = getNodesQuery.data ? getNodesQuery.data.result : [] - const filteredNetworks = networks.filter((el) => el.Name.includes(searchString)) - - return ( - - - - - Admit Node - - setSearchString(s)} - mb={3} - placeholder="Search for nodes..." - /> - - {filteredNetworks.map((el) => ( - handleNodeCardClick(el.ID)} - /> - ))} - - - - - ) -} - -AdmitNodeModal.propTypes = { - isOpen: PropTypes.bool, - onClose: PropTypes.func, - onAdmit: PropTypes.func, -} - -AdmitNodeModal.defaultProps = { - isOpen: false, - onAdmit: () => {}, - onClose: () => {}, -} - -export default AdmitNodeModal diff --git a/ui/src/modals/admit-node/node-card.js b/ui/src/modals/admit-node/node-card.js deleted file mode 100644 index 4d4dd2f..0000000 --- a/ui/src/modals/admit-node/node-card.js +++ /dev/null @@ -1,73 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import styled, { css } from 'styled-components' -import { icons } from '_assets/' -import Box from '_components/box' -import Icon from '_components/icon' -import Text from '_components/text' - -const Container = styled(Box).attrs({ - display: 'flex', - p: 2, -})` - height: max-content; - cursor: pointer; - align-items: center; - border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; - :last-child { - border-bottom: none; - } -` - -const StyledIcon = styled(Icon)` - width: 36px; - height: 36px; - padding: 4px; - border-radius: 4px; - background: ${(props) => props.theme.colors.neutralLighter}; - align-items: center; - justify-content: center; -` - -const SelectionIndicator = styled(Box).attrs({})` - border: 1px solid ${(props) => props.theme.colors.neutralLight}; - width: 10px; - height: 10px; - border-radius: 50%; - ${(props) => - props.isSelected && - css` - background: ${props.theme.colors.neutralDarker}; - `}; -` - -const NodeCard = ({ id, name, isSelected, onClick }) => ( - onClick(id)}> - - - } color="neutralDarker" /> - - - {name} - - - {id} - - - - -) - -NodeCard.propTypes = { - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - isSelected: PropTypes.bool, - onClick: PropTypes.func, -} - -NodeCard.defaultProps = { - onClick: () => {}, - isSelected: false, -} - -export default NodeCard diff --git a/ui/src/modals/connect-peer/index.js b/ui/src/modals/connect-peer/index.js deleted file mode 100644 index 5998fb1..0000000 --- a/ui/src/modals/connect-peer/index.js +++ /dev/null @@ -1,185 +0,0 @@ -import { useLazyQuery, useQuery } from '@apollo/client' -import { useParams } from '@reach/router' -import PropTypes from 'prop-types' -import React, { useEffect, useState } from 'react' -import styled from 'styled-components' -import Modal from 'styled-react-modal' -import { icons } from '_assets/' -import Box from '_components/box' -import Button from '_components/button' -import IconButton from '_components/icon-button' -import SearchInput from '_components/inputs/search-input' -import List from '_components/list' -import NetworkSelectInput from '_components/network-select-input' -import Text from '_components/text' -import { GET_NETWORKS, GET_NODE_WITH_INTERFACES, GET_PEERS } from '_graphql/queries' -import PeerCard from './peer-card' - -const Container = styled(Box)` - height: 600px; - width: 400px; - background: ${(props) => props.theme.colors.white}; - border: 1px solid; - border-color: ${(props) => props.theme.colors.neutralLighter}; - border-radius: 4px; - padding: 32px; - flex-direction: column; - padding-bottom: 32px; - position: relative; -` - -const CloseButton = styled(IconButton).attrs({ - icon: , - size: '32px', - color: 'neutralLight', -})` - position: absolute; - top: 0; - right: 0; -` - -const ConnectPeerModal = ({ isOpen, onJoin, onClose }) => { - const { nodeId } = useParams() - - const [networks, setNetworks] = useState([]) - const [selectedNetwork, setSelectedNetwork] = useState() - - const [peers, setPeers] = useState([]) - const [selectedPeer, setSelectedPeer] = useState() - - const [searchString, setSearchString] = useState('') - - const getNodeQuery = useQuery(GET_NODE_WITH_INTERFACES, { - variables: { id: nodeId }, - }) - - const handleGetNetworksQueryData = (data) => { - setNetworks(data.result) - } - - const handleGetPeersQueryData = (data) => { - setPeers(data.result) - } - - const getNetworksQuery = useQuery(GET_NETWORKS, { - onCompleted: handleGetNetworksQueryData, - }) - - const [getPeers, getPeersQuery] = useLazyQuery(GET_PEERS, { - onCompleted: handleGetPeersQueryData, - }) - - useEffect(() => { - window.scrollTo(0, 0) - }, []) - - useEffect(() => { - if (isOpen) { - setPeers([]) - setNetworks([]) - setSelectedPeer(undefined) - setSelectedNetwork(undefined) - if (getPeersQuery.called) { - getNetworksQuery.refetch({}).then((res) => handleGetNetworksQueryData(res.data)) - } - getPeersQuery.refetch() - setSearchString('') - } - }, [isOpen]) - - useEffect(() => { - if (getPeersQuery.called) { - getPeersQuery.refetch({ nodeId: '', networkId: selectedNetwork }) - } else { - getPeers({ - variables: { nodeId: '', networkId: selectedNetwork }, - }) - } - }, [selectedNetwork]) - - const handleSelectedNetworkChanged = (id) => { - setSelectedNetwork(id) - setSelectedPeer(undefined) - } - - const handlePeerCardClick = (id) => { - setSelectedPeer(selectedPeer !== id ? id : undefined) - } - - const handleJoinButtonClick = () => { - onJoin(selectedNetwork, selectedPeer) - onClose() - } - - const node = getNodeQuery.data ? getNodeQuery.data.result : { Interfaces: [] } - - // Filter network options to include only those containing the current node - const nodeNetworkIDs = node.Interfaces.map((el) => el.NetworkID) - const nodeNetworks = networks.filter( - (el) => nodeNetworkIDs.find((id) => id === el.ID) !== undefined - ) - - // Find node interface within the selected network - const sourceInterface = node.Interfaces.find((el) => el.NetworkID === selectedNetwork) - - const filteredPeers = peers - .filter((el) => el.ID !== sourceInterface.ID) // Do not show source interface as an option - .filter((el) => (el.Name !== null ? el.Name.includes(searchString) : true)) // Filter peers based on search query - - return ( - - - - - Connect to Peer - - - setSearchString(s)} - mb={3} - placeholder="Search for peers..." - /> - - {filteredPeers.map((el) => ( - handlePeerCardClick(el.ID)} - /> - ))} - - - - - ) -} - -ConnectPeerModal.propTypes = { - isOpen: PropTypes.bool, - onJoin: PropTypes.func, - onClose: PropTypes.func, -} - -ConnectPeerModal.defaultProps = { - isOpen: false, - onJoin: () => {}, - onClose: () => {}, -} - -export default ConnectPeerModal diff --git a/ui/src/modals/connect-peer/peer-card.js b/ui/src/modals/connect-peer/peer-card.js deleted file mode 100644 index 9dacd8b..0000000 --- a/ui/src/modals/connect-peer/peer-card.js +++ /dev/null @@ -1,74 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import styled, { css } from 'styled-components' -import { icons } from '_assets/' -import Box from '_components/box' -import Icon from '_components/icon' -import Text from '_components/text' - -const Container = styled(Box).attrs({ - display: 'flex', - p: 2, -})` - height: max-content; - cursor: pointer; - align-items: center; - border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; - :last-child { - border-bottom: none; - } -` - -const StyledIcon = styled(Icon)` - width: 36px; - height: 36px; - padding: 4px; - border-radius: 4px; - background: ${(props) => props.theme.colors.neutralLighter}; - align-items: center; - justify-content: center; -` - -const SelectionIndicator = styled(Box).attrs({})` - border: 1px solid ${(props) => props.theme.colors.neutralLight}; - width: 10px; - height: 10px; - border-radius: 50%; - ${(props) => - props.isSelected && - css` - background: ${props.theme.colors.neutralDarker}; - `}; -` - -const PeerCard = ({ id, name, address, isSelected, onClick }) => ( - onClick(id)}> - - - } color="neutralDarker" /> - - - {name} - - - {address ? `${address}` : 'NA'} - - - - -) - -PeerCard.propTypes = { - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - address: PropTypes.string.isRequired, - isSelected: PropTypes.bool, - onClick: PropTypes.func, -} - -PeerCard.defaultProps = { - onClick: () => {}, - isSelected: false, -} - -export default PeerCard diff --git a/ui/src/modals/join-network/index.js b/ui/src/modals/join-network/index.js deleted file mode 100644 index e1ca1d5..0000000 --- a/ui/src/modals/join-network/index.js +++ /dev/null @@ -1,118 +0,0 @@ -import { useQuery } from '@apollo/client' -import PropTypes from 'prop-types' -import React, { useEffect, useState } from 'react' -import styled from 'styled-components' -import Modal from 'styled-react-modal' -import { icons } from '_assets/' -import Box from '_components/box' -import Button from '_components/button' -import IconButton from '_components/icon-button' -import SearchInput from '_components/inputs/search-input' -import List from '_components/list' -import Text from '_components/text' -import { GET_NETWORKS } from '_graphql/queries' -import NetworkCard from './network-card' - -const Container = styled(Box)` - height: 600px; - width: 400px; - background: ${(props) => props.theme.colors.white}; - border: 1px solid; - border-color: ${(props) => props.theme.colors.neutralLighter}; - border-radius: 4px; - padding: 32px; - flex-direction: column; - padding-bottom: 32px; - position: relative; -` - -const CloseButton = styled(IconButton).attrs({ - icon: , - size: '32px', - color: 'neutralLight', -})` - position: absolute; - top: 0; - right: 0; -` - -const JoinNetworkModal = ({ isOpen, onJoin, onClose }) => { - const [searchString, setSearchString] = useState('') - const [selectedNetwork, setSelectedNetwork] = useState() - const getNetworksQuery = useQuery(GET_NETWORKS, {}) - - const handleNetworkCardClick = (id) => { - setSelectedNetwork(selectedNetwork !== id ? id : undefined) - } - - const handleJoinButtonClick = () => { - onJoin(selectedNetwork) - onClose() - } - - useEffect(() => { - window.scrollTo(0, 0) - }, []) - - useEffect(() => { - setSearchString('') - setSelectedNetwork(undefined) - getNetworksQuery.refetch() - }, [isOpen]) - - const networks = getNetworksQuery.data ? getNetworksQuery.data.result : [] - const filteredNetworks = networks.filter((el) => el.Name.includes(searchString)) - - return ( - - - - - Join Network - - setSearchString(s)} - mb={3} - placeholder="Search for networks..." - /> - - {filteredNetworks.map((el) => ( - handleNetworkCardClick(el.ID)} - /> - ))} - - - - - ) -} - -JoinNetworkModal.propTypes = { - isOpen: PropTypes.bool, - onJoin: PropTypes.func, - onClose: PropTypes.func, -} - -JoinNetworkModal.defaultProps = { - isOpen: false, - onJoin: () => {}, - onClose: () => {}, -} - -export default JoinNetworkModal diff --git a/ui/src/modals/join-network/network-card.js b/ui/src/modals/join-network/network-card.js deleted file mode 100644 index 644db73..0000000 --- a/ui/src/modals/join-network/network-card.js +++ /dev/null @@ -1,91 +0,0 @@ -import * as pluralize from 'pluralize' -import PropTypes from 'prop-types' -import React from 'react' -import styled, { css } from 'styled-components' -import { icons } from '_assets/' -import Box from '_components/box' -import Icon from '_components/icon' -import Text from '_components/text' - -const Container = styled(Box).attrs({ - display: 'flex', - p: 2, -})` - height: max-content; - cursor: pointer; - align-items: center; - border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; - :last-child { - border-bottom: none; - } -` - -const StyledIcon = styled(Icon)` - width: 36px; - height: 36px; - padding: 4px; - border-radius: 4px; - background: ${(props) => props.theme.colors.neutralLighter}; - align-items: center; - justify-content: center; -` - -const Badge = styled(Box)` - color: ${(props) => props.theme.colors.neutralDark}; - background: ${(props) => props.theme.colors.neutralLighter}; - padding: 4px 8px; - border-radius: 2px; - width: 48px; - justify-content: center; -` -const SelectionIndicator = styled(Box).attrs({})` - border: 1px solid ${(props) => props.theme.colors.neutralLight}; - width: 10px; - height: 10px; - border-radius: 50%; - ${(props) => - props.isSelected && - css` - background: ${props.theme.colors.neutralDarker}; - `}; -` - -const NetworkCard = ({ id, name, ipAddressRange, numHosts, isSelected, onClick }) => ( - onClick(id)}> - - - } color="neutralDarker" /> - - - {name} - - - {ipAddressRange} - - - - - - - {numHosts} {pluralize('host', numHosts)} - - - - -) - -NetworkCard.propTypes = { - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - ipAddressRange: PropTypes.string.isRequired, - isSelected: PropTypes.bool, - numHosts: PropTypes.number.isRequired, - onClick: PropTypes.func, -} - -NetworkCard.defaultProps = { - onClick: () => {}, - isSelected: false, -} - -export default NetworkCard diff --git a/ui/src/serviceWorker.js b/ui/src/serviceWorker.js deleted file mode 100644 index 2d3f769..0000000 --- a/ui/src/serviceWorker.js +++ /dev/null @@ -1,132 +0,0 @@ -/* eslint-disable */ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA - -const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) -) - -export function register(config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href) - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return - } - - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config) - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ) - }) - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config) - } - }) - } -} - -function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing - if (installingWorker == null) { - return - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ) - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration) - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.') - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration) - } - } - } - } - } - }) - .catch(error => { - console.error('Error during service worker registration:', error) - }) -} - -function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type') - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload() - }) - }) - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config) - } - }) - .catch(() => { - console.log('No internet connection found. App is running in offline mode.') - }) -} - -export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister() - }) - } -} \ No newline at end of file diff --git a/ui/src/styles/index.js b/ui/src/styles/index.js deleted file mode 100644 index 1cdd6f6..0000000 --- a/ui/src/styles/index.js +++ /dev/null @@ -1,54 +0,0 @@ -import reset from 'styled-reset' -import { variant } from 'styled-system' -import { createGlobalStyle } from 'styled-components' - -import lightTheme from './themes/light' - -const GlobalStyles = createGlobalStyle` - ${reset} - - :root { - height: 100%; - font-family: Lato; - text-rendering: optimizeLegibility; - outline: none; - } - - body { - height: 100%; - } - - body::-webkit-scrollbar { - width: 8px; - height: 8px; - background-color: ${(props) => props.theme.colors.background3}; - } - - body::-webkit-scrollbar-track { - border-radius: 4px; - background-color: ${(props) => props.theme.colors.background3}; - } - - body::-webkit-scrollbar-thumb { - border-radius: 4px; - background-color: ${(props) => props.theme.colors.border1}; - } - - * { - outline: none; - -webkit-tap-highlight-color: transparent; - } - - button { - cursor: pointer; - } -` - -const containers = variant({ - scale: 'containers', - prop: 'type', -}) - -const themes = { light: lightTheme } - -export { GlobalStyles, containers, themes } diff --git a/ui/src/styles/themes/light.js b/ui/src/styles/themes/light.js deleted file mode 100644 index 61409ac..0000000 --- a/ui/src/styles/themes/light.js +++ /dev/null @@ -1,165 +0,0 @@ -const colors = { - white: '#fff', - black: '#131d2a', - transparent: 'rgba(0,0,0,0)', - - primaryDark: '#000000', - primary: '#333333', - primaryLight: '666666', - primaryLighter: '999999', - primaryLightest: 'CCCCCC', - - secondary: '#00000', - - neutralDarkest: '#555c66', - neutralDarker: '#666d77', - neutralDark: '#8d949b', - neutral: '#b1b6bd', - neutralLight: '#c3c9ce', - neutralLighter: '#ececf0', - neutralLightest: '#fafbfc', - - green: '#00c582', - yellow: '#ffc85e', - violet: '#c66ce0', - blue: '#43d1ff', - purple: '#8b74ff', - orange: '#ff824c', - success: '#21ba45', - warning: '#fbbd08', - danger: '#ff5353', -} - -const gradients = { - primary: 'linear-gradient(45deg, #00a1ff 0%, #1effc3 100%)', -} - -const shadows = { - light: '1px 2px 4px 0 rgba(0, 0, 0, 0.05)', - medium: '0px 2px 8px rgba(0, 0, 0, 0.1);', - heavy: '1px 2px 4px 0 rgba(0, 0, 0, 0.2)', - primary: '0 4px 9px rgba(0, 161, 255, 0.33)', -} - -const borders = { - thin: `1px solid`, - medium: `2px solid`, - thick: `3px solid`, - weird: `4px solid ${colors.primary}`, - discrete: `1px solid ${colors.neutralLighter}`, -} - -const breakpoints = ['200px', '52em', '64em'] - -const mediaQueries = { - small: `@media screen and (min-width: ${breakpoints[0]})`, - medium: `@media screen and (min-width: ${breakpoints[1]})`, - large: `@media screen and (min-width: ${breakpoints[2]})`, -} - -export default { - colors, - shadows, - borders, - containers: { - 'grid-12': { - display: 'grid', - gridTemplate: 'auto / repeat(12, 76px)', - gridGap: '24px', - }, - 'grid-6': { - display: 'grid', - gridTemplate: 'auto / repeat(6, 72px)', - gridGap: '16px', - }, - padded: { - padding: '24px 32px', - }, - }, - buttons: { - glowing: { - background: gradients.primary, - boxShadow: shadows.primary, - color: colors.white, - }, - primary: { background: colors.primary, color: colors.white }, - secondary: { background: colors.secondary, color: colors.white }, - neutral: { background: colors.neutralLighter, color: colors.neutralDarker }, - warning: { background: colors.transparent, color: colors.warning }, - danger: { background: colors.transparent, color: colors.danger }, - primaryInverted: { - background: colors.transparent, - border: borders.thin, - borderColor: colors.primary, - color: colors.primary, - }, - dangerInverted: { - background: colors.transparent, - color: colors.danger, - border: borders.thin, - borderColor: colors.danger, - }, - }, - colorStyles: { - darkHighContrast: { - background: colors.primaryDark, - color: colors.neutralLight, - borderColor: colors.neutralLight, - fill: colors.neutralLight, - }, - lightHighContrast: { - background: colors.white, - color: colors.neutralDark, - borderColor: colors.neutralDark, - fill: colors.neutralDark, - }, - }, - textStyles: { - title: { - fontSize: 30, - fontFamily: 'Raleway', - fontWeight: 'bold', - color: colors.neutralDarkest, - }, - subtitle: { - fontSize: 24, - fontFamily: 'Raleway', - fontWeight: 'bold', - lineHeight: '18px', - color: colors.neutralDarker, - }, - description: { - fontSize: 16, - fontFamily: 'sans-serif', - fontWeight: '400', - lineHeight: '22px', - color: colors.neutralDark, - }, - bodyText: { - fontSize: 16, - fontFamily: 'Lato', - fontWeight: '400', - lineHeight: '18px', - color: colors.neutralDarker, - }, - detail: { - fontSize: 11, - fontFamily: 'sans-serif', - fontWeight: '400', - lineHeight: '14px', - color: colors.neutralDarker, - }, - code: { - fontSize: 14, - fontFamily: 'open-sans', - fontWeight: '400', - lineHeight: '16px', - color: colors.neutralDark, - }, - }, - fontSizes: [10, 12, 14, 16, 20, 24, 32, 48, 64, 72], - sizes: [0.5, 1, 2, 4, 8, 16, 32, 48, 64, 72, 96, 128], - space: [0, 4, 8, 16, 32, 64, 128, 256, 512], - breakpoints, - mediaQueries, -} diff --git a/ui/src/utils/formik-utils.js b/ui/src/utils/formik-utils.js deleted file mode 100644 index e0cd810..0000000 --- a/ui/src/utils/formik-utils.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { Portal } from 'react-portal' -import Box from '_components/box' -import { DEBUG } from '../environment' - -const Container = styled(Box).attrs({ - border: 'discrete', -})` - padding: 12px; - position: absolute; - bottom: 40px; - left: 20px; - height: auto; -` - -const FormikState = (props) => - DEBUG ? ( - - -
-
-            {JSON.stringify(props, null, 4)}
-          
-
-
-
- ) : null - -export default FormikState diff --git a/ui/src/utils/hocs/index.js b/ui/src/utils/hocs/index.js deleted file mode 100644 index 9ff4289..0000000 --- a/ui/src/utils/hocs/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export { default as withValidityIndicator } from './with-validity-indicator' diff --git a/ui/src/utils/hocs/with-validity-indicator.js b/ui/src/utils/hocs/with-validity-indicator.js deleted file mode 100644 index bc3a240..0000000 --- a/ui/src/utils/hocs/with-validity-indicator.js +++ /dev/null @@ -1,58 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import styled from 'styled-components' -import { icons } from '_assets/' -import Box from '_components/box' -import Popover from '_components/popover' -import Text from '_components/text' - -const ErrorIconContainer = styled.div` - position: absolute; - right: -28px; - top: 50%; - transform: translateY(-50%); -` - -const ErrorTooltip = styled.div` - padding: 8px; - max-width: 180px; - background: #fff; - border-radius: 4px; -` - -const ErrorIndicator = ({ error }) => { - if (error === undefined) return null - return ( - - - - {error} - - - } - > - - - - ) -} - -ErrorIndicator.propTypes = { - error: PropTypes.string, -} - -ErrorIndicator.defaultProps = { - error: undefined, -} - -const withValidityIndicator = (input, error) => ( - - {input} - - -) - -export default withValidityIndicator diff --git a/ui/src/utils/toast-provider.js b/ui/src/utils/toast-provider.js deleted file mode 100644 index c088949..0000000 --- a/ui/src/utils/toast-provider.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useContext } from 'react' -import { toast } from 'react-toastify' - -import { icons } from '_assets/' - -import { - ToastContainer, - ToastBody, - IconContainer, - DismissButton, - ToastContent, -} from '_components/toast' - -const displayToast = (content, icon, color, options = {}) => { - toast( - ({ closeToast }) => ( - - {icon} - {content} - } color={color} onClick={() => closeToast()} /> - - ), - { ...options, autoClose: 3000, closeButton: false } - ) -} - -const ToastContext = React.createContext() - -export const useToast = () => useContext(ToastContext) - -const ToastProvider = ({ children }) => ( - <> - displayToast(text, icon, color, options), - error: (text, options) => displayToast(text, , 'danger', options), - warning: (text, options) => displayToast(text, , 'warning', options), - success: (text, options) => displayToast(text, , 'success', options), - }} - > - {children} - - - -) - -ToastProvider.propTypes = { - children: PropTypes.node.isRequired, -} - -export default ToastProvider diff --git a/ui/src/utils/use-localstorage.js b/ui/src/utils/use-localstorage.js deleted file mode 100644 index de92196..0000000 --- a/ui/src/utils/use-localstorage.js +++ /dev/null @@ -1,65 +0,0 @@ -import log from 'loglevel' -import { useEffect, useState } from 'react' - -function useLocalStorage(key, initialValue) { - // Get from local storage then - // parse stored json or return initialValue - const readValue = () => { - // Prevent build error "window is undefined" but keep keep working - if (typeof window === 'undefined') { - return initialValue - } - try { - const item = window.localStorage.getItem(key) - return item || initialValue - } catch (error) { - log.warn(`Error reading localStorage key “${key}”:`, error) - return initialValue - } - } - // State to store our value - // Pass initial state function to useState so logic is only executed once - const [storedValue, setStoredValue] = useState(readValue) - // Return a wrapped version of useState's setter function that ... - // ... persists the new value to localStorage. - const setValue = (value) => { - // Prevent build error "window is undefined" but keep keep working - if (typeof window === 'undefined') { - log.warn(`Tried setting localStorage key “${key}” even though environment is not a client`) - } - try { - // Allow value to be a function so we have the same API as useState - const newValue = value instanceof Function ? value(storedValue) : value - if (newValue === null || newValue === undefined) { - window.localStorage.removeItem(key) - } else { - // Save to local storage - window.localStorage.setItem(key, newValue) - } - // Save state - setStoredValue(newValue) - // We dispatch a custom event so every useLocalStorage hook are notified - window.dispatchEvent(new Event('local-storage')) - } catch (error) { - log.warn(`Error setting localStorage key “${key}”:`, error) - } - } - useEffect(() => { - setStoredValue(readValue()) - }, []) - useEffect(() => { - const handleStorageChange = () => { - setStoredValue(readValue()) - } - // this only works for other documents, not the current one - window.addEventListener('storage', handleStorageChange) - // this is a custom event, triggered in writeValueToLocalStorage - window.addEventListener('local-storage', handleStorageChange) - return () => { - window.removeEventListener('storage', handleStorageChange) - window.removeEventListener('local-storage', handleStorageChange) - } - }, []) - return [storedValue, setValue] -} -export default useLocalStorage diff --git a/ui/src/views/app/index.js b/ui/src/views/app/index.js deleted file mode 100644 index a00bf06..0000000 --- a/ui/src/views/app/index.js +++ /dev/null @@ -1,56 +0,0 @@ -import { Router } from '@reach/router' -import React from 'react' -import styled from 'styled-components' -import Footer from '_containers/footer' -import Header from '_containers/header' -import SideNav from '_containers/side-nav' -import ClientsRouter from '_views/clients' -import HomeView from '_views/home' -import NetworksRouter from '_views/networks' -import NotFound from '_views/not-found' -import SettingsRouter from '_views/settings' - -const Dashboard = styled.div` - position: relative; - display: grid; - height: 100vh; - grid-template: 72px auto 40px / auto; - grid-template-areas: - 'header' - 'body' - 'footer'; -` - -const Content = styled(Router).attrs({ primary: false })` - padding-top: 84px; - padding-bottom: 32px; - - grid-area: body; - - width: 90%; - max-width: 800px; - justify-self: center; - - // Laptops and above - @media (min-width: 1280px) { - padding-left: 200px; - } -` - -const App = () => ( - -
- - - - - {/* */} - {/* */} - - - - -