├── .envrc ├── .github ├── CODEOWNERS ├── dependabot.yml ├── services │ └── go-tests │ │ └── docker-compose.yml └── workflows │ ├── actionlint.yml │ └── go-tests.yml ├── .gitignore ├── .go-version ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── waypoint-hzn │ └── main.go ├── docker-compose.yml ├── go.mod ├── go.sum ├── hack └── Dockerfile.evanphx ├── internal ├── pkg │ └── golang-petname │ │ ├── LICENSE │ │ ├── README.md │ │ ├── cmd │ │ └── petname │ │ │ └── main.go │ │ ├── debian │ │ ├── changelog │ │ ├── compat │ │ ├── control │ │ ├── copyright │ │ ├── golang-petname-dev.install │ │ ├── golang-petname.install │ │ ├── lintian-overrides │ │ ├── rules │ │ ├── source │ │ │ └── format │ │ └── update-wordlists.sh │ │ ├── golang-petname.1 │ │ ├── petname.go │ │ └── petname_test.go └── testsql │ └── testsql.go ├── kubernetes └── waypoint-hzn.yml ├── migrations ├── 20200521162525_initial.down.sql └── 20200521162525_initial.up.sql ├── pkg ├── models │ └── models.go ├── pb │ ├── gen.go │ ├── server.pb.go │ └── server.pb.validate.go └── server │ ├── auth.go │ ├── grpc.go │ ├── grpc_log.go │ ├── grpc_log_test.go │ ├── server.go │ ├── service.go │ ├── service_account.go │ ├── service_account_test.go │ ├── service_hostname.go │ ├── service_hostname_test.go │ └── testing.go ├── proto ├── defs │ └── validate.proto └── server.proto └── shell.nix /.envrc: -------------------------------------------------------------------------------- 1 | # If we are a computer with nix-shell available, then use that to setup 2 | # the build environment with exactly what we need. 3 | if has nix-shell; then 4 | use nix 5 | fi 6 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hashicorp/waypoint 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" -------------------------------------------------------------------------------- /.github/services/go-tests/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | localstack: 4 | image: docker.mirror.hashicorp.services/localstack/localstack:1.3 5 | ports: 6 | - 4510-4559:4510-4559 7 | - 4566:4566 8 | - 5678:5678 9 | pebble: 10 | image: docker.mirror.hashicorp.services/letsencrypt/pebble 11 | command: pebble 12 | environment: 13 | PEBBLE_VA_NOSLEEP: 1 14 | PEBBLE_VA_ALWAYS_VALID: 1 15 | postgres: 16 | image: docker.mirror.hashicorp.services/circleci/postgres:11-alpine 17 | environment: 18 | POSTGRES_USER: postgres 19 | POSTGRES_DB: waypoint_test 20 | ports: 21 | - "5432:5432" 22 | vault: 23 | image: docker.mirror.hashicorp.services/vault 24 | command: server -dev -dev-root-token-id=hznroot 25 | ports: 26 | - "8200:8200" 27 | -------------------------------------------------------------------------------- /.github/workflows/actionlint.yml: -------------------------------------------------------------------------------- 1 | # If the repository is public, be sure to change to GitHub hosted runners 2 | name: Lint GitHub Actions Workflows 3 | on: 4 | push: 5 | pull_request: 6 | permissions: 7 | contents: read 8 | jobs: 9 | actionlint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 13 | - name: "Check workflow files" 14 | uses: docker://docker.mirror.hashicorp.services/rhysd/actionlint:latest 15 | -------------------------------------------------------------------------------- /.github/workflows/go-tests.yml: -------------------------------------------------------------------------------- 1 | name: hashicorp/waypoint-hzn/go-tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | check-vendor: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 11 | - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 12 | with: 13 | go-version-file: go.mod 14 | - run: go mod tidy 15 | - run: | 16 | if ! git diff --exit-code; then 17 | echo "Git directory has vendor changes" 18 | exit 1 19 | fi 20 | lint: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 24 | - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 25 | with: 26 | cache: true 27 | cache-dependency-path: go.sum 28 | go-version-file: go.mod 29 | - uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 30 | with: 31 | version: v1.50.0 32 | args: --timeout 3m00s 33 | skip-pkg-cache: true 34 | skip-build-cache: true 35 | dev-build: 36 | runs-on: ubuntu-latest 37 | outputs: 38 | go-version: ${{ steps.go-version.outputs.go-version }} 39 | steps: 40 | - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 41 | - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 42 | with: 43 | go-version-file: go.mod 44 | - id: go-version 45 | run: echo "go-version=$(cat ./.go-version)" >> "$GITHUB_OUTPUT" 46 | - uses: hashicorp/actions-go-build@v0.1.7 47 | with: 48 | go_version: ${{ steps.go-version.outputs.go-version }} 49 | os: linux 50 | arch: amd64 51 | reproducible: nope 52 | # TODO: inquire about versioning 53 | instructions: |- 54 | mkdir -p dist 55 | go build -o dist ./cmd/waypoint-hzn 56 | go-test: 57 | runs-on: ubuntu-latest 58 | env: 59 | TEST_RESULTS_DIR: "/tmp/test-results" 60 | GO_TAGS: server 61 | GOTESTSUM_RELEASE: 1.8.2 62 | 63 | steps: 64 | - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 65 | - name: Start Services 66 | run: |- 67 | docker compose -f .github/services/go-tests/docker-compose.yml up --detach --no-color --wait 68 | - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 69 | with: 70 | go-version-file: './go.mod' 71 | - name: Install gotestsum 72 | run: |- 73 | url=https://github.com/gotestyourself/gotestsum/releases/download 74 | curl -sSL "${url}/v${GOTESTSUM_RELEASE}/gotestsum_${GOTESTSUM_RELEASE}_linux_amd64.tar.gz" | \ 75 | sudo tar -xz --overwrite -C /usr/local/bin gotestsum 76 | - run: go mod download 77 | - name: Waiting for Postgres to be ready 78 | run: |- 79 | for _ in $(seq 1 10); 80 | do 81 | nc -z localhost 5432 && echo Success && exit 0 82 | echo -n . 83 | sleep 1 84 | done 85 | echo Failed waiting for Postgres && exit 1 86 | - name: go test 87 | env: 88 | PACKAGE_NAMES: ./... 89 | POSTGRES_USER: postgres 90 | POSTGRES_DB: waypoint_test 91 | run: |- 92 | mkdir -p "$TEST_RESULTS_DIR" 93 | echo "Testing \"$PACKAGE_NAMES\"" 94 | gotestsum --format=short-verbose \ 95 | --junitfile "$TEST_RESULTS_DIR"/gotestsum-report.xml -- \ 96 | -tags="$GOTAGS" -p 2 \ 97 | -cover -coverprofile=coverage.txt \ 98 | "$PACKAGE_NAMES" 99 | - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 100 | with: 101 | name: test-results 102 | path: "/tmp/test-results" 103 | 104 | - name: Stop containers 105 | if: always() 106 | run: |- 107 | docker compose -f .github/services/go-tests/docker-compose.yml down 108 | permissions: 109 | contents: read 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | tmp/ 3 | 4 | # System-specific 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.go-version: -------------------------------------------------------------------------------- 1 | 1.19.4 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker.mirror.hashicorp.services/docker/dockerfile:experimental 2 | 3 | FROM docker.mirror.hashicorp.services/golang:alpine AS builder 4 | 5 | RUN apk add --no-cache git gcc libc-dev openssh 6 | 7 | RUN mkdir -p /tmp/prime 8 | COPY go.sum /tmp/prime 9 | COPY go.mod /tmp/prime 10 | 11 | WORKDIR /tmp/prime 12 | 13 | RUN mkdir -p -m 0600 ~/.ssh \ 14 | && ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts 15 | RUN git config --global url.ssh://git@github.com/.insteadOf https://github.com/ 16 | RUN --mount=type=ssh --mount=type=secret,id=ssh.config --mount=type=secret,id=ssh.key \ 17 | GIT_SSH_COMMAND="ssh -o \"ControlMaster auto\" -F \"/run/secrets/ssh.config\"" \ 18 | GOPRIVATE=github.com/hashicorp \ 19 | go mod download 20 | 21 | COPY . /tmp/src 22 | WORKDIR /tmp/src 23 | 24 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=ssh go build -o /tmp/waypoint-hzn -ldflags "-X main.sha1ver=`git rev-parse HEAD` -X main.buildTime=$(date +'+%FT%T.%N%:z')" ./cmd/waypoint-hzn 25 | 26 | FROM docker.mirror.hashicorp.services/alpine 27 | 28 | COPY --from=builder /tmp/waypoint-hzn /usr/bin/waypoint-hzn 29 | 30 | RUN GRPC_HEALTH_PROBE_VERSION=v0.3.2 && \ 31 | wget -qO/usr/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 32 | chmod +x /usr/bin/grpc_health_probe 33 | 34 | COPY ./migrations /migrations 35 | 36 | ENTRYPOINT ["/usr/bin/waypoint-hzn"] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 HashiCorp, Inc. 2 | 3 | Mozilla Public License Version 2.0 4 | ================================== 5 | 6 | 1. Definitions 7 | -------------- 8 | 9 | 1.1. "Contributor" 10 | means each individual or legal entity that creates, contributes to 11 | the creation of, or owns Covered Software. 12 | 13 | 1.2. "Contributor Version" 14 | means the combination of the Contributions of others (if any) used 15 | by a Contributor and that particular Contributor's Contribution. 16 | 17 | 1.3. "Contribution" 18 | means Covered Software of a particular Contributor. 19 | 20 | 1.4. "Covered Software" 21 | means Source Code Form to which the initial Contributor has attached 22 | the notice in Exhibit A, the Executable Form of such Source Code 23 | Form, and Modifications of such Source Code Form, in each case 24 | including portions thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | (a) that the initial Contributor has attached the notice described 30 | in Exhibit B to the Covered Software; or 31 | 32 | (b) that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the 34 | terms of a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | means any form of the work other than Source Code Form. 38 | 39 | 1.7. "Larger Work" 40 | means a work that combines Covered Software with other material, in 41 | a separate file or files, that is not Covered Software. 42 | 43 | 1.8. "License" 44 | means this document. 45 | 46 | 1.9. "Licensable" 47 | means having the right to grant, to the maximum extent possible, 48 | whether at the time of the initial grant or subsequently, any and 49 | all of the rights conveyed by this License. 50 | 51 | 1.10. "Modifications" 52 | means any of the following: 53 | 54 | (a) any file in Source Code Form that results from an addition to, 55 | deletion from, or modification of the contents of Covered 56 | Software; or 57 | 58 | (b) any new file in Source Code Form that contains any Covered 59 | Software. 60 | 61 | 1.11. "Patent Claims" of a Contributor 62 | means any patent claim(s), including without limitation, method, 63 | process, and apparatus claims, in any patent Licensable by such 64 | Contributor that would be infringed, but for the grant of the 65 | License, by the making, using, selling, offering for sale, having 66 | made, import, or transfer of either its Contributions or its 67 | Contributor Version. 68 | 69 | 1.12. "Secondary License" 70 | means either the GNU General Public License, Version 2.0, the GNU 71 | Lesser General Public License, Version 2.1, the GNU Affero General 72 | Public License, Version 3.0, or any later versions of those 73 | licenses. 74 | 75 | 1.13. "Source Code Form" 76 | means the form of the work preferred for making modifications. 77 | 78 | 1.14. "You" (or "Your") 79 | means an individual or a legal entity exercising rights under this 80 | License. For legal entities, "You" includes any entity that 81 | controls, is controlled by, or is under common control with You. For 82 | purposes of this definition, "control" means (a) the power, direct 83 | or indirect, to cause the direction or management of such entity, 84 | whether by contract or otherwise, or (b) ownership of more than 85 | fifty percent (50%) of the outstanding shares or beneficial 86 | ownership of such entity. 87 | 88 | 2. License Grants and Conditions 89 | -------------------------------- 90 | 91 | 2.1. Grants 92 | 93 | Each Contributor hereby grants You a world-wide, royalty-free, 94 | non-exclusive license: 95 | 96 | (a) under intellectual property rights (other than patent or trademark) 97 | Licensable by such Contributor to use, reproduce, make available, 98 | modify, display, perform, distribute, and otherwise exploit its 99 | Contributions, either on an unmodified basis, with Modifications, or 100 | as part of a Larger Work; and 101 | 102 | (b) under Patent Claims of such Contributor to make, use, sell, offer 103 | for sale, have made, import, and otherwise transfer either its 104 | Contributions or its Contributor Version. 105 | 106 | 2.2. Effective Date 107 | 108 | The licenses granted in Section 2.1 with respect to any Contribution 109 | become effective for each Contribution on the date the Contributor first 110 | distributes such Contribution. 111 | 112 | 2.3. Limitations on Grant Scope 113 | 114 | The licenses granted in this Section 2 are the only rights granted under 115 | this License. No additional rights or licenses will be implied from the 116 | distribution or licensing of Covered Software under this License. 117 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 118 | Contributor: 119 | 120 | (a) for any code that a Contributor has removed from Covered Software; 121 | or 122 | 123 | (b) for infringements caused by: (i) Your and any other third party's 124 | modifications of Covered Software, or (ii) the combination of its 125 | Contributions with other software (except as part of its Contributor 126 | Version); or 127 | 128 | (c) under Patent Claims infringed by Covered Software in the absence of 129 | its Contributions. 130 | 131 | This License does not grant any rights in the trademarks, service marks, 132 | or logos of any Contributor (except as may be necessary to comply with 133 | the notice requirements in Section 3.4). 134 | 135 | 2.4. Subsequent Licenses 136 | 137 | No Contributor makes additional grants as a result of Your choice to 138 | distribute the Covered Software under a subsequent version of this 139 | License (see Section 10.2) or under the terms of a Secondary License (if 140 | permitted under the terms of Section 3.3). 141 | 142 | 2.5. Representation 143 | 144 | Each Contributor represents that the Contributor believes its 145 | Contributions are its original creation(s) or it has sufficient rights 146 | to grant the rights to its Contributions conveyed by this License. 147 | 148 | 2.6. Fair Use 149 | 150 | This License is not intended to limit any rights You have under 151 | applicable copyright doctrines of fair use, fair dealing, or other 152 | equivalents. 153 | 154 | 2.7. Conditions 155 | 156 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 157 | in Section 2.1. 158 | 159 | 3. Responsibilities 160 | ------------------- 161 | 162 | 3.1. Distribution of Source Form 163 | 164 | All distribution of Covered Software in Source Code Form, including any 165 | Modifications that You create or to which You contribute, must be under 166 | the terms of this License. You must inform recipients that the Source 167 | Code Form of the Covered Software is governed by the terms of this 168 | License, and how they can obtain a copy of this License. You may not 169 | attempt to alter or restrict the recipients' rights in the Source Code 170 | Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | (a) such Covered Software must also be made available in Source Code 177 | Form, as described in Section 3.1, and You must inform recipients of 178 | the Executable Form how they can obtain a copy of such Source Code 179 | Form by reasonable means in a timely manner, at a charge no more 180 | than the cost of distribution to the recipient; and 181 | 182 | (b) You may distribute such Executable Form under the terms of this 183 | License, or sublicense it under different terms, provided that the 184 | license for the Executable Form does not attempt to limit or alter 185 | the recipients' rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for 191 | the Covered Software. If the Larger Work is a combination of Covered 192 | Software with a work governed by one or more Secondary Licenses, and the 193 | Covered Software is not Incompatible With Secondary Licenses, this 194 | License permits You to additionally distribute such Covered Software 195 | under the terms of such Secondary License(s), so that the recipient of 196 | the Larger Work may, at their option, further distribute the Covered 197 | Software under the terms of either this License or such Secondary 198 | License(s). 199 | 200 | 3.4. Notices 201 | 202 | You may not remove or alter the substance of any license notices 203 | (including copyright notices, patent notices, disclaimers of warranty, 204 | or limitations of liability) contained within the Source Code Form of 205 | the Covered Software, except that You may alter any license notices to 206 | the extent required to remedy known factual inaccuracies. 207 | 208 | 3.5. Application of Additional Terms 209 | 210 | You may choose to offer, and to charge a fee for, warranty, support, 211 | indemnity or liability obligations to one or more recipients of Covered 212 | Software. However, You may do so only on Your own behalf, and not on 213 | behalf of any Contributor. You must make it absolutely clear that any 214 | such warranty, support, indemnity, or liability obligation is offered by 215 | You alone, and You hereby agree to indemnify every Contributor for any 216 | liability incurred by such Contributor as a result of warranty, support, 217 | indemnity or liability terms You offer. You may include additional 218 | disclaimers of warranty and limitations of liability specific to any 219 | jurisdiction. 220 | 221 | 4. Inability to Comply Due to Statute or Regulation 222 | --------------------------------------------------- 223 | 224 | If it is impossible for You to comply with any of the terms of this 225 | License with respect to some or all of the Covered Software due to 226 | statute, judicial order, or regulation then You must: (a) comply with 227 | the terms of this License to the maximum extent possible; and (b) 228 | describe the limitations and the code they affect. Such description must 229 | be placed in a text file included with all distributions of the Covered 230 | Software under this License. Except to the extent prohibited by statute 231 | or regulation, such description must be sufficiently detailed for a 232 | recipient of ordinary skill to be able to understand it. 233 | 234 | 5. Termination 235 | -------------- 236 | 237 | 5.1. The rights granted under this License will terminate automatically 238 | if You fail to comply with any of its terms. However, if You become 239 | compliant, then the rights granted under this License from a particular 240 | Contributor are reinstated (a) provisionally, unless and until such 241 | Contributor explicitly and finally terminates Your grants, and (b) on an 242 | ongoing basis, if such Contributor fails to notify You of the 243 | non-compliance by some reasonable means prior to 60 days after You have 244 | come back into compliance. Moreover, Your grants from a particular 245 | Contributor are reinstated on an ongoing basis if such Contributor 246 | notifies You of the non-compliance by some reasonable means, this is the 247 | first time You have received notice of non-compliance with this License 248 | from such Contributor, and You become compliant prior to 30 days after 249 | Your receipt of the notice. 250 | 251 | 5.2. If You initiate litigation against any entity by asserting a patent 252 | infringement claim (excluding declaratory judgment actions, 253 | counter-claims, and cross-claims) alleging that a Contributor Version 254 | directly or indirectly infringes any patent, then the rights granted to 255 | You by any and all Contributors for the Covered Software under Section 256 | 2.1 of this License shall terminate. 257 | 258 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 259 | end user license agreements (excluding distributors and resellers) which 260 | have been validly granted by You or Your distributors under this License 261 | prior to termination shall survive termination. 262 | 263 | ************************************************************************ 264 | * * 265 | * 6. Disclaimer of Warranty * 266 | * ------------------------- * 267 | * * 268 | * Covered Software is provided under this License on an "as is" * 269 | * basis, without warranty of any kind, either expressed, implied, or * 270 | * statutory, including, without limitation, warranties that the * 271 | * Covered Software is free of defects, merchantable, fit for a * 272 | * particular purpose or non-infringing. The entire risk as to the * 273 | * quality and performance of the Covered Software is with You. * 274 | * Should any Covered Software prove defective in any respect, You * 275 | * (not any Contributor) assume the cost of any necessary servicing, * 276 | * repair, or correction. This disclaimer of warranty constitutes an * 277 | * essential part of this License. No use of any Covered Software is * 278 | * authorized under this License except under this disclaimer. * 279 | * * 280 | ************************************************************************ 281 | 282 | ************************************************************************ 283 | * * 284 | * 7. Limitation of Liability * 285 | * -------------------------- * 286 | * * 287 | * Under no circumstances and under no legal theory, whether tort * 288 | * (including negligence), contract, or otherwise, shall any * 289 | * Contributor, or anyone who distributes Covered Software as * 290 | * permitted above, be liable to You for any direct, indirect, * 291 | * special, incidental, or consequential damages of any character * 292 | * including, without limitation, damages for lost profits, loss of * 293 | * goodwill, work stoppage, computer failure or malfunction, or any * 294 | * and all other commercial damages or losses, even if such party * 295 | * shall have been informed of the possibility of such damages. This * 296 | * limitation of liability shall not apply to liability for death or * 297 | * personal injury resulting from such party's negligence to the * 298 | * extent applicable law prohibits such limitation. Some * 299 | * jurisdictions do not allow the exclusion or limitation of * 300 | * incidental or consequential damages, so this exclusion and * 301 | * limitation may not apply to You. * 302 | * * 303 | ************************************************************************ 304 | 305 | 8. Litigation 306 | ------------- 307 | 308 | Any litigation relating to this License may be brought only in the 309 | courts of a jurisdiction where the defendant maintains its principal 310 | place of business and such litigation shall be governed by laws of that 311 | jurisdiction, without reference to its conflict-of-law provisions. 312 | Nothing in this Section shall prevent a party's ability to bring 313 | cross-claims or counter-claims. 314 | 315 | 9. Miscellaneous 316 | ---------------- 317 | 318 | This License represents the complete agreement concerning the subject 319 | matter hereof. If any provision of this License is held to be 320 | unenforceable, such provision shall be reformed only to the extent 321 | necessary to make it enforceable. Any law or regulation which provides 322 | that the language of a contract shall be construed against the drafter 323 | shall not be used to construe this License against a Contributor. 324 | 325 | 10. Versions of the License 326 | --------------------------- 327 | 328 | 10.1. New Versions 329 | 330 | Mozilla Foundation is the license steward. Except as provided in Section 331 | 10.3, no one other than the license steward has the right to modify or 332 | publish new versions of this License. Each version will be given a 333 | distinguishing version number. 334 | 335 | 10.2. Effect of New Versions 336 | 337 | You may distribute the Covered Software under the terms of the version 338 | of the License under which You originally received the Covered Software, 339 | or under the terms of any subsequent version published by the license 340 | steward. 341 | 342 | 10.3. Modified Versions 343 | 344 | If you create software not governed by this License, and you want to 345 | create a new license for such software, you may create and use a 346 | modified version of this License if you rename the license and remove 347 | any references to the name of the license steward (except to note that 348 | such modified license differs from this License). 349 | 350 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 351 | Licenses 352 | 353 | If You choose to distribute Source Code Form that is Incompatible With 354 | Secondary Licenses under the terms of this version of the License, the 355 | notice described in Exhibit B of this License must be attached. 356 | 357 | Exhibit A - Source Code Form License Notice 358 | ------------------------------------------- 359 | 360 | This Source Code Form is subject to the terms of the Mozilla Public 361 | License, v. 2.0. If a copy of the MPL was not distributed with this 362 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 363 | 364 | If it is not possible or desirable to put the notice in a particular 365 | file, then You may include the notice in a location (such as a LICENSE 366 | file in a relevant directory) where a recipient would be likely to look 367 | for such a notice. 368 | 369 | You may add additional accurate notices of copyright ownership. 370 | 371 | Exhibit B - "Incompatible With Secondary Licenses" Notice 372 | --------------------------------------------------------- 373 | 374 | This Source Code Form is "Incompatible With Secondary Licenses", as 375 | defined by the Mozilla Public License, v. 2.0. 376 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docker/local 2 | docker/local: 3 | DOCKER_BUILDKIT=1 docker build \ 4 | --ssh default \ 5 | --secret id=ssh.config,src="${HOME}/.ssh/config" \ 6 | --secret id=ssh.key,src="${HOME}/.ssh/config" \ 7 | -t waypoint-hzn:latest \ 8 | . 9 | 10 | .PHONY: docker/evanphx 11 | docker/evanphx: 12 | DOCKER_BUILDKIT=1 docker build -f hack/Dockerfile.evanphx \ 13 | --ssh default \ 14 | -t waypoint-hzn:latest \ 15 | . 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Waypoint Horizon API (waypoint-hzn) 2 | 3 | This repository contains the Waypoint API frontend to the [Horizon](https://github.com/hashicorp/horizon) 4 | service. Horizon is an internal API that isn't fully exposed to the public. 5 | It expects applications to implement application-specific services in front of 6 | it to perform application-specific business logic. This project (waypoint-hzn) 7 | is the Waypoint-specific API frontend for Horizon. 8 | 9 | ## Development 10 | 11 | The project is made to be developed primarily through a unit-test driven 12 | workflow via `go test`. This tests fully communicating to an in-memory Horizon 13 | server to verify behaviors. 14 | 15 | Some dependencies must be running for the unit tests. These are all contained 16 | in the Docker Compose configuration. Therefore, to run all tests: 17 | 18 | ``` 19 | $ docker-compose up -d 20 | $ go test ./... -p 1 21 | ``` 22 | 23 | To build the project, you can use `go build ./cmd/waypoint-hzn` directly or 24 | build the included Docker image with `make docker/local`. 25 | -------------------------------------------------------------------------------- /cmd/waypoint-hzn/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "flag" 7 | "log" 8 | "net" 9 | "net/url" 10 | "path/filepath" 11 | 12 | "github.com/golang-migrate/migrate/v4" 13 | _ "github.com/golang-migrate/migrate/v4/database/postgres" 14 | _ "github.com/golang-migrate/migrate/v4/source/file" 15 | "github.com/hashicorp/go-hclog" 16 | "github.com/hashicorp/horizon/pkg/grpc/lz4" 17 | grpctoken "github.com/hashicorp/horizon/pkg/grpc/token" 18 | hznpb "github.com/hashicorp/horizon/pkg/pb" 19 | "github.com/jinzhu/gorm" 20 | "github.com/sethvargo/go-envconfig" 21 | "google.golang.org/grpc" 22 | "google.golang.org/grpc/credentials" 23 | 24 | "github.com/hashicorp/waypoint-hzn/pkg/server" 25 | ) 26 | 27 | var ( 28 | flagDev = flag.Bool("dev", false, "dev mode to run locally") 29 | ) 30 | 31 | func main() { 32 | flag.Parse() 33 | 34 | L := hclog.New(&hclog.LoggerOptions{ 35 | Name: "waypoint-hzn", 36 | Level: hclog.Trace, 37 | }) 38 | ctx := context.Background() 39 | 40 | L.Info("reading configuration from env vars") 41 | var cfg config 42 | if err := envconfig.Process(ctx, &cfg); err != nil { 43 | log.Fatalf("error reading configuration: %s", err) 44 | return 45 | } 46 | 47 | // Apply migrations 48 | if cfg.MigrationsApply || *flagDev { 49 | if !filepath.IsAbs(cfg.MigrationsPath) { 50 | path, err := filepath.Abs(cfg.MigrationsPath) 51 | if err != nil { 52 | log.Fatalf("error determining migration path: %s", err) 53 | } 54 | 55 | cfg.MigrationsPath = path 56 | } 57 | 58 | L.Info("applying migrations", "path", cfg.MigrationsPath) 59 | m, err := migrate.New("file://"+cfg.MigrationsPath, cfg.DatabaseUrl) 60 | if err != nil { 61 | log.Fatalf("error creating migrater: %s", err) 62 | } 63 | 64 | err = m.Up() 65 | if err != nil { 66 | if err != migrate.ErrNoChange { 67 | log.Fatalf("error running migrations: %s", err) 68 | } 69 | } 70 | 71 | m.Close() 72 | } 73 | 74 | L.Info("connecting to database") 75 | u, err := url.Parse(cfg.DatabaseUrl) 76 | if err != nil { 77 | log.Fatal(err) 78 | return 79 | } 80 | db, err := gorm.Open("postgres", migrate.FilterCustomQuery(u).String()) 81 | if err != nil { 82 | log.Fatal(err) 83 | return 84 | } 85 | 86 | opts := []grpc.DialOption{ 87 | grpc.WithPerRPCCredentials(grpctoken.Token(cfg.ControlToken)), 88 | grpc.WithDefaultCallOptions(grpc.UseCompressor(lz4.Name)), 89 | } 90 | 91 | if cfg.ControlInsecure { 92 | opts = append(opts, grpc.WithInsecure()) 93 | } else { 94 | creds := credentials.NewTLS(&tls.Config{}) 95 | opts = append(opts, grpc.WithTransportCredentials(creds)) 96 | } 97 | 98 | L.Info("dialing horizon control plane", "addr", cfg.ControlAddr) 99 | gcc, err := grpc.Dial(cfg.ControlAddr, opts...) 100 | if err != nil { 101 | log.Fatal(err) 102 | return 103 | } 104 | 105 | ln, err := net.Listen("tcp", cfg.ListenAddr) 106 | if err != nil { 107 | log.Fatal(err) 108 | return 109 | } 110 | defer ln.Close() 111 | 112 | L.Info("starting server") 113 | err = server.Run( 114 | server.WithContext(ctx), 115 | server.WithLogger(L), 116 | server.WithGRPC(ln), 117 | server.WithDB(db), 118 | server.WithHznControl(hznpb.NewControlManagementClient(gcc)), 119 | server.WithDomain(cfg.Domain), 120 | ) 121 | if err != nil { 122 | log.Fatal(err) 123 | } 124 | } 125 | 126 | type config struct { 127 | ListenAddr string `env:"LISTEN_ADDR,default=:24030"` 128 | ControlAddr string `env:"CONTROL_ADDR,default=127.0.0.1:24401"` 129 | ControlInsecure bool `env:"CONTROL_INSECURE,default=1"` 130 | ControlToken string `env:"CONTROL_TOKEN,default=aabbcc"` 131 | DatabaseUrl string `env:"DATABASE_URL"` 132 | Domain string `env:"DOMAIN,default=waypoint.localdomain"` 133 | MigrationsApply bool `env:"MIGRATIONS_APPLY"` 134 | MigrationsPath string `env:"MIGRATIONS_PATH,default=./migrations"` 135 | } 136 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | pebble: 5 | image: letsencrypt/pebble 6 | command: pebble 7 | ports: 8 | - 14000:14000 # ACME port 9 | - 15000:15000 # Management port 10 | environment: 11 | - PEBBLE_VA_NOSLEEP=1 12 | - PEBBLE_VA_ALWAYS_VALID=1 13 | 14 | localstack: 15 | image: localstack/localstack 16 | ports: 17 | - "4566-4599:4566-4599" 18 | - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}" 19 | environment: 20 | - SERVICES=${SERVICES- } 21 | - DEBUG=${DEBUG- } 22 | - DATA_DIR=${DATA_DIR- } 23 | - PORT_WEB_UI=${PORT_WEB_UI- } 24 | - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- } 25 | - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- } 26 | - LAMBDA_EXECUTOR=local 27 | - HOST_TMP_FOLDER=${TMPDIR} 28 | volumes: 29 | - "${PWD}/tmp/localstack:/tmp/localstack" 30 | 31 | postgres: 32 | image: "postgres:12.3" 33 | ports: 34 | - "5432:5432" 35 | environment: 36 | - POSTGRES_DB=noop 37 | - POSTGRES_PASSWORD=postgres 38 | 39 | vault: 40 | image: vault 41 | ports: 42 | - 8200:8200 43 | command: vault server -dev -dev-root-token-id=hznroot 44 | environment: 45 | - VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/waypoint-hzn 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/envoyproxy/protoc-gen-validate v0.1.0 7 | github.com/golang-migrate/migrate/v4 v4.10.0 8 | github.com/golang/protobuf v1.4.2 9 | github.com/hashicorp/go-hclog v0.14.1 10 | github.com/hashicorp/go-multierror v1.1.0 11 | github.com/hashicorp/horizon v0.0.0-20200717001716-a175a185844b 12 | github.com/jinzhu/gorm v1.9.12 13 | github.com/lib/pq v1.7.0 14 | github.com/mitchellh/go-testing-interface v1.14.0 15 | github.com/mitchellh/mapstructure v1.1.2 16 | github.com/oklog/run v1.1.0 17 | github.com/sethvargo/go-envconfig v0.2.0 18 | github.com/stretchr/testify v1.5.1 19 | google.golang.org/grpc v1.30.0 20 | google.golang.org/protobuf v1.25.0 21 | ) 22 | 23 | require ( 24 | cirello.io/dynamolock v1.3.3 // indirect 25 | github.com/DataDog/datadog-go v3.2.0+incompatible // indirect 26 | github.com/armon/go-metrics v0.3.3 // indirect 27 | github.com/aws/aws-sdk-go v1.33.6 // indirect 28 | github.com/beorn7/perks v1.0.1 // indirect 29 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 30 | github.com/davecgh/go-spew v1.1.1 // indirect 31 | github.com/fatih/color v1.9.0 // indirect 32 | github.com/gogo/protobuf v1.3.1 // indirect 33 | github.com/golang/snappy v0.0.1 // indirect 34 | github.com/hashicorp/errwrap v1.0.0 // indirect 35 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 36 | github.com/hashicorp/go-immutable-radix v1.2.0 // indirect 37 | github.com/hashicorp/go-retryablehttp v0.5.4 // indirect 38 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 39 | github.com/hashicorp/go-sockaddr v1.0.2 // indirect 40 | github.com/hashicorp/golang-lru v0.5.3 // indirect 41 | github.com/hashicorp/hcl v1.0.0 // indirect 42 | github.com/hashicorp/vault/api v1.0.5-0.20190909201928-35325e2c3262 // indirect 43 | github.com/hashicorp/vault/sdk v0.1.14-0.20190909201848-e0fbf9b652e2 // indirect 44 | github.com/jinzhu/inflection v1.0.0 // indirect 45 | github.com/jmespath/go-jmespath v0.3.0 // indirect 46 | github.com/klauspost/compress v1.10.10 // indirect 47 | github.com/kr/text v0.2.0 // indirect 48 | github.com/mattn/go-colorable v0.1.7 // indirect 49 | github.com/mattn/go-isatty v0.0.12 // indirect 50 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 51 | github.com/mitchellh/go-homedir v1.1.0 // indirect 52 | github.com/mr-tron/base58 v1.2.0 // indirect 53 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 54 | github.com/oklog/ulid v1.3.1 // indirect 55 | github.com/oschwald/geoip2-golang v1.4.0 // indirect 56 | github.com/oschwald/maxminddb-golang v1.6.0 // indirect 57 | github.com/pierrec/lz4 v2.2.6+incompatible // indirect 58 | github.com/pierrec/lz4/v3 v3.3.2 // indirect 59 | github.com/pkg/errors v0.9.1 // indirect 60 | github.com/pmezard/go-difflib v1.0.0 // indirect 61 | github.com/prometheus/client_golang v1.4.0 // indirect 62 | github.com/prometheus/client_model v0.2.0 // indirect 63 | github.com/prometheus/common v0.9.1 // indirect 64 | github.com/prometheus/procfs v0.0.8 // indirect 65 | github.com/ryanuber/go-glob v1.0.0 // indirect 66 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 // indirect 67 | golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect 68 | golang.org/x/sys v0.7.0 // indirect 69 | golang.org/x/text v0.3.3 // indirect 70 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect 71 | google.golang.org/genproto v0.0.0-20200715011427-11fb19a81f2c // indirect 72 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 73 | gopkg.in/square/go-jose.v2 v2.4.1 // indirect 74 | gopkg.in/yaml.v2 v2.2.8 // indirect 75 | gortc.io/stun v1.22.2 // indirect 76 | ) 77 | -------------------------------------------------------------------------------- /hack/Dockerfile.evanphx: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:experimental 2 | 3 | FROM golang:alpine AS builder 4 | 5 | RUN apk add --no-cache git gcc libc-dev openssh 6 | 7 | RUN mkdir -p /tmp/prime 8 | COPY go.sum /tmp/prime 9 | COPY go.mod /tmp/prime 10 | 11 | WORKDIR /tmp/prime 12 | 13 | RUN mkdir -p -m 0600 ~/.ssh \ 14 | && ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts 15 | RUN git config --global url.ssh://git@github.com/.insteadOf https://github.com/ 16 | RUN --mount=type=ssh GOPRIVATE=github.com/hashicorp go mod download 17 | 18 | COPY . /tmp/src 19 | WORKDIR /tmp/src 20 | 21 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=ssh go build -o /tmp/waypoint-hzn -ldflags "-X main.sha1ver=`git rev-parse HEAD` -X main.buildTime=$(date +'+%FT%T.%N%:z')" ./cmd/waypoint-hzn 22 | 23 | FROM alpine 24 | 25 | COPY --from=builder /tmp/waypoint-hzn /usr/bin/waypoint-hzn 26 | 27 | RUN GRPC_HEALTH_PROBE_VERSION=v0.3.2 && \ 28 | wget -qO/usr/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 29 | chmod +x /usr/bin/grpc_health_probe 30 | 31 | COPY ./migrations /migrations 32 | 33 | ENTRYPOINT ["/usr/bin/waypoint-hzn"] 34 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/README.md: -------------------------------------------------------------------------------- 1 | # petname 2 | 3 | ## Name 4 | 5 | **petname** − an [RFC1178](https://tools.ietf.org/html/rfc1178) implementation to generate pronounceable, sometimes even memorable, "pet names", consisting of a random combination of adverbs, an adjective, and an animal name 6 | 7 | ## Synopsis 8 | 9 | - Complete version: 10 | ``` 11 | usage: petname [-w|--words INT] [-l|--letters INT] [-s|--separator STR] [-d|--dir STR] [-c|--complexity INT] [-u|--ubuntu] 12 | ``` 13 | 14 | - Python version: 15 | ```bash 16 | usage: petname [-h] [-w WORDS] [-l LETTERS] [-s SEPARATOR] 17 | ``` 18 | 19 | ## Options 20 | - `-w|--words` number of words in the name, default is 2, 21 | - `-l|--letters` maximum number of letters in each word, default is unlimited, 22 | - `-s|--separator` string used to separate name words, default is `'-'`, 23 | - `-d|--dir` directory containing `adverbs.txt`, `adjectives.txt`, `names.txt`, default is `/usr/share/petname/`, 24 | - `-c|--complexity` [0, 1, 2]; 0 = easy words, 1 = standard words, 2 = complex words, default=1, 25 | - `-u|--ubuntu` generate ubuntu-style names, alliteration of first character of each word. 26 | 27 | ## Description 28 | 29 | This utility will generate "pet names", consisting of a random combination of an adverb, adjective, and an animal name. These are useful for unique hostnames or container names, for instance. 30 | 31 | As such, PetName tries to follow the tenets of Zooko’s triangle. Names are: 32 | 33 | - human meaningful 34 | - decentralized 35 | - secure 36 | 37 | Besides this shell utility, there are also native libraries: [python-petname](https://pypi.org/project/petname/), [python3-petname](https://pypi.org/project/petname/), and [golang-petname](https://github.com/dustinkirkland/golang-petname). Here are some programmatic examples in code: 38 | 39 | ## Examples 40 | 41 | ```bash 42 | $ petname 43 | wiggly-yellowtail 44 | 45 | $ petname --words 1 46 | robin 47 | 48 | $ petname --words 3 49 | primly-lasting-toucan 50 | 51 | $ petname --words 4 52 | angrily-impatiently-sage-longhorn 53 | 54 | $ petname --separator ":" 55 | cool:gobbler 56 | 57 | $ petname --separator "" --words 3 58 | comparablyheartylionfish 59 | 60 | $ petname --ubuntu 61 | amazed-asp 62 | 63 | $ petname --complexity 0 64 | massive-colt 65 | ``` 66 | 67 | ---- 68 | 69 | ## Code 70 | 71 | Besides this shell utility, there are also native libraries: python-petname, python3-petname, and golang-petname. Here are some programmatic examples in code: 72 | 73 | ### **Golang Example** 74 | Install it with apt: 75 | ```bash 76 | $ sudo apt-get install golang-petname 77 | ``` 78 | 79 | Or here's an example in golang code: 80 | 81 | ```golang 82 | package main 83 | 84 | import ( 85 | "flag" 86 | "fmt" 87 | "math/rand" 88 | "time" 89 | "github.com/dustinkirkland/golang-petname" 90 | ) 91 | 92 | var ( 93 | words = flag.Int("words", 2, "The number of words in the pet name") 94 | separator = flag.String("separator", "-", "The separator between words in the pet name") 95 | ) 96 | 97 | func init() { 98 | rand.Seed(time.Now().UTC().UnixNano()) 99 | } 100 | 101 | func main() { 102 | flag.Parse() 103 | rand.Seed(time.Now().UnixNano()) 104 | fmt.Println(petname.Generate(*words, *separator)) 105 | } 106 | ``` 107 | 108 | ### **Python Example** 109 | See: [on pypi](https://pypi.python.org/pypi/petname). 110 | 111 | Install it with [pip](https://pip.pypa.io/): 112 | ```bash 113 | $ [sudo] pip install petname 114 | ``` 115 | 116 | ```python 117 | #!/usr/bin/python 118 | import argparse 119 | import petname 120 | import sys 121 | 122 | parser = argparse.ArgumentParser(description='Generate human readable random names') 123 | parser.add_argument('-w', '--words', help='Number of words in name, default=2', default=2) 124 | parser.add_argument('-l', '--letters', help='Maximum number of letters per word, default=6', default=6) 125 | parser.add_argument('-s', '--separator', help='Separator between words, default="-"', default="-") 126 | parser.options = parser.parse_args() 127 | sys.stdout.write(petname.Generate(int(parser.options.words), parser.options.separator, int(parser.options.letters)) + "\n") 128 | ``` 129 | 130 | ## Author 131 | 132 | This manpage and the utility were written by Dustin Kirkland <dustin.kirkland@gmail.com> for Ubuntu systems (but may be used by others). Permission is granted to copy, distribute and/or modify this document and the utility under the terms of the Apache2 License. 133 | 134 | The complete text of the Apache2 License can be found in `/usr/share/common-licenses/Apache-2.0` on Debian/Ubuntu systems. 135 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/cmd/petname/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | petname: binary for generating human-readable, random names 3 | for objects (e.g. hostnames, containers, blobs) 4 | 5 | Copyright 2014 Dustin Kirkland 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "flag" 24 | "fmt" 25 | "math/rand" 26 | "time" 27 | "github.com/hashicorp/waypoint-hzn/internal/pkg/golang-petname" 28 | ) 29 | 30 | var ( 31 | words = flag.Int("words", 2, "The number of words in the pet name") 32 | separator = flag.String("separator", "-", "The separator between words in the pet name") 33 | ) 34 | 35 | func init() { 36 | rand.Seed(time.Now().UTC().UnixNano()) 37 | } 38 | 39 | func main() { 40 | flag.Parse() 41 | rand.Seed(time.Now().UnixNano()) 42 | fmt.Println(petname.Generate(*words, *separator)) 43 | } 44 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/changelog: -------------------------------------------------------------------------------- 1 | golang-petname (2.11) unreleased; urgency=medium 2 | 3 | [ Romain Verduci ] 4 | * petname.go: 5 | - add non-deterministic mode 6 | 7 | -- Dustin Kirkland Fri, 29 Nov 2019 15:51:43 -0600 8 | 9 | golang-petname (2.10-0ubuntu1) eoan; urgency=medium 10 | 11 | * README.md: 12 | - update to the newer python-petname readme 13 | - refer to https://tools.ietf.org/html/rfc1178 14 | - update golang and python examples 15 | 16 | -- Dustin Kirkland Thu, 13 Jun 2019 15:04:54 -0500 17 | 18 | golang-petname (2.9-0ubuntu1) unstable; urgency=medium 19 | 20 | [ Michael Hudson-Doyle ] 21 | * Stop building shared library package. 22 | - https://github.com/dustinkirkland/golang-petname/pull/6 23 | 24 | -- Dustin Kirkland Thu, 21 Sep 2017 17:06:36 -0500 25 | 26 | golang-petname (2.8-0ubuntu1) artful; urgency=medium 27 | 28 | * petname.go, README.md: 29 | - update wordlists, from petname upstream changes 30 | 31 | -- Dustin Kirkland Thu, 21 Sep 2017 17:06:33 -0500 32 | 33 | golang-petname (2.7-0ubuntu1) artful; urgency=medium 34 | 35 | [ Brian Fallik ] 36 | * README.md: 37 | - https://github.com/dustinkirkland/golang-petname/pull/3 38 | - update README.md to be Markdown compliant #3 39 | * cmd/petname/main.go, petname.go: 40 | - move random seeding to the main function 41 | - callers of our library will need to ensure their PRNG 42 | is properly seeded 43 | 44 | -- Dustin Kirkland Thu, 27 Apr 2017 18:08:47 -0500 45 | 46 | golang-petname (2.6-0ubuntu1) zesty; urgency=medium 47 | 48 | [ Michael Hudson-Doyle ] 49 | * Fix repeated use of shlibs:Depends substvar that lead to broken Depends. 50 | - https://github.com/dustinkirkland/golang-petname/pull/2 51 | 52 | -- Dustin Kirkland Thu, 05 Jan 2017 15:50:06 -0600 53 | 54 | golang-petname (2.5-0ubuntu1) zesty; urgency=medium 55 | 56 | [ Alexander Sack ] 57 | * petname.go: 58 | - initialize with a random seed, 59 | https://github.com/dustinkirkland/golang-petname/pull/1 60 | 61 | -- Dustin Kirkland Mon, 14 Nov 2016 09:34:59 -0600 62 | 63 | golang-petname (2.4-0ubuntu1) yakkety; urgency=medium 64 | 65 | * debian/control, debian/rules: LP: #1625753 66 | - fix uninstallable golang-petname-dev on xenial 67 | 68 | -- Dustin Kirkland Tue, 20 Sep 2016 14:48:06 -0500 69 | 70 | golang-petname (2.3-0ubuntu1) yakkety; urgency=medium 71 | 72 | * debian/rules: 73 | - don't build libgolang-petname1 on xenial 74 | 75 | -- Dustin Kirkland Tue, 23 Aug 2016 09:26:37 -0400 76 | 77 | golang-petname (2.2-0ubuntu1) yakkety; urgency=medium 78 | 79 | * debian/rules, === removed directory usr, === removed directory 80 | usr/lib: 81 | - fix shared library build on yakkety while maintaining xenial 82 | no-shared-lib build 83 | 84 | -- Dustin Kirkland Tue, 09 Aug 2016 18:04:30 -0500 85 | 86 | golang-petname (2.1-0ubuntu1) yakkety; urgency=medium 87 | 88 | [ Michael Hudson-Doyle ] 89 | * debian/control, debian/golang-petname-dev.install: 90 | - Build shared lib package. 91 | 92 | [ Dustin Kirkland ] 93 | * === added directory usr, === added directory usr/lib, 94 | debian/control, debian/golang-petname-dev.install: 95 | - add a couple of hacks, to not break the build on releases before 96 | yakkety 97 | + namely, use a dummy (dh-apparmor) to ensure we can meet a 98 | build-dependency on xenial 99 | + create the usr/lib dir, and always install it 100 | 101 | -- Dustin Kirkland Tue, 09 Aug 2016 16:25:53 -0500 102 | 103 | golang-petname (2.0-0ubuntu1) yakkety; urgency=medium 104 | 105 | [ Dustin Kirkland ] 106 | * petname.go, README.md: 107 | - remove some non-words 108 | * debian/control, debian/golang-petname-dev.install: 109 | - reverting Michael's shared library changes, as it's breaking the build 110 | * petname.go, README.md: 111 | - bump to petname 2.0, using animals instead of people names, major change 112 | - rebuild with simplest, smallest wordlist 113 | 114 | -- Dustin Kirkland Tue, 09 Aug 2016 14:17:20 -0500 115 | 116 | golang-petname (1.10-0ubuntu1) xenial; urgency=medium 117 | 118 | [ Michael Hudson-Doyle ] 119 | * Make packaging more typical for a package that uses dh-golang. 120 | 121 | -- Dustin Kirkland Mon, 01 Feb 2016 11:14:50 -0600 122 | 123 | golang-petname (1.9-0ubuntu1) xenial; urgency=medium 124 | 125 | * debian/golang-petname-dev.install, debian/rules: 126 | - install the source code to the appropriate location per 127 | http://pkg-go.alioth.debian.org/packaging.html#_file_locations 128 | - simplify build rules, remove strip override 129 | 130 | -- Dustin Kirkland Thu, 03 Dec 2015 10:07:22 -0600 131 | 132 | golang-petname (1.8-0ubuntu1) xenial; urgency=medium 133 | 134 | * debian/control, debian/rules, debian/update-wordlists.sh: 135 | - remove build dependency on petname; only the maintainer 136 | ever needs to run update-wordlists 137 | 138 | -- Dustin Kirkland Wed, 02 Dec 2015 17:19:08 -0600 139 | 140 | golang-petname (1.7-0ubuntu1) xenial; urgency=medium 141 | 142 | * debian/control, debian/rules: LP: #1520687 143 | - fixes for MIR of -dev library package 144 | - add a build-depends on dh-golang 145 | - build --with=golang 146 | - note the binary was Built-Using 147 | - add a metapackage that meets the Debian standard naming for go libs 148 | 149 | -- Dustin Kirkland Wed, 02 Dec 2015 16:55:42 -0600 150 | 151 | golang-petname (1.6-0ubuntu1) xenial; urgency=medium 152 | 153 | * No op change, testing gopkg in git 154 | * petname.go: 155 | - update wordlists, pruning blacklisted words 156 | 157 | -- Dustin Kirkland Fri, 30 Oct 2015 10:20:27 -0500 158 | 159 | golang-petname (1.5-0ubuntu1) vivid; urgency=medium 160 | 161 | * debian/update-wordlists.sh, petname.go, README.md: 162 | - rebuild and release with updated wordlists 163 | 164 | -- Dustin Kirkland Wed, 28 Jan 2015 16:10:03 -0600 165 | 166 | golang-petname (1.4-0ubuntu1) vivid; urgency=medium 167 | 168 | * cmd/petname/main.go, petname.go, petname_test.go: 169 | - change petname.PetName() to petname.Generate() 170 | 171 | -- Dustin Kirkland Tue, 13 Jan 2015 11:19:06 -0600 172 | 173 | golang-petname (1.3-0ubuntu1) vivid; urgency=medium 174 | 175 | * petname.go: 176 | - add note that these lists aren't manually updated/modified 177 | - drop the "Rand" prefix, which is implicit 178 | - no need for time module 179 | * cmd/petname/main.go, petname.go: 180 | - move the prng seeding to the end caller of the function 181 | - add our import deps, math/rand, time 182 | 183 | -- Dustin Kirkland Tue, 13 Jan 2015 11:19:03 -0600 184 | 185 | golang-petname (1.2-0ubuntu1) vivid; urgency=medium 186 | 187 | * petname.go, petname_test.go: 188 | - add some inline godoc documentation, fix format 189 | * remove stray file 190 | * debian/control: 191 | - arch: any, let it build where it builds 192 | * debian/copyright: 193 | - update upstream name 194 | * debian/update-wordlists.sh: 195 | - update word list location to share 196 | * petname.go: 197 | - update wordlists 198 | 199 | -- Dustin Kirkland Mon, 12 Jan 2015 09:53:24 -0600 200 | 201 | golang-petname (1.1-0ubuntu1) vivid; urgency=medium 202 | 203 | * debian/update-wordlists.sh, petname.go: 204 | - update wordlists from upstream 205 | 206 | -- Dustin Kirkland Tue, 16 Dec 2014 14:33:23 -0600 207 | 208 | golang-petname (1.0-0ubuntu1) vivid; urgency=medium 209 | 210 | * adverbs.txt, debian/control, debian/petname.install, debian/python- 211 | petname.install, __init__.py, Makefile, names.txt, petname.go.in, 212 | petname/petname.py, petname/petname.py.in, === removed directory 213 | petname, setup.py, update.sh: 214 | - rework to a golang only package, rename accordingly 215 | * debian/golang-petname.install, Makefile: 216 | - update build, install binary, fix name 217 | * debian/golang-petname.install, Makefile: 218 | - clean up, install in the right place 219 | * debian/control, debian/golang-petname.install, Makefile, update.sh 220 | => debian/update-wordlists.sh: 221 | - install binary into /usr/bin/golang-petname 222 | - build depend on petname, update wordlists at build time 223 | * adjectives.txt, adverbs.txt, names.txt: 224 | - drop txt files, we build depend on petname now 225 | * LICENSE: 226 | - add license file 227 | * README.md: 228 | - update readme 229 | * cmd/petname/main.go, debian/copyright, README.md: 230 | - update readme, add local location of license 231 | * debian/golang-petname.install, golang-petname.1: 232 | - add a manpage 233 | * debian/update-wordlists.sh: 234 | - add instruction to sync README.md from upstream 235 | * === renamed symlink src/github.com/dustinkirkland/petname => 236 | src/github.com/dustinkirkland/golang-petname: 237 | - fix symlink 238 | * debian/lintian-overrides: 239 | - ignore no-stripped, statically linked binary 240 | * README.md: 241 | - readme updated 242 | 243 | -- Dustin Kirkland Tue, 16 Dec 2014 14:07:44 -0600 244 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/control: -------------------------------------------------------------------------------- 1 | Source: golang-petname 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Dustin Kirkland 5 | Build-Depends: debhelper (>= 9), dh-exec, dh-golang, golang-go 6 | Standards-Version: 3.9.5 7 | Homepage: http://launchpad.net/petname 8 | Vcs-Browser: http://bazaar.launchpad.net/~petname/petname/trunk/files 9 | Vcs-Bzr: https://code.launchpad.net/petname/petname/trunk 10 | 11 | Package: golang-petname 12 | Architecture: any 13 | Depends: ${misc:Depends} 14 | Built-Using: ${misc:Built-Using} 15 | Description: generate pronouncable, perhaps even memorable, pet names 16 | This utility will generate "pet names", consisting of a random 17 | combination of an adverb, adjective, and proper name. These are 18 | useful for unique hostnames, for instance. 19 | The default packaging contains about 2000 names, 1300 adjectives, 20 | and 4000 adverbs, yielding nearly 10 billion unique combinations, 21 | covering over 32 bits of unique namespace. 22 | As such, PetName tries to follow the tenets of Zooko's triangle: 23 | names are human meaningful, decentralized, and secure. 24 | 25 | Package: golang-petname-dev 26 | Architecture: all 27 | Depends: ${misc:Depends} 28 | Description: golang library for generating pronouncable, memorable, pet names 29 | This package provides a library for generating "pet names", consisting 30 | of a random combination of an adverb, adjective, and proper name. 31 | These are useful for unique hostnames, for instance. 32 | The default packaging contains about 2000 names, 1300 adjectives, 33 | and 4000 adverbs, yielding nearly 10 billion unique combinations, 34 | covering over 32 bits of unique namespace. 35 | As such, PetName tries to follow the tenets of Zooko's triangle: 36 | names are human meaningful, decentralized, and secure. 37 | 38 | Package: golang-github-dustinkirkland-golang-petname-dev 39 | Architecture: all 40 | Depends: ${misc:Depends}, golang-petname-dev 41 | Description: golang library for generating pronouncable, memorable, pet names 42 | This is a metapackage for golang-petname-dev, which adheres to the 43 | naming convention. 44 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: golang-petname 3 | Upstream-Contact: Dustin Kirkland 4 | Source: http://launchpad.net/golang-petname 5 | 6 | Files: * 7 | Copyright: 2014, Dustin Kirkland 8 | License: Apache-2 9 | Licensed under the Apache License, Version 2.0 (the "License"); 10 | you may not use this file except in compliance with the License. 11 | You may obtain a copy of the License at 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | On Debian systems, the complete text of the Apache version 2.0 license 19 | can be found in "/usr/share/common-licenses/Apache-2.0". 20 | 21 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/golang-petname-dev.install: -------------------------------------------------------------------------------- 1 | petname.go /usr/share/gocode/src/github.com/dustinkirkland/golang-petname 2 | petname_test.go /usr/share/gocode/src/github.com/dustinkirkland/golang-petname 3 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/golang-petname.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | usr/bin/petname => /usr/bin/golang-petname 3 | golang-petname.1 => /usr/share/man/man1/golang-petname.1 4 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/lintian-overrides: -------------------------------------------------------------------------------- 1 | golang-petname: unstripped-binary-or-object 2 | golang-petname: statically-linked-binary 3 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | export DH_GOPKG := github.com/dustinkirkland/golang-petname 4 | 5 | %: 6 | dh $@ --buildsystem=golang --with=golang 7 | 8 | override_dh_install: 9 | mkdir -p ${PWD}/debian/tmp/usr/lib 10 | dh_install 11 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/debian/update-wordlists.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script only needs to be run by the upstream package maintainer (Dustin Kirkland) 4 | # if the upstream petname wordlists change 5 | 6 | set -e 7 | 8 | PKG="petname" 9 | 10 | [ -d .bzr/ ] && bzr revert ${PKG}.go || true 11 | for f in adverbs adjectives names; do 12 | rm -f "$f".txt.list 13 | printf " $f = [...]string{" > "$f".txt.list 14 | for w in $(cat /usr/share/petname/"$f".txt); do 15 | printf '"%s", ' "$w" >> "$f".txt.list 16 | done 17 | sed -i -e "s/, $/}\n/" "$f".txt.list 18 | sed -i "/^\s\+${f}\s\+= \[\.\.\.\]string{.*$/d" ${PKG}.go 19 | done 20 | printf "\n)\n\n" >> "$f".txt.list 21 | grep -B 1000 "^var (" ${PKG}.go > above 22 | grep -A 1000 "^// Adverb returns" ${PKG}.go > below 23 | cat above *.txt.list below > ${PKG}.go 24 | go fmt ${PKG}.go 25 | rm -f *.txt.list above below 26 | cat /usr/share/doc/petname/README.md > README.md 27 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/golang-petname.1: -------------------------------------------------------------------------------- 1 | .TH golang-petname 1 "15 December 2014" golang-petname "golang-petname" 2 | .SH NAME 3 | golang-petname \- utility to generate "pet names", consisting of a random combination of adverbs, an adjective, and a proper name 4 | 5 | .SH SYNOPSIS 6 | \fBgolang-petname\fP [-w|--words INT] [-s|--separator STR] 7 | 8 | .SH OPTIONS 9 | 10 | --words number of words in the name, default is 2 11 | --separator string used to separate name words, default is '-' 12 | 13 | .SH DESCRIPTION 14 | 15 | This utility will generate "pet names", consisting of a random combination of an adverb, adjective, and proper name. These are useful for unique hostnames, for instance. 16 | 17 | The default packaging contains about 2000 names, 1300 adjectives, and 4000 adverbs, yielding nearly 10 billion unique combinations, covering over 32 bits of unique namespace. 18 | 19 | As such, PetName tries to follow the tenets of Zooko's triangle. Names are: 20 | 21 | - human meaningful 22 | - decentralized 23 | - secure 24 | 25 | .SH EXAMPLES 26 | 27 | $ golang-petname 28 | wiggly-Anna 29 | 30 | $ golang-petname --words 1 31 | Marco 32 | 33 | $ golang-petname --words 3 34 | quickly-scornful-Johnathan 35 | 36 | $ golang-petname --words 4 37 | dolorously-leisurely-wee-Susan 38 | 39 | $ golang-petname --separator ":" 40 | hospitable:Isla 41 | 42 | $ golang-petname --separator "" --words 3 43 | adeptlystaticNicole 44 | 45 | .SH SEE ALSO 46 | \fIpetname\fP(1) 47 | 48 | .SH AUTHOR 49 | This manpage and the utility were written by Dustin Kirkland for Ubuntu systems (but may be used by others). Permission is granted to copy, distribute and/or modify this document and the utility under the terms of the Apache2 License. 50 | 51 | The complete text of the Apache2 License can be found in \fI/usr/share/common-licenses/Apache-2.0\fP on Debian/Ubuntu systems. 52 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/petname.go: -------------------------------------------------------------------------------- 1 | /* 2 | petname: library for generating human-readable, random names 3 | for objects (e.g. hostnames, containers, blobs) 4 | 5 | Copyright 2014 Dustin Kirkland 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Package petname is a library for generating human-readable, random 21 | // names for objects (e.g. hostnames, containers, blobs). 22 | package petname 23 | 24 | import ( 25 | "math/rand" 26 | "strings" 27 | "time" 28 | ) 29 | 30 | // These lists are autogenerated from the master lists in the project: 31 | // - https://github.com/dustinkirkland/petname 32 | // These lists only get modified after updating that branch, and then 33 | // automatically updated by ./debian/update-wordlists.sh as part of 34 | // my release process 35 | var ( 36 | adjectives = [...]string{"able", "above", "absolute", "accepted", "accurate", "ace", "active", "actual", "adapted", "adapting", "adequate", "adjusted", "advanced", "alert", "alive", "allowed", "allowing", "amazed", "amazing", "ample", "amused", "amusing", "apparent", "apt", "arriving", "artistic", "assured", "assuring", "awaited", "awake", "aware", "balanced", "becoming", "beloved", "better", "big", "blessed", "bold", "boss", "brave", "brief", "bright", "bursting", "busy", "calm", "capable", "capital", "careful", "caring", "casual", "causal", "central", "certain", "champion", "charmed", "charming", "cheerful", "chief", "choice", "civil", "classic", "clean", "clear", "clever", "climbing", "close", "closing", "coherent", "comic", "communal", "complete", "composed", "concise", "concrete", "content", "cool", "correct", "cosmic", "crack", "creative", "credible", "crisp", "crucial", "cuddly", "cunning", "curious", "current", "cute", "daring", "darling", "dashing", "dear", "decent", "deciding", "deep", "definite", "delicate", "desired", "destined", "devoted", "direct", "discrete", "distinct", "diverse", "divine", "dominant", "driven", "driving", "dynamic", "eager", "easy", "electric", "elegant", "emerging", "eminent", "enabled", "enabling", "endless", "engaged", "engaging", "enhanced", "enjoyed", "enormous", "enough", "epic", "equal", "equipped", "eternal", "ethical", "evident", "evolved", "evolving", "exact", "excited", "exciting", "exotic", "expert", "factual", "fair", "faithful", "famous", "fancy", "fast", "feasible", "fine", "finer", "firm", "first", "fit", "fitting", "fleet", "flexible", "flowing", "fluent", "flying", "fond", "frank", "free", "fresh", "full", "fun", "funky", "funny", "game", "generous", "gentle", "genuine", "giving", "glad", "glorious", "glowing", "golden", "good", "gorgeous", "grand", "grateful", "great", "growing", "grown", "guided", "guiding", "handy", "happy", "hardy", "harmless", "healthy", "helped", "helpful", "helping", "heroic", "hip", "holy", "honest", "hopeful", "hot", "huge", "humane", "humble", "humorous", "ideal", "immense", "immortal", "immune", "improved", "in", "included", "infinite", "informed", "innocent", "inspired", "integral", "intense", "intent", "internal", "intimate", "inviting", "joint", "just", "keen", "key", "kind", "knowing", "known", "large", "lasting", "leading", "learning", "legal", "legible", "lenient", "liberal", "light", "liked", "literate", "live", "living", "logical", "loved", "loving", "loyal", "lucky", "magical", "magnetic", "main", "major", "many", "massive", "master", "mature", "maximum", "measured", "meet", "merry", "mighty", "mint", "model", "modern", "modest", "moral", "more", "moved", "moving", "musical", "mutual", "national", "native", "natural", "nearby", "neat", "needed", "neutral", "new", "next", "nice", "noble", "normal", "notable", "noted", "novel", "obliging", "on", "one", "open", "optimal", "optimum", "organic", "oriented", "outgoing", "patient", "peaceful", "perfect", "pet", "picked", "pleasant", "pleased", "pleasing", "poetic", "polished", "polite", "popular", "positive", "possible", "powerful", "precious", "precise", "premium", "prepared", "present", "pretty", "primary", "prime", "pro", "probable", "profound", "promoted", "prompt", "proper", "proud", "proven", "pumped", "pure", "quality", "quick", "quiet", "rapid", "rare", "rational", "ready", "real", "refined", "regular", "related", "relative", "relaxed", "relaxing", "relevant", "relieved", "renewed", "renewing", "resolved", "rested", "rich", "right", "robust", "romantic", "ruling", "sacred", "safe", "saved", "saving", "secure", "select", "selected", "sensible", "set", "settled", "settling", "sharing", "sharp", "shining", "simple", "sincere", "singular", "skilled", "smart", "smashing", "smiling", "smooth", "social", "solid", "sought", "sound", "special", "splendid", "square", "stable", "star", "steady", "sterling", "still", "stirred", "stirring", "striking", "strong", "stunning", "subtle", "suitable", "suited", "summary", "sunny", "super", "superb", "supreme", "sure", "sweeping", "sweet", "talented", "teaching", "tender", "thankful", "thorough", "tidy", "tight", "together", "tolerant", "top", "topical", "tops", "touched", "touching", "tough", "true", "trusted", "trusting", "trusty", "ultimate", "unbiased", "uncommon", "unified", "unique", "united", "up", "upright", "upward", "usable", "useful", "valid", "valued", "vast", "verified", "viable", "vital", "vocal", "wanted", "warm", "wealthy", "welcome", "welcomed", "well", "whole", "willing", "winning", "wired", "wise", "witty", "wondrous", "workable", "working", "worthy"} 37 | adverbs = [...]string{"abnormally", "absolutely", "accurately", "actively", "actually", "adequately", "admittedly", "adversely", "allegedly", "amazingly", "annually", "apparently", "arguably", "awfully", "badly", "barely", "basically", "blatantly", "blindly", "briefly", "brightly", "broadly", "carefully", "centrally", "certainly", "cheaply", "cleanly", "clearly", "closely", "commonly", "completely", "constantly", "conversely", "correctly", "curiously", "currently", "daily", "deadly", "deeply", "definitely", "directly", "distinctly", "duly", "eagerly", "early", "easily", "eminently", "endlessly", "enormously", "entirely", "equally", "especially", "evenly", "evidently", "exactly", "explicitly", "externally", "extremely", "factually", "fairly", "finally", "firmly", "firstly", "forcibly", "formally", "formerly", "frankly", "freely", "frequently", "friendly", "fully", "generally", "gently", "genuinely", "ghastly", "gladly", "globally", "gradually", "gratefully", "greatly", "grossly", "happily", "hardly", "heartily", "heavily", "hideously", "highly", "honestly", "hopefully", "hopelessly", "horribly", "hugely", "humbly", "ideally", "illegally", "immensely", "implicitly", "incredibly", "indirectly", "infinitely", "informally", "inherently", "initially", "instantly", "intensely", "internally", "jointly", "jolly", "kindly", "largely", "lately", "legally", "lightly", "likely", "literally", "lively", "locally", "logically", "loosely", "loudly", "lovely", "luckily", "mainly", "manually", "marginally", "mentally", "merely", "mildly", "miserably", "mistakenly", "moderately", "monthly", "morally", "mostly", "multiply", "mutually", "namely", "nationally", "naturally", "nearly", "neatly", "needlessly", "newly", "nicely", "nominally", "normally", "notably", "noticeably", "obviously", "oddly", "officially", "only", "openly", "optionally", "overly", "painfully", "partially", "partly", "perfectly", "personally", "physically", "plainly", "pleasantly", "poorly", "positively", "possibly", "precisely", "preferably", "presently", "presumably", "previously", "primarily", "privately", "probably", "promptly", "properly", "publicly", "purely", "quickly", "quietly", "radically", "randomly", "rapidly", "rarely", "rationally", "readily", "really", "reasonably", "recently", "regularly", "reliably", "remarkably", "remotely", "repeatedly", "rightly", "roughly", "routinely", "sadly", "safely", "scarcely", "secondly", "secretly", "seemingly", "sensibly", "separately", "seriously", "severely", "sharply", "shortly", "similarly", "simply", "sincerely", "singularly", "slightly", "slowly", "smoothly", "socially", "solely", "specially", "steadily", "strangely", "strictly", "strongly", "subtly", "suddenly", "suitably", "supposedly", "surely", "terminally", "terribly", "thankfully", "thoroughly", "tightly", "totally", "trivially", "truly", "typically", "ultimately", "unduly", "uniformly", "uniquely", "unlikely", "urgently", "usefully", "usually", "utterly", "vaguely", "vastly", "verbally", "vertically", "vigorously", "violently", "virtually", "visually", "weekly", "wholly", "widely", "wildly", "willingly", "wrongly", "yearly"} 38 | names = [...]string{"ox", "ant", "ape", "asp", "bat", "bee", "boa", "bug", "cat", "cod", "cow", "cub", "doe", "dog", "eel", "eft", "elf", "elk", "emu", "ewe", "fly", "fox", "gar", "gnu", "hen", "hog", "imp", "jay", "kid", "kit", "koi", "lab", "man", "owl", "pig", "pug", "pup", "ram", "rat", "ray", "yak", "bass", "bear", "bird", "boar", "buck", "bull", "calf", "chow", "clam", "colt", "crab", "crow", "dane", "deer", "dodo", "dory", "dove", "drum", "duck", "fawn", "fish", "flea", "foal", "fowl", "frog", "gnat", "goat", "grub", "gull", "hare", "hawk", "ibex", "joey", "kite", "kiwi", "lamb", "lark", "lion", "loon", "lynx", "mako", "mink", "mite", "mole", "moth", "mule", "mutt", "newt", "orca", "oryx", "pika", "pony", "puma", "seal", "shad", "slug", "sole", "stag", "stud", "swan", "tahr", "teal", "tick", "toad", "tuna", "wasp", "wolf", "worm", "wren", "yeti", "adder", "akita", "alien", "aphid", "bison", "boxer", "bream", "bunny", "burro", "camel", "chimp", "civet", "cobra", "coral", "corgi", "crane", "dingo", "drake", "eagle", "egret", "filly", "finch", "gator", "gecko", "ghost", "ghoul", "goose", "guppy", "heron", "hippo", "horse", "hound", "husky", "hyena", "koala", "krill", "leech", "lemur", "liger", "llama", "louse", "macaw", "midge", "molly", "moose", "moray", "mouse", "panda", "perch", "prawn", "quail", "racer", "raven", "rhino", "robin", "satyr", "shark", "sheep", "shrew", "skink", "skunk", "sloth", "snail", "snake", "snipe", "squid", "stork", "swift", "swine", "tapir", "tetra", "tiger", "troll", "trout", "viper", "wahoo", "whale", "zebra", "alpaca", "amoeba", "baboon", "badger", "beagle", "bedbug", "beetle", "bengal", "bobcat", "caiman", "cattle", "cicada", "collie", "condor", "cougar", "coyote", "dassie", "donkey", "dragon", "earwig", "falcon", "feline", "ferret", "gannet", "gibbon", "glider", "goblin", "gopher", "grouse", "guinea", "hermit", "hornet", "iguana", "impala", "insect", "jackal", "jaguar", "jennet", "kitten", "kodiak", "lizard", "locust", "maggot", "magpie", "mammal", "mantis", "marlin", "marmot", "marten", "martin", "mayfly", "minnow", "monkey", "mullet", "muskox", "ocelot", "oriole", "osprey", "oyster", "parrot", "pigeon", "piglet", "poodle", "possum", "python", "quagga", "rabbit", "raptor", "rodent", "roughy", "salmon", "sawfly", "serval", "shiner", "shrimp", "spider", "sponge", "tarpon", "thrush", "tomcat", "toucan", "turkey", "turtle", "urchin", "vervet", "walrus", "weasel", "weevil", "wombat", "anchovy", "anemone", "bluejay", "buffalo", "bulldog", "buzzard", "caribou", "catfish", "chamois", "cheetah", "chicken", "chigger", "columbidae", "cowbird", "crappie", "crawdad", "cricket", "dogfish", "dolphin", "firefly", "garfish", "gazelle", "gelding", "giraffe", "gobbler", "gorilla", "goshawk", "grackle", "griffon", "grizzly", "grouper", "haddock", "hagfish", "halibut", "hamster", "herring", "donkey", "javelin", "jawfish", "jaybird", "katydid", "ladybug", "lamprey", "lemming", "leopard", "lioness", "lobster", "macaque", "mallard", "mammoth", "manatee", "mastiff", "meerkat", "mollusk", "monarch", "mongrel", "monitor", "monster", "mudfish", "muskrat", "mustang", "narwhal", "oarfish", "octopus", "opossum", "ostrich", "panther", "peacock", "pegasus", "pelican", "penguin", "phoenix", "piranha", "polecat", "primate", "quetzal", "raccoon", "rattler", "redbird", "redfish", "reptile", "rooster", "sawfish", "sculpin", "seagull", "skylark", "snapper", "spaniel", "sparrow", "sunbeam", "sunbird", "sunfish", "tadpole", "termite", "terrier", "unicorn", "vulture", "wallaby", "walleye", "warthog", "whippet", "wildcat", "aardvark", "airedale", "albacore", "anteater", "antelope", "arachnid", "barnacle", "basilisk", "blowfish", "bluebird", "bluegill", "bonefish", "bullfrog", "cardinal", "chipmunk", "cockatoo", "crayfish", "dinosaur", "doberman", "duckling", "elephant", "escargot", "flamingo", "flounder", "foxhound", "glowworm", "goldfish", "grubworm", "hedgehog", "honeybee", "hookworm", "humpback", "kangaroo", "killdeer", "kingfish", "labrador", "lacewing", "ladybird", "lionfish", "longhorn", "mackerel", "malamute", "marmoset", "mastodon", "moccasin", "mongoose", "monkfish", "mosquito", "pangolin", "parakeet", "pheasant", "pipefish", "platypus", "polliwog", "porpoise", "reindeer", "ringtail", "sailfish", "scorpion", "seahorse", "seasnail", "sheepdog", "shepherd", "silkworm", "squirrel", "stallion", "starfish", "starling", "stingray", "stinkbug", "sturgeon", "terrapin", "titmouse", "tortoise", "treefrog", "werewolf", "qubit", "shiko"} 39 | ) 40 | 41 | // Call this function once before using any other to get real random results 42 | func NonDeterministicMode() { 43 | rand.Seed(time.Now().UnixNano()) 44 | } 45 | 46 | // Adverb returns a random adverb from a list of petname adverbs. 47 | func Adverb() string { 48 | return adverbs[rand.Intn(len(adverbs))] 49 | } 50 | 51 | // Adjective returns a random adjective from a list of petname adjectives. 52 | func Adjective() string { 53 | return adjectives[rand.Intn(len(adjectives))] 54 | } 55 | 56 | // Name returns a random name from a list of petname names. 57 | func Name() string { 58 | return names[rand.Intn(len(names))] 59 | } 60 | 61 | // Generate generates and returns a random pet name. 62 | // It takes two parameters: the number of words in the name, and a separator token. 63 | // If a single word is requested, simply a Name() is returned. 64 | // If two words are requested, a Adjective() and a Name() are returned. 65 | // If three or more words are requested, a variable number of Adverb() and a Adjective and a Name() is returned. 66 | // The separator can be any charater, string, or the empty string. 67 | func Generate(words int, separator string) string { 68 | if words == 1 { 69 | return Name() 70 | } else if words == 2 { 71 | return Adjective() + separator + Name() 72 | } 73 | var petname []string 74 | for i := 0; i < words-2; i++ { 75 | petname = append(petname, Adverb()) 76 | } 77 | petname = append(petname, Adjective(), Name()) 78 | return strings.Join(petname, separator) 79 | } 80 | -------------------------------------------------------------------------------- /internal/pkg/golang-petname/petname_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | petname: test of library for generating human-readable, random names 3 | for objects (e.g. hostnames, containers, blobs) 4 | 5 | Copyright 2014 Dustin Kirkland 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Package petname is a library for generating human-readable, random 21 | // names for objects (e.g. hostnames, containers, blobs). 22 | package petname 23 | 24 | import ( 25 | "testing" 26 | ) 27 | 28 | // Make sure the generated names exist 29 | func TestPetName(t *testing.T) { 30 | for i:=0; i<10; i++ { 31 | name := Generate(i, "-") 32 | if name == "" { 33 | t.Fatalf("Did not generate a %d-word name, '%s'", i, name) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/testsql/testsql.go: -------------------------------------------------------------------------------- 1 | // Package testsql provides helpers for working with PostgreSQL databases in 2 | // unit tests, from creating per-test DBs to also running the migrations. 3 | // 4 | // This package should be used for all unit tests to safely create isolated 5 | // database environments for your tests. 6 | // 7 | // When this package is inserted, it will introduce a `-gorm-debug` flag to 8 | // the global flags package. This flag will turn on verbose output that logs 9 | // all SQL statements. 10 | package testsql 11 | 12 | import ( 13 | "flag" 14 | "fmt" 15 | "go/build" 16 | "os" 17 | "path/filepath" 18 | "sort" 19 | "strings" 20 | 21 | "github.com/golang-migrate/migrate/v4" 22 | migratePostgres "github.com/golang-migrate/migrate/v4/database/postgres" 23 | _ "github.com/golang-migrate/migrate/v4/source/file" 24 | "github.com/hashicorp/go-multierror" 25 | "github.com/jinzhu/gorm" 26 | _ "github.com/jinzhu/gorm/dialects/postgres" 27 | "github.com/mitchellh/go-testing-interface" 28 | ) 29 | 30 | // These variables control the database created for tests. They should not 31 | // be modified while any active tests are running. These generally don't need 32 | // to be modified at all. 33 | var ( 34 | // DefaultDBName is the default name of the database to create for tests. 35 | DefaultDBName = "waypoint_hzn_test" 36 | 37 | // UserName and UserPassword are the username and password, respectively, 38 | // of the test user to create with access to the test database. 39 | UserName = "waypoint_hzn_testuser" 40 | UserPassword = "4555dcc0519478e4ab83fc8b78285022bdbf82da" 41 | 42 | // MigrationsDir is the directory path that is looked for for migrations. 43 | // If this is relative, then TestDB will walk parent directories until 44 | // this path is found and load migrations from there. 45 | MigrationsDir = filepath.Join("migrations") 46 | 47 | // postgresDBInitialized is used to indicate whether the Postgres database 48 | // was created and migrated at least once. This is used in combination with 49 | // the ReuseDB option so that databases are only reused if they are known to 50 | // exist and migrated before. 51 | // 52 | // This value is stored for all invocations within this process. When 53 | // running tests, Go splits tests into multiple binaries (i.e. one binary 54 | // per package). Therefore, a database is effectively reused only across 55 | // tests from the same package. 56 | postgresDBInitialized = false 57 | ) 58 | 59 | var gormDebug = flag.Bool("waypoint-hzn-gorm-debug", false, "set to true to have Gorm log all generated SQL.") 60 | 61 | // TestDBOptions collects options that customize the test databases. 62 | type TestDBOptions struct { 63 | // SkipMigration allows skipping over the migration of the database. 64 | SkipMigration bool 65 | 66 | // DBReuse indicates whether the potentially existing test database can be 67 | // reused. If set, the database is created and migrated at least once, but 68 | // won't be destroyed and recreated every time one of the `TestDB` 69 | // functions is called. 70 | ReuseDB bool 71 | } 72 | 73 | type nopLogger struct{} 74 | 75 | func (_ nopLogger) Print(v ...interface{}) {} 76 | 77 | // TestPostgresDB sets up the test DB to use, including running any migrations. 78 | // In case the ReuseDB option is set to true, this function might not create a 79 | // new database. 80 | // 81 | // This expects a local Postgres to be running with default "postgres/postgres" 82 | // superuser credentials. 83 | // 84 | // This also expects that at this or some parent directory, there is the 85 | // path represented by MigrationsDir where the migration files can be found. 86 | // If no migrations are found, then an error is raised. 87 | func TestPostgresDBWithOpts(t testing.T, dbName string, opts *TestDBOptions) *gorm.DB { 88 | t.Helper() 89 | 90 | // Services setting up their tests with this helper are expected to provide 91 | // a database name. If they don't, use a default. 92 | if dbName == "" { 93 | dbName = DefaultDBName 94 | } 95 | 96 | // Create the DB. We first drop the existing DB. The complex SQL 97 | // statement below evicts any connections to that database so we can 98 | // drop it. 99 | db := testDBConnectWithUser(t, "postgres", "", "postgres", "postgres") 100 | if *gormDebug { 101 | db.LogMode(true) 102 | } 103 | 104 | // If the database shouldn't be reused or if it wasn't yet initialized, drop 105 | // a potentially existing database, create a new one, and migrate it to the 106 | // latest version. 107 | if !opts.ReuseDB || !postgresDBInitialized { 108 | db.SetLogger(nopLogger{}) 109 | 110 | // Sometimes a Postgres database can't be dropped because of some internal 111 | // Postgres housekeeping (Postgres runs as a collection of collaborating 112 | // OS processes). Before trying to drop it, terminate all other connections. 113 | db.Exec(`SELECT pid, pg_terminate_backend(pid) 114 | FROM pg_stat_activity 115 | WHERE datname = '` + dbName + `' 116 | AND pid != pg_backend_pid();`) 117 | 118 | db.Exec("DROP DATABASE IF EXISTS " + dbName + ";") 119 | db.Exec("CREATE DATABASE " + dbName + ";") 120 | db.Exec(fmt.Sprintf("DROP USER IF EXISTS %s;", UserName)) 121 | db.Exec(fmt.Sprintf("CREATE USER %s WITH PASSWORD '%s';", UserName, UserPassword)) 122 | db.Close() 123 | 124 | if !opts.SkipMigration { 125 | // Migrate using our migrations 126 | testMigrate(t, "postgres", dbName) 127 | } 128 | 129 | db = testDBConnectWithUser(t, "postgres", dbName, "postgres", "postgres") 130 | db.Exec("GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO " + UserName + ";") 131 | db.Exec("GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO " + UserName + ";") 132 | 133 | db.Close() 134 | postgresDBInitialized = true 135 | } else { 136 | // If the database should be reused and already exists, we truncate 137 | // tables. 138 | var tablesToTruncate []string 139 | 140 | db = testDBConnectWithUser(t, "postgres", dbName, "postgres", "postgres") 141 | 142 | // Find all user tables except the schema migrations table. 143 | rows, err := db.Table("pg_stat_user_tables"). 144 | Where("relname != 'schema_migrations'"). 145 | Rows() 146 | if err != nil { 147 | t.Errorf("unable to determine tables to truncate: %v", err) 148 | } 149 | defer rows.Close() 150 | 151 | var table struct { 152 | Relname string 153 | Schemaname string 154 | } 155 | for rows.Next() { 156 | if err := db.ScanRows(rows, &table); err != nil { 157 | t.Errorf("unable to scan rows: %v", err) 158 | } 159 | 160 | // We truncate the user tables from all schemas, so prepend the 161 | // table name with the schema name and quote both, e.g. 162 | // "public"."operations" 163 | tablesToTruncate = append(tablesToTruncate, 164 | fmt.Sprintf("%q.%q", table.Schemaname, table.Relname), 165 | ) 166 | } 167 | 168 | if len(tablesToTruncate) > 0 { 169 | // By truncating all tables within the same query, foreign key 170 | // constraints don't break. 171 | db = db.Exec(fmt.Sprintf("TRUNCATE %s;", strings.Join(tablesToTruncate, ","))) 172 | if errs := db.GetErrors(); len(errs) > 0 { 173 | t.Errorf("failed to truncate tables: %v", 174 | multierror.Append(nil, errs...), 175 | ) 176 | } 177 | } 178 | db.Close() 179 | } 180 | 181 | return TestDBConnect(t, "postgres", dbName) 182 | } 183 | 184 | // TestPostgresDB sets up the test DB to use, including running any migrations. 185 | // 186 | // This expects a local Postgres to be running with default "postgres/postgres" 187 | // superuser credentials. 188 | // 189 | // This also expects that at this or some parent directory, there is the 190 | // path represented by MigrationsDir where the migration files can be found. 191 | // If no migrations are found, then an error is raised. 192 | func TestPostgresDB(t testing.T, dbName string) *gorm.DB { 193 | return TestPostgresDBWithOpts(t, dbName, &TestDBOptions{}) 194 | } 195 | 196 | // TestPostgresDBString returns the connection string for the database. 197 | // This is safe to call alongside TestPostgresDB. This won't be valid until 198 | // TestPostgresDB is called. 199 | func TestPostgresDBString(t testing.T, dbName string) string { 200 | return testDBConnectWithUserString(t, "postgres", dbName, UserName, UserPassword) 201 | } 202 | 203 | // TestDBConnect connects to the local test database but does not recreate it. 204 | func TestDBConnect(t testing.T, family, dbName string) *gorm.DB { 205 | return testDBConnectWithUser(t, family, dbName, UserName, UserPassword) 206 | } 207 | 208 | // TestDBConnectSuper connects to the local database as a super user but does 209 | // not recreate it. 210 | func TestDBConnectSuper(t testing.T, family, dbName string) *gorm.DB { 211 | return testDBConnectWithUser(t, family, dbName, "root", "root") 212 | } 213 | 214 | // TestDBCommit commits a query and verifies it succeeds. This function is 215 | // useful for one-liners in tests to load data or make changes. For example: 216 | // 217 | // var count int 218 | // TestDBCommit(t, db.Begin().Table("foos").Count(&count)) 219 | // 220 | func TestDBCommit(t testing.T, db *gorm.DB) { 221 | t.Helper() 222 | 223 | if errs := db.Commit().GetErrors(); len(errs) > 0 { 224 | err := multierror.Append(nil, errs...) 225 | t.Fatalf("err: %s", err) 226 | } 227 | } 228 | 229 | // TestDBSave saves a changed model. 230 | func TestDBSave(t testing.T, db *gorm.DB, m interface{}) { 231 | t.Helper() 232 | 233 | q := db.Begin().Save(m).Commit() 234 | if errs := q.GetErrors(); len(errs) > 0 { 235 | err := multierror.Append(nil, errs...) 236 | t.Fatalf("err: %s", err) 237 | } 238 | } 239 | 240 | func testDBConnectWithUser(t testing.T, family, database, user, pass string) *gorm.DB { 241 | t.Helper() 242 | 243 | db, err := gorm.Open("postgres", 244 | fmt.Sprintf("host=127.0.0.1 port=5432 sslmode=disable user=%s password=%s dbname=%s", user, pass, database)) 245 | if err != nil { 246 | t.Fatalf("err: %s", err) 247 | } 248 | 249 | // BlockGlobalUpdate - Error on update/delete without where clause 250 | // since it's usually a bug to not have any filters when querying models. 251 | // 252 | // By default, GORM will not include zero values in where clauses. 253 | // This setting may help prevent bugs or missing validation causing major data corruption. 254 | // 255 | // For example: 256 | // 257 | // var result models.Deployment 258 | // db.Model(models.Deployment{}). 259 | // Where(models.Deployment{ClusterID: sqluuid.UUID{}}). 260 | // First(&result) 261 | // 262 | // Results in this query: 263 | // SELECT * FROM `consul_deployments` ORDER BY `consul_deployments`.`number` ASC LIMIT 1 264 | // 265 | // Which effectively picks a random deployment. 266 | db.BlockGlobalUpdate(true) 267 | 268 | if *gormDebug { 269 | db.LogMode(true) 270 | } 271 | 272 | return db 273 | } 274 | 275 | func testDBConnectWithUserString(t testing.T, family, database, user, pass string) string { 276 | return fmt.Sprintf("host=127.0.0.1 port=5432 sslmode=disable user=%s password=%s dbname=%s", 277 | user, pass, database) 278 | } 279 | 280 | // testMigrate migrates the current database. 281 | func testMigrate(t testing.T, family, dbName string) { 282 | t.Helper() 283 | 284 | // Find the path to the migrations. We do this using a heuristic 285 | // of just searching up directories until we find 286 | // "models/migrations". This assumes any tests run 287 | // will be a child of the root folder. 288 | dir := testMigrateDir(t) 289 | 290 | db := testDBConnectWithUser(t, "postgres", dbName, "postgres", "postgres") 291 | defer db.Close() 292 | driver, err := migratePostgres.WithInstance(db.DB(), &migratePostgres.Config{ 293 | MigrationsTable: "waypoint_hzn_test_migrations", 294 | }) 295 | if err != nil { 296 | t.Fatalf("err: %s", err) 297 | } 298 | 299 | // Creator the migrator 300 | migrator, err := migrate.NewWithDatabaseInstance( 301 | "file://"+dir, family, driver) 302 | if err != nil { 303 | t.Fatalf("err: %s", err) 304 | } 305 | defer migrator.Close() 306 | 307 | // Enable logging 308 | if *gormDebug { 309 | migrator.Log = &migrateLogger{t: t} 310 | } 311 | 312 | // Migrate 313 | if err := migrator.Up(); err != nil { 314 | t.Fatalf("err migrating: %s", err) 315 | } 316 | } 317 | 318 | // testMigrateDir attempts to find the directory with migrations. This will 319 | // search the working directory and parents first, then will fall back to 320 | // the Go Modules directory. 321 | func testMigrateDir(t testing.T) string { 322 | search := func(root string) string { 323 | for { 324 | current := filepath.Join(root, MigrationsDir) 325 | _, err := os.Stat(current) 326 | if err == nil { 327 | // Found it! 328 | return current 329 | } 330 | if err != nil && !os.IsNotExist(err) { 331 | t.Fatalf("error at %s: %s", root, err) 332 | } 333 | 334 | // Traverse to parent 335 | next := filepath.Dir(root) 336 | if root == next { 337 | return "" 338 | } 339 | root = next 340 | } 341 | } 342 | 343 | // Search our working directory first 344 | dir, err := os.Getwd() 345 | if err != nil { 346 | t.Fatalf("err getting working dir: %s", err) 347 | } 348 | if v := search(dir); v != "" { 349 | return v 350 | } 351 | 352 | // Search for a gomod directory 353 | dir = gomodDir(t) 354 | if dir != "" { 355 | return search(dir) 356 | } 357 | 358 | return "" 359 | } 360 | 361 | // gomodDir finds the Horizon module with the latest version. 362 | func gomodDir(t testing.T) string { 363 | // Get the first GOPATH element 364 | gopath := build.Default.GOPATH 365 | if idx := strings.Index(gopath, ":"); idx != -1 { 366 | gopath = gopath[:idx] 367 | } 368 | if gopath == "" { 369 | return "" 370 | } 371 | 372 | // Open the directory to list all modules for HashiCorp 373 | root := filepath.Join(gopath, "pkg", "mod", "github.com", "hashicorp") 374 | dirh, err := os.Open(root) 375 | if err != nil { 376 | if os.IsNotExist(err) { 377 | return "" 378 | } 379 | 380 | t.Fatalf("error looking for migrations: %s", err) 381 | } 382 | defer dirh.Close() 383 | 384 | // Read all the directories and sort them 385 | names, err := dirh.Readdirnames(-1) 386 | if err != nil { 387 | t.Fatalf("error looking for migrations: %s", err) 388 | } 389 | sort.Sort(sort.Reverse(sort.StringSlice(names))) 390 | 391 | // Find the horizon one 392 | for _, n := range names { 393 | if strings.HasPrefix(n, "waypoint-hzn@") { 394 | return filepath.Join(root, n) 395 | } 396 | } 397 | 398 | return "" 399 | } 400 | 401 | // migrateLogger implements migrate.Logger so that we can have logging 402 | // on migrations when requested. 403 | type migrateLogger struct{ t testing.T } 404 | 405 | func (m *migrateLogger) Printf(format string, v ...interface{}) { 406 | m.t.Logf(format, v...) 407 | } 408 | 409 | func (m *migrateLogger) Verbose() bool { 410 | return true 411 | } 412 | 413 | // TestAssertCount is a helper for asserting the expected number of rows exist 414 | // in the DB. It requires that the db argument is passed a *gorm.DB that must 415 | // already have had a Table selection and optionally a where clause added to 416 | // specify what to count. This helper will run `Count()` on the db passed and 417 | // assert it succeeds and finds the desired number of records. 418 | // Examples: 419 | // // Assert foo is empty 420 | // models.TestAssertCount(t, db.Table("foo"), 0) 421 | // // Assert 3 providers exist for a given module 422 | // models.TestAssertCount(t, 423 | // db.Model(&models.ModuleProvider{}).Where("provider = ?", provider), 424 | // 3) 425 | func TestAssertCount(t testing.T, db *gorm.DB, want int) { 426 | t.Helper() 427 | 428 | count := 0 429 | // Assume DB already describes a query that selects the rows required 430 | db.Count(&count) 431 | if errs := db.GetErrors(); len(errs) > 0 { 432 | err := multierror.Append(nil, errs...) 433 | t.Fatalf("failed counting rows: %s", err) 434 | } 435 | 436 | if want != count { 437 | t.Fatalf("got %d rows, want %d", count, want) 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /kubernetes/waypoint-hzn.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: waypoint-hzn 5 | labels: 6 | app: waypoint-hzn 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: waypoint-hzn 12 | template: 13 | metadata: 14 | labels: 15 | app: waypoint-hzn 16 | spec: 17 | imagePullSecrets: 18 | - name: quay 19 | 20 | containers: 21 | - name: waypoint-hzn 22 | image: quay.io/hashicorp/waypoint-hzn:latest 23 | env: 24 | - name: DATABASE_URL 25 | valueFrom: 26 | secretKeyRef: 27 | name: waypoint-hzn-rds 28 | key: url 29 | 30 | - name: MIGRATIONS_APPLY 31 | value: "1" 32 | 33 | - name: MIGRATIONS_PATH 34 | value: "/migrations" 35 | 36 | - name: CONTROL_TOKEN 37 | valueFrom: 38 | secretKeyRef: 39 | name: register 40 | key: token 41 | 42 | - name: CONTROL_ADDR 43 | valueFrom: 44 | configMapKeyRef: 45 | name: wp-hzn 46 | key: control_addr 47 | 48 | - name: CONTROL_INSECURE 49 | value: "0" 50 | 51 | - name: DOMAIN 52 | valueFrom: 53 | configMapKeyRef: 54 | name: wp-hzn 55 | key: domain 56 | 57 | - name: LISTEN_ADDR 58 | value: ":8080" 59 | 60 | ports: 61 | - name: api 62 | containerPort: 8080 63 | 64 | readinessProbe: 65 | exec: 66 | command: ["/usr/bin/grpc_health_probe", "-addr=:8080"] 67 | initialDelaySeconds: 10 68 | periodSeconds: 3 69 | 70 | livenessProbe: 71 | exec: 72 | command: ["/usr/bin/grpc_health_probe", "-addr=:8080"] 73 | initialDelaySeconds: 10 74 | periodSeconds: 3 75 | -------------------------------------------------------------------------------- /migrations/20200521162525_initial.down.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp/waypoint-hzn/1dc13227ccfd969e610d8977bada8696fa3300cd/migrations/20200521162525_initial.down.sql -------------------------------------------------------------------------------- /migrations/20200521162525_initial.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS registrations ( 2 | id SERIAL PRIMARY KEY, 3 | account_id bytea NOT NULL UNIQUE, 4 | email text NOT NULL, 5 | name text, 6 | created_at timestamptz NOT NULL DEFAULT NOW(), 7 | updated_at timestamptz NOT NULL DEFAULT NOW() 8 | ); 9 | 10 | CREATE TABLE IF NOT EXISTS hostnames ( 11 | id SERIAL PRIMARY KEY, 12 | registration_id int NOT NULL, 13 | hostname text NOT NULL UNIQUE, 14 | labels text[] NOT NULL, 15 | created_at timestamptz NOT NULL DEFAULT NOW(), 16 | updated_at timestamptz NOT NULL DEFAULT NOW() 17 | ); 18 | -------------------------------------------------------------------------------- /pkg/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lib/pq" 7 | ) 8 | 9 | type Registration struct { 10 | Id int `gorm:"primary_key"` 11 | AccountId []byte 12 | Email string 13 | Name string 14 | CreatedAt time.Time 15 | UpdatedAt time.Time 16 | } 17 | 18 | type Hostname struct { 19 | Id int `gorm:"primary_key"` 20 | 21 | Registration *Registration 22 | RegistrationId int 23 | 24 | Hostname string 25 | Labels pq.StringArray 26 | 27 | CreatedAt time.Time 28 | UpdatedAt time.Time 29 | } 30 | -------------------------------------------------------------------------------- /pkg/pb/gen.go: -------------------------------------------------------------------------------- 1 | package pb 2 | 3 | //go:generate sh -c "protoc -I../../proto --go_out=plugins=grpc:. --validate_out=\"lang=go:.\" ../../proto/*.proto" 4 | -------------------------------------------------------------------------------- /pkg/pb/server.pb.validate.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-validate. DO NOT EDIT. 2 | // source: server.proto 3 | 4 | package pb 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/mail" 12 | "net/url" 13 | "regexp" 14 | "strings" 15 | "time" 16 | "unicode/utf8" 17 | 18 | "github.com/golang/protobuf/ptypes" 19 | ) 20 | 21 | // ensure the imports are used 22 | var ( 23 | _ = bytes.MinRead 24 | _ = errors.New("") 25 | _ = fmt.Print 26 | _ = utf8.UTFMax 27 | _ = (*regexp.Regexp)(nil) 28 | _ = (*strings.Reader)(nil) 29 | _ = net.IPv4len 30 | _ = time.Duration(0) 31 | _ = (*url.URL)(nil) 32 | _ = (*mail.Address)(nil) 33 | _ = ptypes.DynamicAny{} 34 | ) 35 | 36 | // define the regex for a UUID once up-front 37 | var _server_uuidPattern = regexp.MustCompile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") 38 | 39 | // Validate checks the field values on Label with the rules defined in the 40 | // proto definition for this message. If any rules are violated, an error is returned. 41 | func (m *Label) Validate() error { 42 | if m == nil { 43 | return nil 44 | } 45 | 46 | // no validation rules for Name 47 | 48 | // no validation rules for Value 49 | 50 | return nil 51 | } 52 | 53 | // LabelValidationError is the validation error returned by Label.Validate if 54 | // the designated constraints aren't met. 55 | type LabelValidationError struct { 56 | field string 57 | reason string 58 | cause error 59 | key bool 60 | } 61 | 62 | // Field function returns field value. 63 | func (e LabelValidationError) Field() string { return e.field } 64 | 65 | // Reason function returns reason value. 66 | func (e LabelValidationError) Reason() string { return e.reason } 67 | 68 | // Cause function returns cause value. 69 | func (e LabelValidationError) Cause() error { return e.cause } 70 | 71 | // Key function returns key value. 72 | func (e LabelValidationError) Key() bool { return e.key } 73 | 74 | // ErrorName returns error name. 75 | func (e LabelValidationError) ErrorName() string { return "LabelValidationError" } 76 | 77 | // Error satisfies the builtin error interface 78 | func (e LabelValidationError) Error() string { 79 | cause := "" 80 | if e.cause != nil { 81 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 82 | } 83 | 84 | key := "" 85 | if e.key { 86 | key = "key for " 87 | } 88 | 89 | return fmt.Sprintf( 90 | "invalid %sLabel.%s: %s%s", 91 | key, 92 | e.field, 93 | e.reason, 94 | cause) 95 | } 96 | 97 | var _ error = LabelValidationError{} 98 | 99 | var _ interface { 100 | Field() string 101 | Reason() string 102 | Key() bool 103 | Cause() error 104 | ErrorName() string 105 | } = LabelValidationError{} 106 | 107 | // Validate checks the field values on LabelSet with the rules defined in the 108 | // proto definition for this message. If any rules are violated, an error is returned. 109 | func (m *LabelSet) Validate() error { 110 | if m == nil { 111 | return nil 112 | } 113 | 114 | if len(m.GetLabels()) < 1 { 115 | return LabelSetValidationError{ 116 | field: "Labels", 117 | reason: "value must contain at least 1 item(s)", 118 | } 119 | } 120 | 121 | for idx, item := range m.GetLabels() { 122 | _, _ = idx, item 123 | 124 | if v, ok := interface{}(item).(interface{ Validate() error }); ok { 125 | if err := v.Validate(); err != nil { 126 | return LabelSetValidationError{ 127 | field: fmt.Sprintf("Labels[%v]", idx), 128 | reason: "embedded message failed validation", 129 | cause: err, 130 | } 131 | } 132 | } 133 | 134 | } 135 | 136 | return nil 137 | } 138 | 139 | // LabelSetValidationError is the validation error returned by 140 | // LabelSet.Validate if the designated constraints aren't met. 141 | type LabelSetValidationError struct { 142 | field string 143 | reason string 144 | cause error 145 | key bool 146 | } 147 | 148 | // Field function returns field value. 149 | func (e LabelSetValidationError) Field() string { return e.field } 150 | 151 | // Reason function returns reason value. 152 | func (e LabelSetValidationError) Reason() string { return e.reason } 153 | 154 | // Cause function returns cause value. 155 | func (e LabelSetValidationError) Cause() error { return e.cause } 156 | 157 | // Key function returns key value. 158 | func (e LabelSetValidationError) Key() bool { return e.key } 159 | 160 | // ErrorName returns error name. 161 | func (e LabelSetValidationError) ErrorName() string { return "LabelSetValidationError" } 162 | 163 | // Error satisfies the builtin error interface 164 | func (e LabelSetValidationError) Error() string { 165 | cause := "" 166 | if e.cause != nil { 167 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 168 | } 169 | 170 | key := "" 171 | if e.key { 172 | key = "key for " 173 | } 174 | 175 | return fmt.Sprintf( 176 | "invalid %sLabelSet.%s: %s%s", 177 | key, 178 | e.field, 179 | e.reason, 180 | cause) 181 | } 182 | 183 | var _ error = LabelSetValidationError{} 184 | 185 | var _ interface { 186 | Field() string 187 | Reason() string 188 | Key() bool 189 | Cause() error 190 | ErrorName() string 191 | } = LabelSetValidationError{} 192 | 193 | // Validate checks the field values on RegisterGuestAccountRequest with the 194 | // rules defined in the proto definition for this message. If any rules are 195 | // violated, an error is returned. 196 | func (m *RegisterGuestAccountRequest) Validate() error { 197 | if m == nil { 198 | return nil 199 | } 200 | 201 | // no validation rules for ServerId 202 | 203 | // no validation rules for AcceptTos 204 | 205 | return nil 206 | } 207 | 208 | // RegisterGuestAccountRequestValidationError is the validation error returned 209 | // by RegisterGuestAccountRequest.Validate if the designated constraints 210 | // aren't met. 211 | type RegisterGuestAccountRequestValidationError struct { 212 | field string 213 | reason string 214 | cause error 215 | key bool 216 | } 217 | 218 | // Field function returns field value. 219 | func (e RegisterGuestAccountRequestValidationError) Field() string { return e.field } 220 | 221 | // Reason function returns reason value. 222 | func (e RegisterGuestAccountRequestValidationError) Reason() string { return e.reason } 223 | 224 | // Cause function returns cause value. 225 | func (e RegisterGuestAccountRequestValidationError) Cause() error { return e.cause } 226 | 227 | // Key function returns key value. 228 | func (e RegisterGuestAccountRequestValidationError) Key() bool { return e.key } 229 | 230 | // ErrorName returns error name. 231 | func (e RegisterGuestAccountRequestValidationError) ErrorName() string { 232 | return "RegisterGuestAccountRequestValidationError" 233 | } 234 | 235 | // Error satisfies the builtin error interface 236 | func (e RegisterGuestAccountRequestValidationError) Error() string { 237 | cause := "" 238 | if e.cause != nil { 239 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 240 | } 241 | 242 | key := "" 243 | if e.key { 244 | key = "key for " 245 | } 246 | 247 | return fmt.Sprintf( 248 | "invalid %sRegisterGuestAccountRequest.%s: %s%s", 249 | key, 250 | e.field, 251 | e.reason, 252 | cause) 253 | } 254 | 255 | var _ error = RegisterGuestAccountRequestValidationError{} 256 | 257 | var _ interface { 258 | Field() string 259 | Reason() string 260 | Key() bool 261 | Cause() error 262 | ErrorName() string 263 | } = RegisterGuestAccountRequestValidationError{} 264 | 265 | // Validate checks the field values on RegisterGuestAccountResponse with the 266 | // rules defined in the proto definition for this message. If any rules are 267 | // violated, an error is returned. 268 | func (m *RegisterGuestAccountResponse) Validate() error { 269 | if m == nil { 270 | return nil 271 | } 272 | 273 | // no validation rules for Token 274 | 275 | return nil 276 | } 277 | 278 | // RegisterGuestAccountResponseValidationError is the validation error returned 279 | // by RegisterGuestAccountResponse.Validate if the designated constraints 280 | // aren't met. 281 | type RegisterGuestAccountResponseValidationError struct { 282 | field string 283 | reason string 284 | cause error 285 | key bool 286 | } 287 | 288 | // Field function returns field value. 289 | func (e RegisterGuestAccountResponseValidationError) Field() string { return e.field } 290 | 291 | // Reason function returns reason value. 292 | func (e RegisterGuestAccountResponseValidationError) Reason() string { return e.reason } 293 | 294 | // Cause function returns cause value. 295 | func (e RegisterGuestAccountResponseValidationError) Cause() error { return e.cause } 296 | 297 | // Key function returns key value. 298 | func (e RegisterGuestAccountResponseValidationError) Key() bool { return e.key } 299 | 300 | // ErrorName returns error name. 301 | func (e RegisterGuestAccountResponseValidationError) ErrorName() string { 302 | return "RegisterGuestAccountResponseValidationError" 303 | } 304 | 305 | // Error satisfies the builtin error interface 306 | func (e RegisterGuestAccountResponseValidationError) Error() string { 307 | cause := "" 308 | if e.cause != nil { 309 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 310 | } 311 | 312 | key := "" 313 | if e.key { 314 | key = "key for " 315 | } 316 | 317 | return fmt.Sprintf( 318 | "invalid %sRegisterGuestAccountResponse.%s: %s%s", 319 | key, 320 | e.field, 321 | e.reason, 322 | cause) 323 | } 324 | 325 | var _ error = RegisterGuestAccountResponseValidationError{} 326 | 327 | var _ interface { 328 | Field() string 329 | Reason() string 330 | Key() bool 331 | Cause() error 332 | ErrorName() string 333 | } = RegisterGuestAccountResponseValidationError{} 334 | 335 | // Validate checks the field values on RegisterHostnameRequest with the rules 336 | // defined in the proto definition for this message. If any rules are 337 | // violated, an error is returned. 338 | func (m *RegisterHostnameRequest) Validate() error { 339 | if m == nil { 340 | return nil 341 | } 342 | 343 | if m.GetLabels() == nil { 344 | return RegisterHostnameRequestValidationError{ 345 | field: "Labels", 346 | reason: "value is required", 347 | } 348 | } 349 | 350 | if v, ok := interface{}(m.GetLabels()).(interface{ Validate() error }); ok { 351 | if err := v.Validate(); err != nil { 352 | return RegisterHostnameRequestValidationError{ 353 | field: "Labels", 354 | reason: "embedded message failed validation", 355 | cause: err, 356 | } 357 | } 358 | } 359 | 360 | switch m.Hostname.(type) { 361 | 362 | case *RegisterHostnameRequest_Generate: 363 | 364 | if v, ok := interface{}(m.GetGenerate()).(interface{ Validate() error }); ok { 365 | if err := v.Validate(); err != nil { 366 | return RegisterHostnameRequestValidationError{ 367 | field: "Generate", 368 | reason: "embedded message failed validation", 369 | cause: err, 370 | } 371 | } 372 | } 373 | 374 | case *RegisterHostnameRequest_Exact: 375 | 376 | if _, ok := _RegisterHostnameRequest_Exact_NotInLookup[m.GetExact()]; ok { 377 | return RegisterHostnameRequestValidationError{ 378 | field: "Exact", 379 | reason: "value must not be in list [admin api blog hzn horizon waypoint]", 380 | } 381 | } 382 | 383 | if utf8.RuneCountInString(m.GetExact()) < 3 { 384 | return RegisterHostnameRequestValidationError{ 385 | field: "Exact", 386 | reason: "value length must be at least 3 runes", 387 | } 388 | } 389 | 390 | if !_RegisterHostnameRequest_Exact_Pattern.MatchString(m.GetExact()) { 391 | return RegisterHostnameRequestValidationError{ 392 | field: "Exact", 393 | reason: "value does not match regex pattern \"\\\\w+[\\\\w\\\\d-]*\"", 394 | } 395 | } 396 | 397 | default: 398 | return RegisterHostnameRequestValidationError{ 399 | field: "Hostname", 400 | reason: "value is required", 401 | } 402 | 403 | } 404 | 405 | return nil 406 | } 407 | 408 | // RegisterHostnameRequestValidationError is the validation error returned by 409 | // RegisterHostnameRequest.Validate if the designated constraints aren't met. 410 | type RegisterHostnameRequestValidationError struct { 411 | field string 412 | reason string 413 | cause error 414 | key bool 415 | } 416 | 417 | // Field function returns field value. 418 | func (e RegisterHostnameRequestValidationError) Field() string { return e.field } 419 | 420 | // Reason function returns reason value. 421 | func (e RegisterHostnameRequestValidationError) Reason() string { return e.reason } 422 | 423 | // Cause function returns cause value. 424 | func (e RegisterHostnameRequestValidationError) Cause() error { return e.cause } 425 | 426 | // Key function returns key value. 427 | func (e RegisterHostnameRequestValidationError) Key() bool { return e.key } 428 | 429 | // ErrorName returns error name. 430 | func (e RegisterHostnameRequestValidationError) ErrorName() string { 431 | return "RegisterHostnameRequestValidationError" 432 | } 433 | 434 | // Error satisfies the builtin error interface 435 | func (e RegisterHostnameRequestValidationError) Error() string { 436 | cause := "" 437 | if e.cause != nil { 438 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 439 | } 440 | 441 | key := "" 442 | if e.key { 443 | key = "key for " 444 | } 445 | 446 | return fmt.Sprintf( 447 | "invalid %sRegisterHostnameRequest.%s: %s%s", 448 | key, 449 | e.field, 450 | e.reason, 451 | cause) 452 | } 453 | 454 | var _ error = RegisterHostnameRequestValidationError{} 455 | 456 | var _ interface { 457 | Field() string 458 | Reason() string 459 | Key() bool 460 | Cause() error 461 | ErrorName() string 462 | } = RegisterHostnameRequestValidationError{} 463 | 464 | var _RegisterHostnameRequest_Exact_NotInLookup = map[string]struct{}{ 465 | "admin": {}, 466 | "api": {}, 467 | "blog": {}, 468 | "hzn": {}, 469 | "horizon": {}, 470 | "waypoint": {}, 471 | } 472 | 473 | var _RegisterHostnameRequest_Exact_Pattern = regexp.MustCompile("\\w+[\\w\\d-]*") 474 | 475 | // Validate checks the field values on RegisterHostnameResponse with the rules 476 | // defined in the proto definition for this message. If any rules are 477 | // violated, an error is returned. 478 | func (m *RegisterHostnameResponse) Validate() error { 479 | if m == nil { 480 | return nil 481 | } 482 | 483 | // no validation rules for Hostname 484 | 485 | // no validation rules for Fqdn 486 | 487 | return nil 488 | } 489 | 490 | // RegisterHostnameResponseValidationError is the validation error returned by 491 | // RegisterHostnameResponse.Validate if the designated constraints aren't met. 492 | type RegisterHostnameResponseValidationError struct { 493 | field string 494 | reason string 495 | cause error 496 | key bool 497 | } 498 | 499 | // Field function returns field value. 500 | func (e RegisterHostnameResponseValidationError) Field() string { return e.field } 501 | 502 | // Reason function returns reason value. 503 | func (e RegisterHostnameResponseValidationError) Reason() string { return e.reason } 504 | 505 | // Cause function returns cause value. 506 | func (e RegisterHostnameResponseValidationError) Cause() error { return e.cause } 507 | 508 | // Key function returns key value. 509 | func (e RegisterHostnameResponseValidationError) Key() bool { return e.key } 510 | 511 | // ErrorName returns error name. 512 | func (e RegisterHostnameResponseValidationError) ErrorName() string { 513 | return "RegisterHostnameResponseValidationError" 514 | } 515 | 516 | // Error satisfies the builtin error interface 517 | func (e RegisterHostnameResponseValidationError) Error() string { 518 | cause := "" 519 | if e.cause != nil { 520 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 521 | } 522 | 523 | key := "" 524 | if e.key { 525 | key = "key for " 526 | } 527 | 528 | return fmt.Sprintf( 529 | "invalid %sRegisterHostnameResponse.%s: %s%s", 530 | key, 531 | e.field, 532 | e.reason, 533 | cause) 534 | } 535 | 536 | var _ error = RegisterHostnameResponseValidationError{} 537 | 538 | var _ interface { 539 | Field() string 540 | Reason() string 541 | Key() bool 542 | Cause() error 543 | ErrorName() string 544 | } = RegisterHostnameResponseValidationError{} 545 | 546 | // Validate checks the field values on ListHostnamesRequest with the rules 547 | // defined in the proto definition for this message. If any rules are 548 | // violated, an error is returned. 549 | func (m *ListHostnamesRequest) Validate() error { 550 | if m == nil { 551 | return nil 552 | } 553 | 554 | return nil 555 | } 556 | 557 | // ListHostnamesRequestValidationError is the validation error returned by 558 | // ListHostnamesRequest.Validate if the designated constraints aren't met. 559 | type ListHostnamesRequestValidationError struct { 560 | field string 561 | reason string 562 | cause error 563 | key bool 564 | } 565 | 566 | // Field function returns field value. 567 | func (e ListHostnamesRequestValidationError) Field() string { return e.field } 568 | 569 | // Reason function returns reason value. 570 | func (e ListHostnamesRequestValidationError) Reason() string { return e.reason } 571 | 572 | // Cause function returns cause value. 573 | func (e ListHostnamesRequestValidationError) Cause() error { return e.cause } 574 | 575 | // Key function returns key value. 576 | func (e ListHostnamesRequestValidationError) Key() bool { return e.key } 577 | 578 | // ErrorName returns error name. 579 | func (e ListHostnamesRequestValidationError) ErrorName() string { 580 | return "ListHostnamesRequestValidationError" 581 | } 582 | 583 | // Error satisfies the builtin error interface 584 | func (e ListHostnamesRequestValidationError) Error() string { 585 | cause := "" 586 | if e.cause != nil { 587 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 588 | } 589 | 590 | key := "" 591 | if e.key { 592 | key = "key for " 593 | } 594 | 595 | return fmt.Sprintf( 596 | "invalid %sListHostnamesRequest.%s: %s%s", 597 | key, 598 | e.field, 599 | e.reason, 600 | cause) 601 | } 602 | 603 | var _ error = ListHostnamesRequestValidationError{} 604 | 605 | var _ interface { 606 | Field() string 607 | Reason() string 608 | Key() bool 609 | Cause() error 610 | ErrorName() string 611 | } = ListHostnamesRequestValidationError{} 612 | 613 | // Validate checks the field values on ListHostnamesResponse with the rules 614 | // defined in the proto definition for this message. If any rules are 615 | // violated, an error is returned. 616 | func (m *ListHostnamesResponse) Validate() error { 617 | if m == nil { 618 | return nil 619 | } 620 | 621 | for idx, item := range m.GetHostnames() { 622 | _, _ = idx, item 623 | 624 | if v, ok := interface{}(item).(interface{ Validate() error }); ok { 625 | if err := v.Validate(); err != nil { 626 | return ListHostnamesResponseValidationError{ 627 | field: fmt.Sprintf("Hostnames[%v]", idx), 628 | reason: "embedded message failed validation", 629 | cause: err, 630 | } 631 | } 632 | } 633 | 634 | } 635 | 636 | return nil 637 | } 638 | 639 | // ListHostnamesResponseValidationError is the validation error returned by 640 | // ListHostnamesResponse.Validate if the designated constraints aren't met. 641 | type ListHostnamesResponseValidationError struct { 642 | field string 643 | reason string 644 | cause error 645 | key bool 646 | } 647 | 648 | // Field function returns field value. 649 | func (e ListHostnamesResponseValidationError) Field() string { return e.field } 650 | 651 | // Reason function returns reason value. 652 | func (e ListHostnamesResponseValidationError) Reason() string { return e.reason } 653 | 654 | // Cause function returns cause value. 655 | func (e ListHostnamesResponseValidationError) Cause() error { return e.cause } 656 | 657 | // Key function returns key value. 658 | func (e ListHostnamesResponseValidationError) Key() bool { return e.key } 659 | 660 | // ErrorName returns error name. 661 | func (e ListHostnamesResponseValidationError) ErrorName() string { 662 | return "ListHostnamesResponseValidationError" 663 | } 664 | 665 | // Error satisfies the builtin error interface 666 | func (e ListHostnamesResponseValidationError) Error() string { 667 | cause := "" 668 | if e.cause != nil { 669 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 670 | } 671 | 672 | key := "" 673 | if e.key { 674 | key = "key for " 675 | } 676 | 677 | return fmt.Sprintf( 678 | "invalid %sListHostnamesResponse.%s: %s%s", 679 | key, 680 | e.field, 681 | e.reason, 682 | cause) 683 | } 684 | 685 | var _ error = ListHostnamesResponseValidationError{} 686 | 687 | var _ interface { 688 | Field() string 689 | Reason() string 690 | Key() bool 691 | Cause() error 692 | ErrorName() string 693 | } = ListHostnamesResponseValidationError{} 694 | 695 | // Validate checks the field values on DeleteHostnameRequest with the rules 696 | // defined in the proto definition for this message. If any rules are 697 | // violated, an error is returned. 698 | func (m *DeleteHostnameRequest) Validate() error { 699 | if m == nil { 700 | return nil 701 | } 702 | 703 | // no validation rules for Hostname 704 | 705 | return nil 706 | } 707 | 708 | // DeleteHostnameRequestValidationError is the validation error returned by 709 | // DeleteHostnameRequest.Validate if the designated constraints aren't met. 710 | type DeleteHostnameRequestValidationError struct { 711 | field string 712 | reason string 713 | cause error 714 | key bool 715 | } 716 | 717 | // Field function returns field value. 718 | func (e DeleteHostnameRequestValidationError) Field() string { return e.field } 719 | 720 | // Reason function returns reason value. 721 | func (e DeleteHostnameRequestValidationError) Reason() string { return e.reason } 722 | 723 | // Cause function returns cause value. 724 | func (e DeleteHostnameRequestValidationError) Cause() error { return e.cause } 725 | 726 | // Key function returns key value. 727 | func (e DeleteHostnameRequestValidationError) Key() bool { return e.key } 728 | 729 | // ErrorName returns error name. 730 | func (e DeleteHostnameRequestValidationError) ErrorName() string { 731 | return "DeleteHostnameRequestValidationError" 732 | } 733 | 734 | // Error satisfies the builtin error interface 735 | func (e DeleteHostnameRequestValidationError) Error() string { 736 | cause := "" 737 | if e.cause != nil { 738 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 739 | } 740 | 741 | key := "" 742 | if e.key { 743 | key = "key for " 744 | } 745 | 746 | return fmt.Sprintf( 747 | "invalid %sDeleteHostnameRequest.%s: %s%s", 748 | key, 749 | e.field, 750 | e.reason, 751 | cause) 752 | } 753 | 754 | var _ error = DeleteHostnameRequestValidationError{} 755 | 756 | var _ interface { 757 | Field() string 758 | Reason() string 759 | Key() bool 760 | Cause() error 761 | ErrorName() string 762 | } = DeleteHostnameRequestValidationError{} 763 | 764 | // Validate checks the field values on ListHostnamesResponse_Hostname with the 765 | // rules defined in the proto definition for this message. If any rules are 766 | // violated, an error is returned. 767 | func (m *ListHostnamesResponse_Hostname) Validate() error { 768 | if m == nil { 769 | return nil 770 | } 771 | 772 | // no validation rules for Hostname 773 | 774 | // no validation rules for Fqdn 775 | 776 | if v, ok := interface{}(m.GetLabels()).(interface{ Validate() error }); ok { 777 | if err := v.Validate(); err != nil { 778 | return ListHostnamesResponse_HostnameValidationError{ 779 | field: "Labels", 780 | reason: "embedded message failed validation", 781 | cause: err, 782 | } 783 | } 784 | } 785 | 786 | return nil 787 | } 788 | 789 | // ListHostnamesResponse_HostnameValidationError is the validation error 790 | // returned by ListHostnamesResponse_Hostname.Validate if the designated 791 | // constraints aren't met. 792 | type ListHostnamesResponse_HostnameValidationError struct { 793 | field string 794 | reason string 795 | cause error 796 | key bool 797 | } 798 | 799 | // Field function returns field value. 800 | func (e ListHostnamesResponse_HostnameValidationError) Field() string { return e.field } 801 | 802 | // Reason function returns reason value. 803 | func (e ListHostnamesResponse_HostnameValidationError) Reason() string { return e.reason } 804 | 805 | // Cause function returns cause value. 806 | func (e ListHostnamesResponse_HostnameValidationError) Cause() error { return e.cause } 807 | 808 | // Key function returns key value. 809 | func (e ListHostnamesResponse_HostnameValidationError) Key() bool { return e.key } 810 | 811 | // ErrorName returns error name. 812 | func (e ListHostnamesResponse_HostnameValidationError) ErrorName() string { 813 | return "ListHostnamesResponse_HostnameValidationError" 814 | } 815 | 816 | // Error satisfies the builtin error interface 817 | func (e ListHostnamesResponse_HostnameValidationError) Error() string { 818 | cause := "" 819 | if e.cause != nil { 820 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 821 | } 822 | 823 | key := "" 824 | if e.key { 825 | key = "key for " 826 | } 827 | 828 | return fmt.Sprintf( 829 | "invalid %sListHostnamesResponse_Hostname.%s: %s%s", 830 | key, 831 | e.field, 832 | e.reason, 833 | cause) 834 | } 835 | 836 | var _ error = ListHostnamesResponse_HostnameValidationError{} 837 | 838 | var _ interface { 839 | Field() string 840 | Reason() string 841 | Key() bool 842 | Cause() error 843 | ErrorName() string 844 | } = ListHostnamesResponse_HostnameValidationError{} 845 | -------------------------------------------------------------------------------- /pkg/server/auth.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/go-hclog" 7 | "github.com/hashicorp/horizon/pkg/token" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/metadata" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | func (s *service) checkAuth(ctx context.Context) (*token.ValidToken, error) { 14 | L := hclog.FromContext(ctx) 15 | 16 | md, ok := metadata.FromIncomingContext(ctx) 17 | if !ok { 18 | return nil, status.Errorf(codes.PermissionDenied, 19 | "authentication information not presented") 20 | } 21 | 22 | auth := md["authorization"] 23 | 24 | if len(auth) < 1 { 25 | return nil, status.Errorf(codes.PermissionDenied, 26 | "authentication information not presented") 27 | } 28 | 29 | token, err := token.CheckTokenED25519(auth[0], s.tokenPub) 30 | if err != nil { 31 | L.Warn("error checking token signature", "error", err) 32 | return nil, status.Errorf(codes.PermissionDenied, 33 | "authentication information not presented") 34 | } 35 | 36 | account := token.Account() 37 | if account.Namespace != s.Namespace { 38 | return nil, status.Errorf(codes.PermissionDenied, 39 | "invalid token") 40 | } 41 | 42 | return token, nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/server/grpc.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "time" 5 | 6 | petname "github.com/hashicorp/waypoint-hzn/internal/pkg/golang-petname" 7 | hznpb "github.com/hashicorp/horizon/pkg/pb" 8 | "github.com/oklog/run" 9 | "google.golang.org/grpc" 10 | grpchealth "google.golang.org/grpc/health" 11 | healthpb "google.golang.org/grpc/health/grpc_health_v1" 12 | 13 | "github.com/hashicorp/waypoint-hzn/pkg/pb" 14 | ) 15 | 16 | // grpcInit initializes the gRPC server and adds it to the run group. 17 | func grpcInit(group *run.Group, opts *options) error { 18 | log := opts.Logger.Named("grpc") 19 | 20 | var so []grpc.ServerOption 21 | 22 | /* 23 | if opts.AuthChecker != nil { 24 | so = append(so, 25 | grpc.ChainUnaryInterceptor(authUnaryInterceptor(opts.AuthChecker)), 26 | grpc.ChainStreamInterceptor(authStreamInterceptor(opts.AuthChecker)), 27 | ) 28 | } 29 | */ 30 | 31 | so = append(so, 32 | grpc.ChainUnaryInterceptor( 33 | // Insert our logger and also log req/resp 34 | logUnaryInterceptor(log, false), 35 | ), 36 | grpc.ChainStreamInterceptor( 37 | // Insert our logger and log 38 | logStreamInterceptor(log, false), 39 | ), 40 | ) 41 | 42 | s := grpc.NewServer(so...) 43 | 44 | // Get our public key 45 | tokenInfo, err := opts.HznControl.GetTokenPublicKey(opts.Context, &hznpb.Noop{}) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | // Setup petname randomization 51 | petname.NonDeterministicMode() 52 | 53 | // Register our server 54 | pb.RegisterWaypointHznServer(s, &service{ 55 | DB: opts.DB, 56 | Domain: opts.Domain, 57 | Namespace: opts.Namespace, 58 | HznControl: opts.HznControl, 59 | tokenPub: tokenInfo.PublicKey, 60 | Logger: opts.Logger, 61 | }) 62 | 63 | // Register our health check 64 | hs := grpchealth.NewServer() 65 | hs.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING) 66 | healthpb.RegisterHealthServer(s, hs) 67 | 68 | // Add our gRPC server to the run group 69 | group.Add(func() error { 70 | // Set our status to healthy 71 | hs.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) 72 | 73 | // Serve traffic 74 | ln := opts.GRPCListener 75 | log.Info("starting gRPC server", "addr", ln.Addr().String()) 76 | return s.Serve(ln) 77 | }, func(err error) { 78 | // Graceful in a goroutine so we can timeout 79 | gracefulCh := make(chan struct{}) 80 | go func() { 81 | defer close(gracefulCh) 82 | log.Info("shutting down gRPC server") 83 | s.GracefulStop() 84 | }() 85 | 86 | select { 87 | case <-gracefulCh: 88 | 89 | // After a timeout we just forcibly exit. Our gRPC endpoints should 90 | // be fairly quick and their operations are atomic so we just kill 91 | // the connections after a few seconds. 92 | case <-time.After(2 * time.Second): 93 | s.Stop() 94 | } 95 | }) 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /pkg/server/grpc_log.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/hashicorp/go-hclog" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // logUnaryInterceptor returns a gRPC unary interceptor that inserts a hclog.Logger 12 | // into the request context. 13 | // 14 | // Additionally, logUnaryInterceptor logs request and response metadata. If verbose 15 | // is set to true, the request and response attributes are logged too. 16 | func logUnaryInterceptor(logger hclog.Logger, verbose bool) grpc.UnaryServerInterceptor { 17 | return func( 18 | ctx context.Context, 19 | req interface{}, 20 | info *grpc.UnaryServerInfo, 21 | handler grpc.UnaryHandler) (interface{}, error) { 22 | start := time.Now() 23 | 24 | // We don't want to log the health checks 25 | skip := info.FullMethod == "/grpc.health.v1.Health/Check" 26 | 27 | // Log the request. 28 | if !skip { 29 | var reqLogArgs []interface{} 30 | // Log the request's attributes only if verbose is set to true. 31 | if verbose { 32 | reqLogArgs = append(reqLogArgs, "request", req) 33 | } 34 | logger.Info(info.FullMethod+" request", reqLogArgs...) 35 | } 36 | 37 | // Invoke the handler. 38 | ctx = hclog.WithContext(ctx, logger) 39 | resp, err := handler(ctx, req) 40 | 41 | // Log the response. 42 | if !skip { 43 | respLogArgs := []interface{}{ 44 | "error", err, 45 | "duration", time.Since(start).String(), 46 | } 47 | // Log the response's attributes only if verbose is set to true. 48 | if verbose { 49 | respLogArgs = append(respLogArgs, "response", resp) 50 | } 51 | logger.Info(info.FullMethod+" response", respLogArgs...) 52 | } 53 | 54 | return resp, err 55 | } 56 | } 57 | 58 | // logUnaryInterceptor returns a gRPC unary interceptor that inserts a hclog.Logger 59 | // into the request context. 60 | // 61 | // Additionally, logUnaryInterceptor logs request and response metadata. If verbose 62 | // is set to true, the request and response attributes are logged too. 63 | func logStreamInterceptor(logger hclog.Logger, verbose bool) grpc.StreamServerInterceptor { 64 | return func( 65 | srv interface{}, 66 | ss grpc.ServerStream, 67 | info *grpc.StreamServerInfo, 68 | handler grpc.StreamHandler) error { 69 | start := time.Now() 70 | 71 | // Log the request. 72 | logger.Info(info.FullMethod + " request") 73 | 74 | // Invoke the handler. 75 | err := handler(srv, &logStream{ 76 | ServerStream: ss, 77 | context: hclog.WithContext(ss.Context(), logger), 78 | }) 79 | 80 | // Log the response. 81 | logger.Info(info.FullMethod+" response", 82 | "error", err, 83 | "duration", time.Since(start).String(), 84 | ) 85 | 86 | return err 87 | } 88 | } 89 | 90 | type logStream struct { 91 | grpc.ServerStream 92 | context context.Context 93 | } 94 | 95 | func (s *logStream) Context() context.Context { 96 | return s.context 97 | } 98 | -------------------------------------------------------------------------------- /pkg/server/grpc_log_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "testing" 7 | 8 | "github.com/hashicorp/go-hclog" 9 | "github.com/stretchr/testify/require" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | func TestLogUnaryInterceptor(t *testing.T) { 14 | require := require.New(t) 15 | 16 | var buf bytes.Buffer 17 | logger := hclog.New(&hclog.LoggerOptions{ 18 | Name: "test", 19 | Level: hclog.Debug, 20 | Output: &buf, 21 | IncludeLocation: true, 22 | }) 23 | 24 | f := logUnaryInterceptor(logger, false) 25 | 26 | // Empty context 27 | called := false 28 | resp, err := f(context.Background(), nil, &grpc.UnaryServerInfo{}, 29 | func(ctx context.Context, req interface{}) (interface{}, error) { 30 | called = true 31 | reqLogger := hclog.FromContext(ctx) 32 | require.Equal(reqLogger, logger) 33 | return "hello", nil 34 | }, 35 | ) 36 | require.True(called) 37 | require.Equal("hello", resp) 38 | require.NoError(err) 39 | 40 | called = false 41 | resp, err = f(context.Background(), nil, &grpc.UnaryServerInfo{}, 42 | func(ctx context.Context, req interface{}) (interface{}, error) { 43 | called = true 44 | logger := hclog.FromContext(ctx) 45 | logger.Warn("warning") 46 | return "hello", nil 47 | }, 48 | ) 49 | require.True(called) 50 | require.Equal("hello", resp) 51 | require.NoError(err) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/hashicorp/go-hclog" 8 | hznpb "github.com/hashicorp/horizon/pkg/pb" 9 | "github.com/jinzhu/gorm" 10 | "github.com/oklog/run" 11 | ) 12 | 13 | // hznNamespace is the namespace we use for all our Horizon API calls. 14 | const hznNamespace = "/waypoint" 15 | 16 | // Run initializes and starts the server. This will block until the server 17 | // exits (by cancelling the associated context set with WithContext or due 18 | // to an unrecoverable error). 19 | func Run(opts ...Option) error { 20 | var cfg options 21 | for _, opt := range opts { 22 | opt(&cfg) 23 | } 24 | 25 | // Set defaults 26 | if cfg.Context == nil { 27 | cfg.Context = context.Background() 28 | } 29 | if cfg.Logger == nil { 30 | cfg.Logger = hclog.L() 31 | } 32 | if cfg.Namespace == "" { 33 | cfg.Namespace = hznNamespace 34 | } 35 | 36 | // Setup our run group since we're going to be starting multiple 37 | // goroutines for all the servers that we want to live/die as a group. 38 | var group run.Group 39 | 40 | // We first add an actor that just returns when the context ends. This 41 | // will trigger the rest of the group to end since a group will not exit 42 | // until any of its actors exit. 43 | ctx, cancelCtx := context.WithCancel(cfg.Context) 44 | group.Add(func() error { 45 | <-ctx.Done() 46 | return ctx.Err() 47 | }, func(error) { cancelCtx() }) 48 | 49 | // Setup our gRPC server. 50 | if err := grpcInit(&group, &cfg); err != nil { 51 | return err 52 | } 53 | 54 | // Run! 55 | return group.Run() 56 | } 57 | 58 | // Option configures Run 59 | type Option func(*options) 60 | 61 | // options configure a server and are set by users only using the exported 62 | // Option functions. 63 | type options struct { 64 | // Context is the context to use for the server. When this is cancelled, 65 | // the server will be gracefully shutdown. 66 | Context context.Context 67 | 68 | // Logger is the logger to use. This will default to hclog.L() if not set. 69 | Logger hclog.Logger 70 | 71 | // GRPCListener will setup the gRPC server. If this is nil, then a 72 | // random loopback port will be chosen. The gRPC server must run since it 73 | // serves the HTTP endpoints as well. 74 | GRPCListener net.Listener 75 | 76 | // PostgreSQL DB connection. 77 | DB *gorm.DB 78 | 79 | // Client to Horizon control client 80 | HznControl hznpb.ControlManagementClient 81 | 82 | // Domain to use 83 | Domain string 84 | 85 | // Horizon namespace for all accounts 86 | Namespace string 87 | } 88 | 89 | // WithContext sets the context for the server. When this context is cancelled, 90 | // the server will be shut down. 91 | func WithContext(ctx context.Context) Option { 92 | return func(opts *options) { opts.Context = ctx } 93 | } 94 | 95 | // WithLogger sets the logger. 96 | func WithLogger(log hclog.Logger) Option { 97 | return func(opts *options) { opts.Logger = log } 98 | } 99 | 100 | // WithGRPC sets the GRPC listener. This listener must be closed manually 101 | // by the caller. Prior to closing the listener, it is recommended that you 102 | // cancel the context set with WithContext and wait for Run to return. 103 | func WithGRPC(ln net.Listener) Option { 104 | return func(opts *options) { opts.GRPCListener = ln } 105 | } 106 | 107 | // WithDB sets the DB connection. 108 | func WithDB(db *gorm.DB) Option { 109 | return func(opts *options) { opts.DB = db } 110 | } 111 | 112 | // WithHznControl 113 | func WithHznControl(client hznpb.ControlManagementClient) Option { 114 | return func(opts *options) { opts.HznControl = client } 115 | } 116 | 117 | // WithDomain 118 | func WithDomain(d string) Option { 119 | return func(opts *options) { opts.Domain = d } 120 | } 121 | 122 | // WithNamespace 123 | func WithNamespace(ns string) Option { 124 | return func(opts *options) { opts.Namespace = ns } 125 | } 126 | -------------------------------------------------------------------------------- /pkg/server/service.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/hashicorp/go-hclog" 5 | hznpb "github.com/hashicorp/horizon/pkg/pb" 6 | "github.com/jinzhu/gorm" 7 | 8 | "github.com/hashicorp/waypoint-hzn/pkg/pb" 9 | ) 10 | 11 | // service implements pb.WaypointHznServer. 12 | type service struct { 13 | DB *gorm.DB 14 | Domain string 15 | Namespace string 16 | HznControl hznpb.ControlManagementClient 17 | 18 | Logger hclog.Logger 19 | 20 | // Token public key is derived from the HznControl client on startup 21 | tokenPub []byte 22 | } 23 | 24 | var _ pb.WaypointHznServer = (*service)(nil) 25 | -------------------------------------------------------------------------------- /pkg/server/service_account.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hashicorp/horizon/pkg/dbx" 8 | hznpb "github.com/hashicorp/horizon/pkg/pb" 9 | "google.golang.org/grpc/peer" 10 | 11 | "github.com/hashicorp/waypoint-hzn/pkg/models" 12 | "github.com/hashicorp/waypoint-hzn/pkg/pb" 13 | ) 14 | 15 | var ( 16 | GuestLimits = &hznpb.Account_Limits{ 17 | HttpRequests: 5, // per second 18 | Bandwidth: 1024 / 60.0, // in KB/second 19 | } 20 | ) 21 | 22 | func (s *service) RegisterGuestAccount( 23 | ctx context.Context, 24 | req *pb.RegisterGuestAccountRequest, 25 | ) (*pb.RegisterGuestAccountResponse, error) { 26 | p, _ := peer.FromContext(ctx) 27 | 28 | accountId := hznpb.NewULID() 29 | 30 | s.Logger.Info("creating guest account", 31 | "client-ip", p.Addr.String(), 32 | "account-id", accountId.String(), 33 | "accept-tos", req.AcceptTos, 34 | ) 35 | 36 | if !req.AcceptTos { 37 | return nil, fmt.Errorf("TOS not accepted, rejecting request to create guest account") 38 | } 39 | 40 | _, err := s.HznControl.AddAccount(ctx, &hznpb.AddAccountRequest{ 41 | Account: &hznpb.Account{ 42 | AccountId: accountId, 43 | Namespace: s.Namespace, 44 | }, 45 | Limits: GuestLimits, 46 | }) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | // Register the token with the control server 52 | ctr, err := s.HznControl.CreateToken(ctx, &hznpb.CreateTokenRequest{ 53 | Account: &hznpb.Account{ 54 | AccountId: accountId, 55 | Namespace: s.Namespace, 56 | }, 57 | Capabilities: []hznpb.TokenCapability{ 58 | { 59 | Capability: hznpb.SERVE, 60 | }, 61 | { 62 | Capability: hznpb.CONNECT, 63 | }, 64 | }, 65 | }) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | // Register the account in our database 71 | err = dbx.Check(s.DB.Create(&models.Registration{ 72 | AccountId: accountId.Bytes(), 73 | })) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | return &pb.RegisterGuestAccountResponse{Token: ctr.Token}, nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/server/service_account_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/hashicorp/waypoint-hzn/pkg/pb" 10 | ) 11 | 12 | func TestServiceRegisterGuestAccount(t *testing.T) { 13 | ctx := context.Background() 14 | require := require.New(t) 15 | 16 | data := TestServer(t) 17 | client := data.Client 18 | 19 | resp, err := client.RegisterGuestAccount(ctx, &pb.RegisterGuestAccountRequest{ 20 | ServerId: "A", 21 | AcceptTos: true, 22 | }) 23 | require.NoError(err) 24 | require.NotNil(resp) 25 | require.NotEmpty(resp.Token) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/server/service_hostname.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | empty "github.com/golang/protobuf/ptypes/empty" 9 | "github.com/hashicorp/go-hclog" 10 | "github.com/hashicorp/horizon/pkg/dbx" 11 | hznpb "github.com/hashicorp/horizon/pkg/pb" 12 | petname "github.com/hashicorp/waypoint-hzn/internal/pkg/golang-petname" 13 | "github.com/jinzhu/gorm" 14 | "github.com/mitchellh/mapstructure" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/status" 17 | 18 | "github.com/hashicorp/waypoint-hzn/pkg/models" 19 | "github.com/hashicorp/waypoint-hzn/pkg/pb" 20 | ) 21 | 22 | var ( 23 | // testLabelLinkFailed if set will cause the AddLabelLink to always fail. 24 | // This is used for tests. 25 | testLabelLinkFailed bool 26 | ) 27 | 28 | func (s *service) RegisterHostname( 29 | ctx context.Context, 30 | req *pb.RegisterHostnameRequest, 31 | ) (*pb.RegisterHostnameResponse, error) { 32 | L := hclog.FromContext(ctx) 33 | 34 | // Auth required. 35 | token, err := s.checkAuth(ctx) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | // Validate 41 | if err := req.Validate(); err != nil { 42 | return nil, err 43 | } 44 | 45 | // Parse our labels 46 | var labels hznpb.LabelSet 47 | if err := mapstructure.Decode(req.Labels, &labels); err != nil { 48 | return nil, err 49 | } 50 | 51 | // Get our account registration 52 | var reg models.Registration 53 | if err = dbx.Check( 54 | s.DB.Where("account_id = ?", token.Account().AccountId.Bytes()).First(®), 55 | ); err != nil { 56 | return nil, status.Errorf(codes.PermissionDenied, 57 | "unregistered account") 58 | } 59 | 60 | // Determine the full hostname 61 | var hostname, fqdn string 62 | 63 | trying: 64 | for { 65 | switch v := req.Hostname.(type) { 66 | case *pb.RegisterHostnameRequest_Generate: 67 | hostname = petname.Generate(3, "-") 68 | 69 | if strings.Contains(hostname, "--") { 70 | // extremely odd, but go ahead and just retry 71 | continue trying 72 | } 73 | case *pb.RegisterHostnameRequest_Exact: 74 | hostname = v.Exact 75 | 76 | if strings.Contains(hostname, "--") { 77 | return nil, fmt.Errorf("hostname must not contain a double hyphen") 78 | } 79 | } 80 | 81 | var host models.Hostname 82 | host.RegistrationId = reg.Id 83 | host.Hostname = hostname 84 | host.Labels = labels.AsStringArray() 85 | 86 | if err := dbx.Check(s.DB.Create(&host)); err != nil { 87 | // For now, assume the failure is because of failing the unique 88 | // constraint. If we autogenerated the name, retry, otherwise return 89 | // an error. 90 | if _, ok := req.Hostname.(*pb.RegisterHostnameRequest_Generate); ok { 91 | continue 92 | } 93 | 94 | L.Error("error creating hostname", "error", err) 95 | return nil, fmt.Errorf("requested hostname is not available") 96 | } 97 | 98 | // Add the domain 99 | fqdn = hostname + "." + s.Domain 100 | 101 | break 102 | } 103 | 104 | L.Debug("adding label link", "hostname", fqdn, "target", req.Labels) 105 | _, err = s.HznControl.AddLabelLink(ctx, &hznpb.AddLabelLinkRequest{ 106 | Labels: hznpb.MakeLabels(":hostname", fqdn), 107 | Account: token.Account(), 108 | Target: &labels, 109 | }) 110 | if err == nil && testLabelLinkFailed { 111 | err = fmt.Errorf("forced err by setting testLabelLinkFailed") 112 | } 113 | if err != nil { 114 | // We need to delete our record we created earlier since the 115 | // creation failed. 116 | if derr := dbx.Check(s.DB.Delete( 117 | models.Hostname{}, 118 | "registration_id = ? and hostname = ?", reg.Id, hostname), 119 | ); derr != nil { 120 | // If we fail, the best we can do is make a loud log message 121 | // since we're already returning an error to the user anyways. 122 | L.Error("error deleting hostname after label link failed, dangling hosname", 123 | "hostname", fqdn, 124 | "err", derr) 125 | } 126 | 127 | return nil, err 128 | } 129 | L.Info("added label link", "hostname", fqdn, "target", req.Labels) 130 | 131 | return &pb.RegisterHostnameResponse{ 132 | Hostname: hostname, 133 | Fqdn: fqdn, 134 | }, nil 135 | } 136 | 137 | func (s *service) ListHostnames( 138 | ctx context.Context, 139 | req *pb.ListHostnamesRequest, 140 | ) (*pb.ListHostnamesResponse, error) { 141 | L := hclog.FromContext(ctx) 142 | 143 | // Auth required. 144 | token, err := s.checkAuth(ctx) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | // Validate 150 | if err := req.Validate(); err != nil { 151 | return nil, err 152 | } 153 | 154 | // Get our account registration 155 | var reg models.Registration 156 | if err = dbx.Check( 157 | s.DB.Where("account_id = ?", token.Account().AccountId.Bytes()).First(®), 158 | ); err != nil { 159 | return nil, status.Errorf(codes.PermissionDenied, 160 | "unregistered account") 161 | } 162 | 163 | var hostnames []*models.Hostname 164 | err = dbx.Check(s.DB.Find(&hostnames, "registration_id = ?", reg.Id)) 165 | if err != nil { 166 | if err == gorm.ErrRecordNotFound { 167 | return nil, status.Errorf(codes.NotFound, "unregistered account") 168 | } 169 | 170 | L.Error("error looking up hostnames for account", "registration-id", reg.Id) 171 | return nil, status.Errorf(codes.Internal, "error querying hostnames") 172 | } 173 | 174 | var resp pb.ListHostnamesResponse 175 | for _, h := range hostnames { 176 | var hznlabels hznpb.LabelSet 177 | if err := hznlabels.Scan(h.Labels); err != nil { 178 | return nil, status.Errorf(codes.Internal, "error querying hostnames") 179 | } 180 | 181 | // Parse our labels 182 | var labels pb.LabelSet 183 | if err := mapstructure.Decode(hznlabels, &labels); err != nil { 184 | return nil, err 185 | } 186 | 187 | resp.Hostnames = append(resp.Hostnames, &pb.ListHostnamesResponse_Hostname{ 188 | Hostname: h.Hostname, 189 | Fqdn: h.Hostname + "." + s.Domain, 190 | Labels: &labels, 191 | }) 192 | } 193 | 194 | return &resp, nil 195 | } 196 | 197 | func (s *service) DeleteHostname( 198 | ctx context.Context, 199 | req *pb.DeleteHostnameRequest, 200 | ) (*empty.Empty, error) { 201 | L := hclog.FromContext(ctx) 202 | 203 | // Auth required. 204 | token, err := s.checkAuth(ctx) 205 | if err != nil { 206 | return nil, err 207 | } 208 | 209 | // Validate 210 | if err := req.Validate(); err != nil { 211 | return nil, err 212 | } 213 | 214 | // Get our account registration 215 | var reg models.Registration 216 | if err = dbx.Check( 217 | s.DB.Where("account_id = ?", token.Account().AccountId.Bytes()).First(®), 218 | ); err != nil { 219 | return nil, status.Errorf(codes.PermissionDenied, 220 | "unregistered account") 221 | } 222 | 223 | // Delete from our DB 224 | err = dbx.Check(s.DB.Delete( 225 | models.Hostname{}, 226 | "registration_id = ? and hostname = ?", reg.Id, req.Hostname), 227 | ) 228 | if err != nil { 229 | if err == gorm.ErrRecordNotFound { 230 | return &empty.Empty{}, nil 231 | } 232 | 233 | L.Error("error looking up hostnames for account", "registration-id", reg.Id) 234 | return nil, status.Errorf(codes.Internal, "error deleting hostname") 235 | } 236 | 237 | _, err = s.HznControl.RemoveLabelLink(ctx, &hznpb.RemoveLabelLinkRequest{ 238 | Labels: hznpb.MakeLabels(":hostname", req.Hostname), 239 | Account: token.Account(), 240 | }) 241 | if err != nil { 242 | L.Error("error removing label link", "error", err) 243 | return nil, status.Errorf(codes.Internal, "error deleting hostname") 244 | } 245 | 246 | return &empty.Empty{}, nil 247 | } 248 | -------------------------------------------------------------------------------- /pkg/server/service_hostname_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "testing" 7 | 8 | empty "github.com/golang/protobuf/ptypes/empty" 9 | grpctoken "github.com/hashicorp/horizon/pkg/grpc/token" 10 | "github.com/stretchr/testify/require" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | 15 | "github.com/hashicorp/waypoint-hzn/pkg/pb" 16 | ) 17 | 18 | func TestServiceRegisterHostname(t *testing.T) { 19 | ctx := context.Background() 20 | 21 | t.Run("invalid auth", func(t *testing.T) { 22 | require := require.New(t) 23 | 24 | data := TestServer(t) 25 | client := data.Client 26 | 27 | // Get a hostname 28 | resp, err := client.RegisterHostname(ctx, &pb.RegisterHostnameRequest{ 29 | Labels: &pb.LabelSet{ 30 | Labels: []*pb.Label{ 31 | {Name: "app", Value: "test"}, 32 | }, 33 | }, 34 | }, grpc.PerRPCCredentials(grpctoken.Token("NOPE"))) 35 | require.Error(err) 36 | require.Equal(codes.PermissionDenied, status.Code(err)) 37 | require.Nil(resp) 38 | }) 39 | 40 | t.Run("generated hostname", func(t *testing.T) { 41 | require := require.New(t) 42 | 43 | data := TestServer(t) 44 | client := data.Client 45 | optAuth := TestGuestAccount(t, client) 46 | 47 | // Should have no hostnames 48 | { 49 | resp, err := client.ListHostnames(ctx, &pb.ListHostnamesRequest{}, optAuth) 50 | require.NoError(err) 51 | require.NotNil(resp) 52 | require.Len(resp.Hostnames, 0) 53 | } 54 | 55 | // Get a hostname 56 | resp, err := client.RegisterHostname(ctx, &pb.RegisterHostnameRequest{ 57 | Hostname: &pb.RegisterHostnameRequest_Generate{ 58 | Generate: &empty.Empty{}, 59 | }, 60 | 61 | Labels: &pb.LabelSet{ 62 | Labels: []*pb.Label{ 63 | {Name: "app", Value: "test"}, 64 | }, 65 | }, 66 | }, optAuth) 67 | require.NoError(err) 68 | require.NotNil(resp) 69 | require.NotEmpty(resp.Fqdn) 70 | 71 | // Should show up in the list 72 | { 73 | resp, err := client.ListHostnames(ctx, &pb.ListHostnamesRequest{}, optAuth) 74 | require.NoError(err) 75 | require.NotNil(resp) 76 | require.Len(resp.Hostnames, 1) 77 | } 78 | }) 79 | 80 | t.Run("exact hostname", func(t *testing.T) { 81 | require := require.New(t) 82 | 83 | data := TestServer(t) 84 | client := data.Client 85 | optAuth := TestGuestAccount(t, client) 86 | 87 | // Get a hostname 88 | resp, err := client.RegisterHostname(ctx, &pb.RegisterHostnameRequest{ 89 | Hostname: &pb.RegisterHostnameRequest_Exact{ 90 | Exact: "foo", 91 | }, 92 | 93 | Labels: &pb.LabelSet{ 94 | Labels: []*pb.Label{ 95 | {Name: "app", Value: "test"}, 96 | }, 97 | }, 98 | }, optAuth) 99 | require.NoError(err) 100 | require.NotNil(resp) 101 | require.NotEmpty(resp.Fqdn) 102 | require.True(strings.HasPrefix(resp.Fqdn, "foo.")) 103 | 104 | // Should be able to delete 105 | { 106 | resp, err := client.DeleteHostname(ctx, &pb.DeleteHostnameRequest{ 107 | Hostname: "foo", 108 | }, optAuth) 109 | require.NoError(err) 110 | require.NotNil(resp) 111 | } 112 | 113 | // Should have no hostnames 114 | { 115 | resp, err := client.ListHostnames(ctx, &pb.ListHostnamesRequest{}, optAuth) 116 | require.NoError(err) 117 | require.NotNil(resp) 118 | require.Len(resp.Hostnames, 0) 119 | } 120 | }) 121 | 122 | t.Run("rejects hostnames with double hyphen", func(t *testing.T) { 123 | require := require.New(t) 124 | 125 | data := TestServer(t) 126 | client := data.Client 127 | optAuth := TestGuestAccount(t, client) 128 | 129 | // Get a hostname 130 | _, err := client.RegisterHostname(ctx, &pb.RegisterHostnameRequest{ 131 | Hostname: &pb.RegisterHostnameRequest_Exact{ 132 | Exact: "foo--bar", 133 | }, 134 | 135 | Labels: &pb.LabelSet{ 136 | Labels: []*pb.Label{ 137 | {Name: "app", Value: "test"}, 138 | }, 139 | }, 140 | }, optAuth) 141 | require.Error(err) 142 | }) 143 | 144 | t.Run("context cancellation doesn't dangle hostname", func(t *testing.T) { 145 | require := require.New(t) 146 | 147 | data := TestServer(t) 148 | client := data.Client 149 | optAuth := TestGuestAccount(t, client) 150 | 151 | // Should have no hostnames 152 | { 153 | resp, err := client.ListHostnames(ctx, &pb.ListHostnamesRequest{}, optAuth) 154 | require.NoError(err) 155 | require.NotNil(resp) 156 | require.Len(resp.Hostnames, 0) 157 | } 158 | 159 | // Create an expired context 160 | testLabelLinkFailed = true 161 | defer func() { testLabelLinkFailed = false }() 162 | 163 | // Get a hostname 164 | _, err := client.RegisterHostname(ctx, &pb.RegisterHostnameRequest{ 165 | Hostname: &pb.RegisterHostnameRequest_Generate{ 166 | Generate: &empty.Empty{}, 167 | }, 168 | 169 | Labels: &pb.LabelSet{ 170 | Labels: []*pb.Label{ 171 | {Name: "app", Value: "test"}, 172 | }, 173 | }, 174 | }, optAuth) 175 | require.Error(err) 176 | 177 | // Should not show up in the list 178 | { 179 | resp, err := client.ListHostnames(ctx, &pb.ListHostnamesRequest{}, optAuth) 180 | require.NoError(err) 181 | require.NotNil(resp) 182 | require.Len(resp.Hostnames, 0) 183 | } 184 | }) 185 | } 186 | -------------------------------------------------------------------------------- /pkg/server/testing.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/hashicorp/horizon/pkg/grpc/lz4" 8 | grpctoken "github.com/hashicorp/horizon/pkg/grpc/token" 9 | hznpb "github.com/hashicorp/horizon/pkg/pb" 10 | hzntest "github.com/hashicorp/horizon/pkg/testutils/central" 11 | "github.com/mitchellh/go-testing-interface" 12 | "github.com/stretchr/testify/require" 13 | "google.golang.org/grpc" 14 | 15 | "github.com/hashicorp/waypoint-hzn/internal/testsql" 16 | "github.com/hashicorp/waypoint-hzn/pkg/pb" 17 | ) 18 | 19 | // TestServer starts a server and returns various data such as the client 20 | // for that server. We use t.Cleanup to ensure resources are automatically 21 | // cleaned up. 22 | func TestServer(t testing.T, opts ...Option) *TestServerData { 23 | require := require.New(t) 24 | 25 | // Create the server 26 | var data TestServerData 27 | data.readyCh = make(chan struct{}) 28 | //nolint:all 29 | go Run( 30 | append( 31 | append([]Option{}, opts...), 32 | testWithDefaults(t, &data), 33 | )..., 34 | ) 35 | 36 | // Wait for it to start 37 | <-data.readyCh 38 | 39 | // Connect, this should retry in the case Run is not going yet 40 | conn, err := grpc.DialContext(context.Background(), data.Addr, 41 | grpc.WithBlock(), 42 | grpc.WithInsecure(), 43 | ) 44 | require.NoError(err) 45 | t.Cleanup(func() { conn.Close() }) 46 | data.Client = pb.NewWaypointHznClient(conn) 47 | 48 | return &data 49 | } 50 | 51 | type TestServerData struct { 52 | Addr string 53 | Client pb.WaypointHznClient 54 | Hzn *hzntest.DevSetup 55 | 56 | readyCh chan struct{} 57 | } 58 | 59 | // TestGuestAccount registers a guest account and returns a context that 60 | // can be used with auth information for future API calls. 61 | func TestGuestAccount(t testing.T, client pb.WaypointHznClient) grpc.CallOption { 62 | resp, err := client.RegisterGuestAccount( 63 | context.Background(), &pb.RegisterGuestAccountRequest{ 64 | ServerId: "A", 65 | AcceptTos: true, 66 | }, 67 | ) 68 | require.NoError(t, err) 69 | require.NotEmpty(t, resp.Token) 70 | 71 | return grpc.PerRPCCredentials(grpctoken.Token(resp.Token)) 72 | } 73 | 74 | func testWithDefaults(t testing.T, data *TestServerData) Option { 75 | return func(opts *options) { 76 | defer close(data.readyCh) 77 | 78 | if opts.Domain == "" { 79 | opts.Domain = "waypoint-hzn.localhost" 80 | } 81 | if opts.Namespace == "" { 82 | opts.Namespace = hznNamespace 83 | } 84 | 85 | testWithContext(t, opts) 86 | testWithListener(t, opts, data) 87 | testWithDB(t, opts, data) 88 | testWithHzn(t, opts, data) 89 | } 90 | } 91 | 92 | func testWithContext(t testing.T, opts *options) { 93 | // Setup the context 94 | if opts.Context == nil { 95 | opts.Context = context.Background() 96 | } 97 | 98 | // We need the context to be cancellable 99 | ctx, cancel := context.WithCancel(opts.Context) 100 | opts.Context = ctx 101 | t.Cleanup(func() { cancel() }) 102 | } 103 | 104 | func testWithListener(t testing.T, opts *options, data *TestServerData) { 105 | if opts.GRPCListener == nil { 106 | // Listen on a random port 107 | ln, err := net.Listen("tcp", "127.0.0.1:") 108 | require.NoError(t, err) 109 | t.Cleanup(func() { ln.Close() }) 110 | opts.GRPCListener = ln 111 | } 112 | 113 | data.Addr = opts.GRPCListener.Addr().String() 114 | } 115 | 116 | func testWithDB(t testing.T, opts *options, data *TestServerData) { 117 | if opts.DB == nil { 118 | opts.DB = testsql.TestPostgresDB(t, "waypoint_hzn_test") 119 | } 120 | } 121 | 122 | func testWithHzn(t testing.T, opts *options, data *TestServerData) { 123 | if opts.HznControl == nil { 124 | // Create the test server. On test end we close the channel which quits 125 | // the Horizon test server. 126 | setupCh := make(chan *hzntest.DevSetup, 1) 127 | closeCh := make(chan struct{}) 128 | t.Cleanup(func() { close(closeCh) }) 129 | go hzntest.Dev(t, func(setup *hzntest.DevSetup) { 130 | setupCh <- setup 131 | <-closeCh 132 | }) 133 | data.Hzn = <-setupCh 134 | 135 | // We need a management token for our namespace 136 | token, err := data.Hzn.ControlServer.GetManagementToken(context.Background(), opts.Namespace) 137 | require.NoError(t, err) 138 | 139 | // New connection that uses this token 140 | conn, err := grpc.Dial(data.Hzn.ServerAddr, 141 | grpc.WithInsecure(), 142 | grpc.WithPerRPCCredentials(grpctoken.Token(token)), 143 | grpc.WithDefaultCallOptions(grpc.UseCompressor(lz4.Name)), 144 | ) 145 | require.NoError(t, err) 146 | t.Cleanup(func() { conn.Close() }) 147 | opts.HznControl = hznpb.NewControlManagementClient(conn) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /proto/defs/validate.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package validate; 3 | 4 | option go_package = "github.com/envoyproxy/protoc-gen-validate/validate"; 5 | option java_package = "io.envoyproxy.pgv.validate"; 6 | 7 | import "google/protobuf/descriptor.proto"; 8 | import "google/protobuf/duration.proto"; 9 | import "google/protobuf/timestamp.proto"; 10 | 11 | // Validation rules applied at the message level 12 | extend google.protobuf.MessageOptions { 13 | // Disabled nullifies any validation rules for this message, including any 14 | // message fields associated with it that do support validation. 15 | optional bool disabled = 1071; 16 | } 17 | 18 | // Validation rules applied at the oneof level 19 | extend google.protobuf.OneofOptions { 20 | // Required ensures that exactly one the field options in a oneof is set; 21 | // validation fails if no fields in the oneof are set. 22 | optional bool required = 1071; 23 | } 24 | 25 | // Validation rules applied at the field level 26 | extend google.protobuf.FieldOptions { 27 | // Rules specify the validations to be performed on this field. By default, 28 | // no validation is performed against a field. 29 | optional FieldRules rules = 1071; 30 | } 31 | 32 | // FieldRules encapsulates the rules for each type of field. Depending on the 33 | // field, the correct set should be used to ensure proper validations. 34 | message FieldRules { 35 | optional MessageRules message = 17; 36 | oneof type { 37 | // Scalar Field Types 38 | FloatRules float = 1; 39 | DoubleRules double = 2; 40 | Int32Rules int32 = 3; 41 | Int64Rules int64 = 4; 42 | UInt32Rules uint32 = 5; 43 | UInt64Rules uint64 = 6; 44 | SInt32Rules sint32 = 7; 45 | SInt64Rules sint64 = 8; 46 | Fixed32Rules fixed32 = 9; 47 | Fixed64Rules fixed64 = 10; 48 | SFixed32Rules sfixed32 = 11; 49 | SFixed64Rules sfixed64 = 12; 50 | BoolRules bool = 13; 51 | StringRules string = 14; 52 | BytesRules bytes = 15; 53 | 54 | // Complex Field Types 55 | EnumRules enum = 16; 56 | RepeatedRules repeated = 18; 57 | MapRules map = 19; 58 | 59 | // Well-Known Field Types 60 | AnyRules any = 20; 61 | DurationRules duration = 21; 62 | TimestampRules timestamp = 22; 63 | } 64 | } 65 | 66 | // FloatRules describes the constraints applied to `float` values 67 | message FloatRules { 68 | // Const specifies that this field must be exactly the specified value 69 | optional float const = 1; 70 | 71 | // Lt specifies that this field must be less than the specified value, 72 | // exclusive 73 | optional float lt = 2; 74 | 75 | // Lte specifies that this field must be less than or equal to the 76 | // specified value, inclusive 77 | optional float lte = 3; 78 | 79 | // Gt specifies that this field must be greater than the specified value, 80 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 81 | // range is reversed. 82 | optional float gt = 4; 83 | 84 | // Gte specifies that this field must be greater than or equal to the 85 | // specified value, inclusive. If the value of Gte is larger than a 86 | // specified Lt or Lte, the range is reversed. 87 | optional float gte = 5; 88 | 89 | // In specifies that this field must be equal to one of the specified 90 | // values 91 | repeated float in = 6; 92 | 93 | // NotIn specifies that this field cannot be equal to one of the specified 94 | // values 95 | repeated float not_in = 7; 96 | } 97 | 98 | // DoubleRules describes the constraints applied to `double` values 99 | message DoubleRules { 100 | // Const specifies that this field must be exactly the specified value 101 | optional double const = 1; 102 | 103 | // Lt specifies that this field must be less than the specified value, 104 | // exclusive 105 | optional double lt = 2; 106 | 107 | // Lte specifies that this field must be less than or equal to the 108 | // specified value, inclusive 109 | optional double lte = 3; 110 | 111 | // Gt specifies that this field must be greater than the specified value, 112 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 113 | // range is reversed. 114 | optional double gt = 4; 115 | 116 | // Gte specifies that this field must be greater than or equal to the 117 | // specified value, inclusive. If the value of Gte is larger than a 118 | // specified Lt or Lte, the range is reversed. 119 | optional double gte = 5; 120 | 121 | // In specifies that this field must be equal to one of the specified 122 | // values 123 | repeated double in = 6; 124 | 125 | // NotIn specifies that this field cannot be equal to one of the specified 126 | // values 127 | repeated double not_in = 7; 128 | } 129 | 130 | // Int32Rules describes the constraints applied to `int32` values 131 | message Int32Rules { 132 | // Const specifies that this field must be exactly the specified value 133 | optional int32 const = 1; 134 | 135 | // Lt specifies that this field must be less than the specified value, 136 | // exclusive 137 | optional int32 lt = 2; 138 | 139 | // Lte specifies that this field must be less than or equal to the 140 | // specified value, inclusive 141 | optional int32 lte = 3; 142 | 143 | // Gt specifies that this field must be greater than the specified value, 144 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 145 | // range is reversed. 146 | optional int32 gt = 4; 147 | 148 | // Gte specifies that this field must be greater than or equal to the 149 | // specified value, inclusive. If the value of Gte is larger than a 150 | // specified Lt or Lte, the range is reversed. 151 | optional int32 gte = 5; 152 | 153 | // In specifies that this field must be equal to one of the specified 154 | // values 155 | repeated int32 in = 6; 156 | 157 | // NotIn specifies that this field cannot be equal to one of the specified 158 | // values 159 | repeated int32 not_in = 7; 160 | } 161 | 162 | // Int64Rules describes the constraints applied to `int64` values 163 | message Int64Rules { 164 | // Const specifies that this field must be exactly the specified value 165 | optional int64 const = 1; 166 | 167 | // Lt specifies that this field must be less than the specified value, 168 | // exclusive 169 | optional int64 lt = 2; 170 | 171 | // Lte specifies that this field must be less than or equal to the 172 | // specified value, inclusive 173 | optional int64 lte = 3; 174 | 175 | // Gt specifies that this field must be greater than the specified value, 176 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 177 | // range is reversed. 178 | optional int64 gt = 4; 179 | 180 | // Gte specifies that this field must be greater than or equal to the 181 | // specified value, inclusive. If the value of Gte is larger than a 182 | // specified Lt or Lte, the range is reversed. 183 | optional int64 gte = 5; 184 | 185 | // In specifies that this field must be equal to one of the specified 186 | // values 187 | repeated int64 in = 6; 188 | 189 | // NotIn specifies that this field cannot be equal to one of the specified 190 | // values 191 | repeated int64 not_in = 7; 192 | } 193 | 194 | // UInt32Rules describes the constraints applied to `uint32` values 195 | message UInt32Rules { 196 | // Const specifies that this field must be exactly the specified value 197 | optional uint32 const = 1; 198 | 199 | // Lt specifies that this field must be less than the specified value, 200 | // exclusive 201 | optional uint32 lt = 2; 202 | 203 | // Lte specifies that this field must be less than or equal to the 204 | // specified value, inclusive 205 | optional uint32 lte = 3; 206 | 207 | // Gt specifies that this field must be greater than the specified value, 208 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 209 | // range is reversed. 210 | optional uint32 gt = 4; 211 | 212 | // Gte specifies that this field must be greater than or equal to the 213 | // specified value, inclusive. If the value of Gte is larger than a 214 | // specified Lt or Lte, the range is reversed. 215 | optional uint32 gte = 5; 216 | 217 | // In specifies that this field must be equal to one of the specified 218 | // values 219 | repeated uint32 in = 6; 220 | 221 | // NotIn specifies that this field cannot be equal to one of the specified 222 | // values 223 | repeated uint32 not_in = 7; 224 | } 225 | 226 | // UInt64Rules describes the constraints applied to `uint64` values 227 | message UInt64Rules { 228 | // Const specifies that this field must be exactly the specified value 229 | optional uint64 const = 1; 230 | 231 | // Lt specifies that this field must be less than the specified value, 232 | // exclusive 233 | optional uint64 lt = 2; 234 | 235 | // Lte specifies that this field must be less than or equal to the 236 | // specified value, inclusive 237 | optional uint64 lte = 3; 238 | 239 | // Gt specifies that this field must be greater than the specified value, 240 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 241 | // range is reversed. 242 | optional uint64 gt = 4; 243 | 244 | // Gte specifies that this field must be greater than or equal to the 245 | // specified value, inclusive. If the value of Gte is larger than a 246 | // specified Lt or Lte, the range is reversed. 247 | optional uint64 gte = 5; 248 | 249 | // In specifies that this field must be equal to one of the specified 250 | // values 251 | repeated uint64 in = 6; 252 | 253 | // NotIn specifies that this field cannot be equal to one of the specified 254 | // values 255 | repeated uint64 not_in = 7; 256 | } 257 | 258 | // SInt32Rules describes the constraints applied to `sint32` values 259 | message SInt32Rules { 260 | // Const specifies that this field must be exactly the specified value 261 | optional sint32 const = 1; 262 | 263 | // Lt specifies that this field must be less than the specified value, 264 | // exclusive 265 | optional sint32 lt = 2; 266 | 267 | // Lte specifies that this field must be less than or equal to the 268 | // specified value, inclusive 269 | optional sint32 lte = 3; 270 | 271 | // Gt specifies that this field must be greater than the specified value, 272 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 273 | // range is reversed. 274 | optional sint32 gt = 4; 275 | 276 | // Gte specifies that this field must be greater than or equal to the 277 | // specified value, inclusive. If the value of Gte is larger than a 278 | // specified Lt or Lte, the range is reversed. 279 | optional sint32 gte = 5; 280 | 281 | // In specifies that this field must be equal to one of the specified 282 | // values 283 | repeated sint32 in = 6; 284 | 285 | // NotIn specifies that this field cannot be equal to one of the specified 286 | // values 287 | repeated sint32 not_in = 7; 288 | } 289 | 290 | // SInt64Rules describes the constraints applied to `sint64` values 291 | message SInt64Rules { 292 | // Const specifies that this field must be exactly the specified value 293 | optional sint64 const = 1; 294 | 295 | // Lt specifies that this field must be less than the specified value, 296 | // exclusive 297 | optional sint64 lt = 2; 298 | 299 | // Lte specifies that this field must be less than or equal to the 300 | // specified value, inclusive 301 | optional sint64 lte = 3; 302 | 303 | // Gt specifies that this field must be greater than the specified value, 304 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 305 | // range is reversed. 306 | optional sint64 gt = 4; 307 | 308 | // Gte specifies that this field must be greater than or equal to the 309 | // specified value, inclusive. If the value of Gte is larger than a 310 | // specified Lt or Lte, the range is reversed. 311 | optional sint64 gte = 5; 312 | 313 | // In specifies that this field must be equal to one of the specified 314 | // values 315 | repeated sint64 in = 6; 316 | 317 | // NotIn specifies that this field cannot be equal to one of the specified 318 | // values 319 | repeated sint64 not_in = 7; 320 | } 321 | 322 | // Fixed32Rules describes the constraints applied to `fixed32` values 323 | message Fixed32Rules { 324 | // Const specifies that this field must be exactly the specified value 325 | optional fixed32 const = 1; 326 | 327 | // Lt specifies that this field must be less than the specified value, 328 | // exclusive 329 | optional fixed32 lt = 2; 330 | 331 | // Lte specifies that this field must be less than or equal to the 332 | // specified value, inclusive 333 | optional fixed32 lte = 3; 334 | 335 | // Gt specifies that this field must be greater than the specified value, 336 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 337 | // range is reversed. 338 | optional fixed32 gt = 4; 339 | 340 | // Gte specifies that this field must be greater than or equal to the 341 | // specified value, inclusive. If the value of Gte is larger than a 342 | // specified Lt or Lte, the range is reversed. 343 | optional fixed32 gte = 5; 344 | 345 | // In specifies that this field must be equal to one of the specified 346 | // values 347 | repeated fixed32 in = 6; 348 | 349 | // NotIn specifies that this field cannot be equal to one of the specified 350 | // values 351 | repeated fixed32 not_in = 7; 352 | } 353 | 354 | // Fixed64Rules describes the constraints applied to `fixed64` values 355 | message Fixed64Rules { 356 | // Const specifies that this field must be exactly the specified value 357 | optional fixed64 const = 1; 358 | 359 | // Lt specifies that this field must be less than the specified value, 360 | // exclusive 361 | optional fixed64 lt = 2; 362 | 363 | // Lte specifies that this field must be less than or equal to the 364 | // specified value, inclusive 365 | optional fixed64 lte = 3; 366 | 367 | // Gt specifies that this field must be greater than the specified value, 368 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 369 | // range is reversed. 370 | optional fixed64 gt = 4; 371 | 372 | // Gte specifies that this field must be greater than or equal to the 373 | // specified value, inclusive. If the value of Gte is larger than a 374 | // specified Lt or Lte, the range is reversed. 375 | optional fixed64 gte = 5; 376 | 377 | // In specifies that this field must be equal to one of the specified 378 | // values 379 | repeated fixed64 in = 6; 380 | 381 | // NotIn specifies that this field cannot be equal to one of the specified 382 | // values 383 | repeated fixed64 not_in = 7; 384 | } 385 | 386 | // SFixed32Rules describes the constraints applied to `sfixed32` values 387 | message SFixed32Rules { 388 | // Const specifies that this field must be exactly the specified value 389 | optional sfixed32 const = 1; 390 | 391 | // Lt specifies that this field must be less than the specified value, 392 | // exclusive 393 | optional sfixed32 lt = 2; 394 | 395 | // Lte specifies that this field must be less than or equal to the 396 | // specified value, inclusive 397 | optional sfixed32 lte = 3; 398 | 399 | // Gt specifies that this field must be greater than the specified value, 400 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 401 | // range is reversed. 402 | optional sfixed32 gt = 4; 403 | 404 | // Gte specifies that this field must be greater than or equal to the 405 | // specified value, inclusive. If the value of Gte is larger than a 406 | // specified Lt or Lte, the range is reversed. 407 | optional sfixed32 gte = 5; 408 | 409 | // In specifies that this field must be equal to one of the specified 410 | // values 411 | repeated sfixed32 in = 6; 412 | 413 | // NotIn specifies that this field cannot be equal to one of the specified 414 | // values 415 | repeated sfixed32 not_in = 7; 416 | } 417 | 418 | // SFixed64Rules describes the constraints applied to `sfixed64` values 419 | message SFixed64Rules { 420 | // Const specifies that this field must be exactly the specified value 421 | optional sfixed64 const = 1; 422 | 423 | // Lt specifies that this field must be less than the specified value, 424 | // exclusive 425 | optional sfixed64 lt = 2; 426 | 427 | // Lte specifies that this field must be less than or equal to the 428 | // specified value, inclusive 429 | optional sfixed64 lte = 3; 430 | 431 | // Gt specifies that this field must be greater than the specified value, 432 | // exclusive. If the value of Gt is larger than a specified Lt or Lte, the 433 | // range is reversed. 434 | optional sfixed64 gt = 4; 435 | 436 | // Gte specifies that this field must be greater than or equal to the 437 | // specified value, inclusive. If the value of Gte is larger than a 438 | // specified Lt or Lte, the range is reversed. 439 | optional sfixed64 gte = 5; 440 | 441 | // In specifies that this field must be equal to one of the specified 442 | // values 443 | repeated sfixed64 in = 6; 444 | 445 | // NotIn specifies that this field cannot be equal to one of the specified 446 | // values 447 | repeated sfixed64 not_in = 7; 448 | } 449 | 450 | // BoolRules describes the constraints applied to `bool` values 451 | message BoolRules { 452 | // Const specifies that this field must be exactly the specified value 453 | optional bool const = 1; 454 | } 455 | 456 | // StringRules describe the constraints applied to `string` values 457 | message StringRules { 458 | // Const specifies that this field must be exactly the specified value 459 | optional string const = 1; 460 | 461 | // Len specifies that this field must be the specified number of 462 | // characters (Unicode code points). Note that the number of 463 | // characters may differ from the number of bytes in the string. 464 | optional uint64 len = 19; 465 | 466 | // MinLen specifies that this field must be the specified number of 467 | // characters (Unicode code points) at a minimum. Note that the number of 468 | // characters may differ from the number of bytes in the string. 469 | optional uint64 min_len = 2; 470 | 471 | // MaxLen specifies that this field must be the specified number of 472 | // characters (Unicode code points) at a maximum. Note that the number of 473 | // characters may differ from the number of bytes in the string. 474 | optional uint64 max_len = 3; 475 | 476 | // LenBytes specifies that this field must be the specified number of bytes 477 | // at a minimum 478 | optional uint64 len_bytes = 20; 479 | 480 | // MinBytes specifies that this field must be the specified number of bytes 481 | // at a minimum 482 | optional uint64 min_bytes = 4; 483 | 484 | // MaxBytes specifies that this field must be the specified number of bytes 485 | // at a maximum 486 | optional uint64 max_bytes = 5; 487 | 488 | // Pattern specifes that this field must match against the specified 489 | // regular expression (RE2 syntax). The included expression should elide 490 | // any delimiters. 491 | optional string pattern = 6; 492 | 493 | // Prefix specifies that this field must have the specified substring at 494 | // the beginning of the string. 495 | optional string prefix = 7; 496 | 497 | // Suffix specifies that this field must have the specified substring at 498 | // the end of the string. 499 | optional string suffix = 8; 500 | 501 | // Contains specifies that this field must have the specified substring 502 | // anywhere in the string. 503 | optional string contains = 9; 504 | 505 | // NotContains specifies that this field cannot have the specified substring 506 | // anywhere in the string. 507 | optional string not_contains = 23; 508 | 509 | // In specifies that this field must be equal to one of the specified 510 | // values 511 | repeated string in = 10; 512 | 513 | // NotIn specifies that this field cannot be equal to one of the specified 514 | // values 515 | repeated string not_in = 11; 516 | 517 | // WellKnown rules provide advanced constraints against common string 518 | // patterns 519 | oneof well_known { 520 | // Email specifies that the field must be a valid email address as 521 | // defined by RFC 5322 522 | bool email = 12; 523 | 524 | // Hostname specifies that the field must be a valid hostname as 525 | // defined by RFC 1034. This constraint does not support 526 | // internationalized domain names (IDNs). 527 | bool hostname = 13; 528 | 529 | // Ip specifies that the field must be a valid IP (v4 or v6) address. 530 | // Valid IPv6 addresses should not include surrounding square brackets. 531 | bool ip = 14; 532 | 533 | // Ipv4 specifies that the field must be a valid IPv4 address. 534 | bool ipv4 = 15; 535 | 536 | // Ipv6 specifies that the field must be a valid IPv6 address. Valid 537 | // IPv6 addresses should not include surrounding square brackets. 538 | bool ipv6 = 16; 539 | 540 | // Uri specifies that the field must be a valid, absolute URI as defined 541 | // by RFC 3986 542 | bool uri = 17; 543 | 544 | // UriRef specifies that the field must be a valid URI as defined by RFC 545 | // 3986 and may be relative or absolute. 546 | bool uri_ref = 18; 547 | 548 | // Address specifies that the field must be either a valid hostname as 549 | // defined by RFC 1034 (which does not support internationalized domain 550 | // names or IDNs), or it can be a valid IP (v4 or v6). 551 | bool address = 21; 552 | 553 | // Uuid specifies that the field must be a valid UUID as defined by 554 | // RFC 4122 555 | bool uuid = 22; 556 | 557 | // WellKnownRegex specifies a common well known pattern defined as a regex. 558 | KnownRegex well_known_regex = 24; 559 | } 560 | 561 | // This applies to regexes HTTP_HEADER_NAME and HTTP_HEADER_VALUE to enable 562 | // strict header validation. 563 | // By default, this is true, and HTTP header validations are RFC-compliant. 564 | // Setting to false will enable a looser validations that only disallows 565 | // \r\n\0 characters, which can be used to bypass header matching rules. 566 | optional bool strict = 25 [default = true]; 567 | } 568 | 569 | // WellKnownRegex contain some well-known patterns. 570 | enum KnownRegex { 571 | UNKNOWN = 0; 572 | 573 | // HTTP header name as defined by RFC 7230. 574 | HTTP_HEADER_NAME = 1; 575 | 576 | // HTTP header value as defined by RFC 7230. 577 | HTTP_HEADER_VALUE = 2; 578 | } 579 | 580 | // BytesRules describe the constraints applied to `bytes` values 581 | message BytesRules { 582 | // Const specifies that this field must be exactly the specified value 583 | optional bytes const = 1; 584 | 585 | // Len specifies that this field must be the specified number of bytes 586 | optional uint64 len = 13; 587 | 588 | // MinLen specifies that this field must be the specified number of bytes 589 | // at a minimum 590 | optional uint64 min_len = 2; 591 | 592 | // MaxLen specifies that this field must be the specified number of bytes 593 | // at a maximum 594 | optional uint64 max_len = 3; 595 | 596 | // Pattern specifes that this field must match against the specified 597 | // regular expression (RE2 syntax). The included expression should elide 598 | // any delimiters. 599 | optional string pattern = 4; 600 | 601 | // Prefix specifies that this field must have the specified bytes at the 602 | // beginning of the string. 603 | optional bytes prefix = 5; 604 | 605 | // Suffix specifies that this field must have the specified bytes at the 606 | // end of the string. 607 | optional bytes suffix = 6; 608 | 609 | // Contains specifies that this field must have the specified bytes 610 | // anywhere in the string. 611 | optional bytes contains = 7; 612 | 613 | // In specifies that this field must be equal to one of the specified 614 | // values 615 | repeated bytes in = 8; 616 | 617 | // NotIn specifies that this field cannot be equal to one of the specified 618 | // values 619 | repeated bytes not_in = 9; 620 | 621 | // WellKnown rules provide advanced constraints against common byte 622 | // patterns 623 | oneof well_known { 624 | // Ip specifies that the field must be a valid IP (v4 or v6) address in 625 | // byte format 626 | bool ip = 10; 627 | 628 | // Ipv4 specifies that the field must be a valid IPv4 address in byte 629 | // format 630 | bool ipv4 = 11; 631 | 632 | // Ipv6 specifies that the field must be a valid IPv6 address in byte 633 | // format 634 | bool ipv6 = 12; 635 | } 636 | } 637 | 638 | // EnumRules describe the constraints applied to enum values 639 | message EnumRules { 640 | // Const specifies that this field must be exactly the specified value 641 | optional int32 const = 1; 642 | 643 | // DefinedOnly specifies that this field must be only one of the defined 644 | // values for this enum, failing on any undefined value. 645 | optional bool defined_only = 2; 646 | 647 | // In specifies that this field must be equal to one of the specified 648 | // values 649 | repeated int32 in = 3; 650 | 651 | // NotIn specifies that this field cannot be equal to one of the specified 652 | // values 653 | repeated int32 not_in = 4; 654 | } 655 | 656 | // MessageRules describe the constraints applied to embedded message values. 657 | // For message-type fields, validation is performed recursively. 658 | message MessageRules { 659 | // Skip specifies that the validation rules of this field should not be 660 | // evaluated 661 | optional bool skip = 1; 662 | 663 | // Required specifies that this field must be set 664 | optional bool required = 2; 665 | } 666 | 667 | // RepeatedRules describe the constraints applied to `repeated` values 668 | message RepeatedRules { 669 | // MinItems specifies that this field must have the specified number of 670 | // items at a minimum 671 | optional uint64 min_items = 1; 672 | 673 | // MaxItems specifies that this field must have the specified number of 674 | // items at a maximum 675 | optional uint64 max_items = 2; 676 | 677 | // Unique specifies that all elements in this field must be unique. This 678 | // contraint is only applicable to scalar and enum types (messages are not 679 | // supported). 680 | optional bool unique = 3; 681 | 682 | // Items specifies the contraints to be applied to each item in the field. 683 | // Repeated message fields will still execute validation against each item 684 | // unless skip is specified here. 685 | optional FieldRules items = 4; 686 | } 687 | 688 | // MapRules describe the constraints applied to `map` values 689 | message MapRules { 690 | // MinPairs specifies that this field must have the specified number of 691 | // KVs at a minimum 692 | optional uint64 min_pairs = 1; 693 | 694 | // MaxPairs specifies that this field must have the specified number of 695 | // KVs at a maximum 696 | optional uint64 max_pairs = 2; 697 | 698 | // NoSparse specifies values in this field cannot be unset. This only 699 | // applies to map's with message value types. 700 | optional bool no_sparse = 3; 701 | 702 | // Keys specifies the constraints to be applied to each key in the field. 703 | optional FieldRules keys = 4; 704 | 705 | // Values specifies the constraints to be applied to the value of each key 706 | // in the field. Message values will still have their validations evaluated 707 | // unless skip is specified here. 708 | optional FieldRules values = 5; 709 | } 710 | 711 | // AnyRules describe constraints applied exclusively to the 712 | // `google.protobuf.Any` well-known type 713 | message AnyRules { 714 | // Required specifies that this field must be set 715 | optional bool required = 1; 716 | 717 | // In specifies that this field's `type_url` must be equal to one of the 718 | // specified values. 719 | repeated string in = 2; 720 | 721 | // NotIn specifies that this field's `type_url` must not be equal to any of 722 | // the specified values. 723 | repeated string not_in = 3; 724 | } 725 | 726 | // DurationRules describe the constraints applied exclusively to the 727 | // `google.protobuf.Duration` well-known type 728 | message DurationRules { 729 | // Required specifies that this field must be set 730 | optional bool required = 1; 731 | 732 | // Const specifies that this field must be exactly the specified value 733 | optional google.protobuf.Duration const = 2; 734 | 735 | // Lt specifies that this field must be less than the specified value, 736 | // exclusive 737 | optional google.protobuf.Duration lt = 3; 738 | 739 | // Lt specifies that this field must be less than the specified value, 740 | // inclusive 741 | optional google.protobuf.Duration lte = 4; 742 | 743 | // Gt specifies that this field must be greater than the specified value, 744 | // exclusive 745 | optional google.protobuf.Duration gt = 5; 746 | 747 | // Gte specifies that this field must be greater than the specified value, 748 | // inclusive 749 | optional google.protobuf.Duration gte = 6; 750 | 751 | // In specifies that this field must be equal to one of the specified 752 | // values 753 | repeated google.protobuf.Duration in = 7; 754 | 755 | // NotIn specifies that this field cannot be equal to one of the specified 756 | // values 757 | repeated google.protobuf.Duration not_in = 8; 758 | } 759 | 760 | // TimestampRules describe the constraints applied exclusively to the 761 | // `google.protobuf.Timestamp` well-known type 762 | message TimestampRules { 763 | // Required specifies that this field must be set 764 | optional bool required = 1; 765 | 766 | // Const specifies that this field must be exactly the specified value 767 | optional google.protobuf.Timestamp const = 2; 768 | 769 | // Lt specifies that this field must be less than the specified value, 770 | // exclusive 771 | optional google.protobuf.Timestamp lt = 3; 772 | 773 | // Lte specifies that this field must be less than the specified value, 774 | // inclusive 775 | optional google.protobuf.Timestamp lte = 4; 776 | 777 | // Gt specifies that this field must be greater than the specified value, 778 | // exclusive 779 | optional google.protobuf.Timestamp gt = 5; 780 | 781 | // Gte specifies that this field must be greater than the specified value, 782 | // inclusive 783 | optional google.protobuf.Timestamp gte = 6; 784 | 785 | // LtNow specifies that this must be less than the current time. LtNow 786 | // can only be used with the Within rule. 787 | optional bool lt_now = 7; 788 | 789 | // GtNow specifies that this must be greater than the current time. GtNow 790 | // can only be used with the Within rule. 791 | optional bool gt_now = 8; 792 | 793 | // Within specifies that this field must be within this duration of the 794 | // current time. This constraint can be used alone or with the LtNow and 795 | // GtNow rules. 796 | optional google.protobuf.Duration within = 9; 797 | } 798 | -------------------------------------------------------------------------------- /proto/server.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hashicorp.waypoint_hzn; 4 | 5 | option go_package = "pb"; 6 | 7 | import "google/protobuf/empty.proto"; 8 | import "defs/validate.proto"; 9 | 10 | // WaypointHzn is the gRPC service frontend for the more private direct 11 | // Horizon API. This exposes higher level operations that Waypoint uses 12 | // to register URLs with Horizon. 13 | service WaypointHzn { 14 | rpc RegisterGuestAccount(RegisterGuestAccountRequest) returns (RegisterGuestAccountResponse); 15 | rpc RegisterHostname(RegisterHostnameRequest) returns (RegisterHostnameResponse); 16 | rpc ListHostnames(ListHostnamesRequest) returns (ListHostnamesResponse); 17 | rpc DeleteHostname(DeleteHostnameRequest) returns (google.protobuf.Empty); 18 | } 19 | 20 | /******************************************************************** 21 | * Shared Messages 22 | ********************************************************************/ 23 | 24 | message Label { 25 | string name = 1; 26 | string value = 2; 27 | } 28 | 29 | message LabelSet { 30 | repeated Label labels = 1 [(validate.rules).repeated.min_items = 1]; 31 | } 32 | 33 | /******************************************************************** 34 | * Account RPCs 35 | ********************************************************************/ 36 | 37 | message RegisterGuestAccountRequest { 38 | // server ID is the unique ULID of the Waypoint server requesting a 39 | // guest account. If this server already has a guest account registered, 40 | // the same token will be returned. 41 | string server_id = 1; 42 | 43 | // Indicates that the user accepted the TOS to access Horizon 44 | bool accept_tos = 2; 45 | } 46 | 47 | message RegisterGuestAccountResponse { 48 | // API token to use for protected endpoints. 49 | string token = 1; 50 | } 51 | 52 | /******************************************************************** 53 | * Hostname RPCs 54 | ********************************************************************/ 55 | 56 | message RegisterHostnameRequest { 57 | // hostname to register 58 | oneof hostname { 59 | option (validate.required) = true; 60 | 61 | // auto-generate a hostname 62 | google.protobuf.Empty generate = 1; 63 | 64 | // specific hostname request 65 | string exact = 2 [(validate.rules).string = { 66 | min_len: 3, 67 | pattern: "\\w+[\\w\\d-]*", 68 | not_in: ["admin", "api", "blog", "hzn", "horizon", "waypoint"], 69 | }]; 70 | } 71 | 72 | // labels to link this hostname to. 73 | LabelSet labels = 3 [(validate.rules).message.required = true]; 74 | } 75 | 76 | message RegisterHostnameResponse { 77 | string hostname = 1; 78 | string fqdn = 2; 79 | } 80 | 81 | message ListHostnamesRequest {} 82 | 83 | message ListHostnamesResponse { 84 | repeated Hostname hostnames = 1; 85 | 86 | message Hostname { 87 | string hostname = 1; 88 | string fqdn = 2; 89 | LabelSet labels = 3; 90 | } 91 | } 92 | 93 | message DeleteHostnameRequest { 94 | string hostname = 1; 95 | } 96 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgsPath ? }: 2 | 3 | let 4 | # First we setup our overlays. These are overrides of the official nix packages. 5 | # We do this to pin the versions we want to use of the software that is in 6 | # the official nixpkgs repo. 7 | pkgs = import pkgsPath { 8 | overlays = [(self: super: { 9 | 10 | go = super.go.overrideAttrs ( old: rec { 11 | version = "1.14.4"; 12 | src = super.fetchurl { 13 | url = "https://dl.google.com/go/go${version}.src.tar.gz"; 14 | sha256 = "1105qk2l4kfy1ki9n9gh8j4gfqrfgfwapa1fp38hih9aphxsy4bh"; 15 | }; 16 | }); 17 | 18 | go-protobuf = super.go-protobuf.overrideAttrs ( old: rec { 19 | version = "1.3.5"; 20 | src = super.fetchFromGitHub { 21 | owner = "golang"; 22 | repo = "protobuf"; 23 | rev = "v${version}"; 24 | sha256 = "1gkd1942vk9n8kfzdwy1iil6wgvlwjq7a3y5jc49ck4lz9rhmgkq"; 25 | }; 26 | 27 | modSha256 = "0jjjj9z1dhilhpc8pq4154czrb79z9cm044jvn75kxcjv6v5l2m5"; 28 | }); 29 | 30 | })]; 31 | }; 32 | in with pkgs; let 33 | protoc-gen-validate = buildGoModule rec { 34 | pname = "protoc-gen-validate"; 35 | version = "0.4.0"; 36 | 37 | src = fetchFromGitHub { 38 | owner = "envoyproxy"; 39 | repo = "protoc-gen-validate"; 40 | rev = "v0.4.0"; 41 | sha256 = "0w352i2nlsz069v28q99mz1590c3wba9f55slz51pmgyr9qlil3c"; 42 | }; 43 | 44 | modSha256 = "1s5kxj25zw0zwqrdbcq45jv1f8g430n8ijf4c4lax6sismzgwc07"; 45 | 46 | subPackages = [ "." ]; 47 | }; 48 | 49 | in pkgs.mkShell rec { 50 | name = "horizon"; 51 | 52 | # The packages in the `buildInputs` list will be added to the PATH in our shell 53 | buildInputs = [ 54 | pkgs.go 55 | pkgs.go-bindata 56 | pkgs.go-protobuf 57 | pkgs.protobuf3_11 58 | pkgs.postgresql_12 59 | protoc-gen-validate 60 | ]; 61 | 62 | # Extra env vars 63 | PGHOST = "localhost"; 64 | PGPORT = "5432"; 65 | PGDATABASE = "noop"; 66 | PGUSER = "postgres"; 67 | PGPASSWORD = "postgres"; 68 | DATABASE_URL = "postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/${PGDATABASE}?sslmode=disable&x-migrations-table=waypoint_hzn_migrations"; 69 | } 70 | --------------------------------------------------------------------------------