├── .bazeliskrc ├── .bazelrc ├── .gitattributes ├── .gitignore ├── BUILD.bazel ├── DEVELOPING.md ├── LICENSE ├── README.md ├── WORKSPACE ├── api ├── endpoint │ └── v1 │ │ ├── BUILD.bazel │ │ └── endpoint.proto ├── envoy │ ├── config │ │ └── core │ │ │ └── v3 │ │ │ ├── BUILD.bazel │ │ │ ├── address.proto │ │ │ └── base.proto │ └── data │ │ ├── accesslog │ │ └── v3 │ │ │ ├── BUILD.bazel │ │ │ └── accesslog.proto │ │ └── tap │ │ └── v3 │ │ ├── BUILD.bazel │ │ ├── common.proto │ │ └── http.proto ├── logs │ └── v1 │ │ ├── BUILD.bazel │ │ └── logs.proto └── middleware │ └── v1 │ ├── BUILD.bazel │ └── middleware.proto ├── cmd └── server │ ├── BUILD.bazel │ ├── envoy-bootstrap.yaml │ ├── main.go │ └── temporalite.go ├── core ├── envoy │ ├── BUILD.bazel │ └── run.go ├── ids │ ├── BUILD.bazel │ └── uuid.go ├── log │ ├── BUILD.bazel │ └── logger.go └── server │ ├── BUILD.bazel │ ├── middleware.go │ └── server.go ├── frontend ├── .env ├── .eslintrc.js ├── .gitignore ├── BUILD.bazel ├── README.md ├── build │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── asset-manifest.json │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── github-mark-white.svg │ ├── index.html │ ├── robots.txt │ ├── site.webmanifest │ └── static │ │ ├── css │ │ ├── main.e6c13ad2.css │ │ └── main.e6c13ad2.css.map │ │ └── js │ │ ├── main.55d208c5.js │ │ ├── main.55d208c5.js.LICENSE.txt │ │ └── main.55d208c5.js.map ├── package-lock.json ├── package.json ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── github-mark-white.svg │ ├── index.html │ ├── robots.txt │ └── site.webmanifest └── src │ ├── api │ ├── Endpoints.js │ ├── Logs.js │ └── Middleware.js │ ├── components │ ├── InputExample.js │ ├── SimpleModal.js │ └── TextInput.js │ ├── index.css │ ├── index.js │ ├── pages │ ├── NotFound.js │ └── home │ │ ├── Home.js │ │ ├── components │ │ ├── CustomTabPanel.js │ │ ├── EndpointsTab.js │ │ ├── ExtensionsTab.js │ │ ├── LogsTab.js │ │ └── StatusIndicator.js │ │ └── index.js │ ├── routes.js │ ├── templates │ └── Main.js │ └── theme.js ├── go.mod ├── go.sum ├── images ├── Dockerfile.build └── README.md ├── server ├── api │ ├── BUILD.bazel │ ├── build.go │ ├── endpoint.go │ ├── logs.go │ ├── middleware.go │ └── utils.go ├── build │ ├── BUILD.bazel │ ├── build.go │ ├── go │ │ ├── BUILD.bazel │ │ └── build.go │ └── rust │ │ ├── BUILD.bazel │ │ └── build.go ├── db │ ├── BUILD.bazel │ ├── conn.go │ ├── migrations.go │ ├── sql │ │ ├── BUILD.bazel │ │ ├── endpoints.sql │ │ ├── logs.sql │ │ ├── middlewares.sql │ │ ├── migrations │ │ │ ├── 00001_init.down.sql │ │ │ ├── 00001_init.up.sql │ │ │ ├── 00002_proxies.down.sql │ │ │ ├── 00002_proxies.up.sql │ │ │ ├── 00003_logs.down.sql │ │ │ ├── 00003_logs.up.sql │ │ │ ├── 00004_endpoints.down.sql │ │ │ ├── 00004_endpoints.up.sql │ │ │ ├── BUILD.bazel │ │ │ └── migrations.go │ │ └── schema.sql │ └── types.go ├── envoy │ ├── BUILD.bazel │ └── envoy.go ├── ingest │ ├── BUILD.bazel │ └── worker.go └── watcher │ ├── BUILD.bazel │ └── watcher.go ├── static ├── github-apoxy.png └── github-proximal.png └── thirdparty ├── BUILD.bazel ├── com_github_cloudflare_circl-amd64-cgo.patch ├── com_github_googleapis_gax_go_v2-googleapis.patch ├── com_github_grpc_ecosystem_grpc_gateway.patch ├── com_github_temporalio_temporalite-new-server-return.patch └── com_google_cloud_go_storage-go-googleapis.patch /.bazeliskrc: -------------------------------------------------------------------------------- 1 | USE_BAZEL_VERSION=6.3.0 2 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | build --workspace_status_command="echo STABLE_GIT_SHA $(git rev-parse HEAD)" 2 | run --workspace_status_command="echo STABLE_GIT_SHA $(git rev-parse HEAD)" 3 | 4 | run --incompatible_enable_cc_toolchain_resolution 5 | run --@io_bazel_rules_docker//transitions:enable=false 6 | build --incompatible_enable_cc_toolchain_resolution 7 | build --@io_bazel_rules_docker//transitions:enable=false 8 | 9 | run:arm64 --platforms=@zig_sdk//platform:linux_arm64 10 | run:arm64 --extra_toolchains=@zig_sdk//toolchain:linux_arm64_gnu.2.28 11 | run:aarch64 --platforms=@zig_sdk//platform:linux_arm64 12 | run:aarch64 --extra_toolchains=@zig_sdk//toolchain:linux_arm64_gnu.2.28 13 | 14 | run:amd64 --platforms=@zig_sdk//platform:linux_amd64 15 | run:amd64 --extra_toolchains=@zig_sdk//toolchain:linux_amd64_gnu.2.28 16 | 17 | build:arm64 --platforms=@zig_sdk//platform:linux_arm64 18 | build:arm64 --extra_toolchains=@zig_sdk//toolchain:linux_arm64_gnu.2.28 19 | build:aarch64 --platforms=@zig_sdk//platform:linux_arm64 20 | build:aarch64 --extra_toolchains=@zig_sdk//toolchain:linux_arm64_gnu.2.28 21 | 22 | build:amd64 --platforms=@zig_sdk//platform:linux_amd64 23 | build:amd64 --extra_toolchains=@zig_sdk//toolchain:linux_amd64_gnu.2.28 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | WORKSPACE linguist-detectable=false 2 | *.bazel linguist-detectable=false 3 | frontend/build/** linguist-vendored 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Bazel 9 | /bazel-proximal 10 | /bazel-bin 11 | /bazel-out 12 | /bazel-testlogs 13 | 14 | # Test binary, built with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Local Databases 21 | *.db 22 | 23 | # Dependency directories (remove the comment below to include it) 24 | # vendor/ 25 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_gazelle//:def.bzl", "gazelle") 2 | 3 | # gazelle:prefix github.com/apoxy-dev/proximal 4 | gazelle(name = "gazelle") 5 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developer Documentation 2 | 3 | ## Building Proximal 4 | 5 | Since we rely on considerable amount of codegen (Protobufs, gRPC, gRPC-GW, sqlc) this project is 6 | using [Bazel](https://bazel.build) for its build automation. Bazel version is pinned using 7 | `.bazeliskrc` in the root so we recommend using [Bazelisk](https://github.com/bazelbuild/bazelisk) 8 | for the least amount of surprise. 9 | 10 | To build the Proximal server process: 11 | ```shell 12 | bazel build --config=`arch` //cmd/server 13 | ``` 14 | 15 | This above command will use [zig cc toolchain](https://sr.ht/~motiejus/bazel-zig-cc/) to compile and 16 | link Linux binary for amd64/aarch64 archs. That means cross-compilation when running from Mac hosts. 17 | 18 | Not to run a locally-built image: 19 | ```shell 20 | bazel run --config=`arch` --stamp //cmd/server:go.image -- --norun && \ 21 | docker run -p 8080:8080 -p 9901:9901 -p 9088:9088 -p 18000:18000 -v $HOME:/mnt bazel/cmd/server:go.image 22 | ``` 23 | 24 | ### Building Frontend 25 | 26 | Run `npm run build` from the `//frontend` directory. Then make sure to `git add` the 27 | `//frontend/build` directory to vendor all generated assets. Just re-run `bazel run` command above 28 | to re-launch with updated frontend code. 29 | 30 | ## Create New Release 31 | 32 | 1. Build images for `linux/arm64` and `linux/amd64` architectures stamped with 33 | the latest Git commit SHA and push them the repository. 34 | 35 | ARM: 36 | ```shell 37 | bazel run --config=arm64 --stamp //cmd/server:publish 38 | ``` 39 | Output: 40 | ```shell 41 | INFO: Build options --extra_toolchains and --platforms have changed, discarding analysis cache. 42 | INFO: Analyzed target //cmd/server:publish (0 packages loaded, 24765 targets configured). 43 | INFO: Found 1 target... 44 | Target //cmd/server:publish up-to-date: 45 | bazel-bin/cmd/server/publish.digest 46 | bazel-bin/cmd/server/publish 47 | INFO: Elapsed time: 52.532s, Critical Path: 40.43s 48 | INFO: 983 processes: 4 internal, 979 darwin-sandbox. 49 | INFO: Build completed successfully, 983 total actions 50 | INFO: Running command line: bazel-bin/cmd/server/publish 51 | 2023/07/25 14:46:57 Destination docker.io/apoxy/proximal:{STABLE_GIT_SHA}-arm64 was resolved to docker.io/apoxy/proximal:193b439e50aba2c30823fc7d952b4520b49ae323-arm64 after stamping. 52 | 2023/07/25 14:47:26 Successfully pushed Docker image to docker.io/apoxy/proximal:193b439e50aba2c30823fc7d952b4520b49ae323-arm64 - docker.io/apoxy/proximal@sha256:cab49bbb6106bbc3a74a348f132e3537e9178225c994f11351899c13d2287063 53 | ``` 54 | 55 | Intel: 56 | ```shell 57 | bazel run --config=amd64 --stamp //cmd/server:publish 58 | ``` 59 | Output: 60 | ``` 61 | INFO: Build option --stamp has changed, discarding analysis cache. 62 | INFO: Analyzed target //cmd/server:publish (0 packages loaded, 24888 targets configured). 63 | INFO: Found 1 target... 64 | Target //cmd/server:publish up-to-date: 65 | bazel-bin/cmd/server/publish.digest 66 | bazel-bin/cmd/server/publish 67 | INFO: Elapsed time: 0.809s, Critical Path: 0.11s 68 | INFO: 7 processes: 4 internal, 3 darwin-sandbox. 69 | INFO: Build completed successfully, 7 total actions 70 | INFO: Running command line: bazel-bin/cmd/server/publish 71 | 2023/07/25 14:45:36 Destination docker.io/apoxy/proximal:{STABLE_GIT_SHA}amd64 was resolved to docker.io/apoxy/proximal:193b439e50aba2c30823fc7d952b4520b49ae323amd64 after stamping. 72 | 2023/07/25 14:45:42 Successfully pushed Docker image to docker.io/apoxy/proximal:193b439e50aba2c30823fc7d952b4520b49ae323amd64 - docker.io/apoxy/proximal@sha256:dac87b48e45dbca1f87309fedb16e3ad1ab26f9977f1204101789c1141404788 73 | ``` 74 | 75 | 2. Use `buildx imagetools` command to create a single multi-arch manifest referencing the above outputs: 76 | 77 | ```shell 78 | TAG= 79 | docker buildx imagetools create -t docker.io/apoxy/proximal:$TAG \ 80 | docker.io/apoxy/proximal:193b439e50aba2c30823fc7d952b4520b49ae323-amd64 81 | docker.io/apoxy/proximal:193b439e50aba2c30823fc7d952b4520b49ae323-arm64 82 | ``` 83 | 84 | 3. Bump the `:latest` manifest: 85 | ``` 86 | docker buildx imagetools create -t docker.io/apoxy/proximal:latest docker.io/apoxy/proximal:$TAG 87 | ``` 88 | 89 | :tada:! 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 | 5 | 6 | Proximal Logo 7 | 8 |
9 |
10 | 11 | [![Apache 2.0 License](https://badgen.net/badge/License/Apache2.0?icon=github)](LICENSE) [![Slack Community](https://img.shields.io/badge/slack-apoxy-bde868.svg?logo=slack)](http://slack.apoxy.dev/) 12 | 13 |
14 |
15 | 16 | Proximal makes it easy to develop 17 | [Proxy-WASM](https://github.com/proxy-wasm/spec) modules for [Envoy](https://www.envoyproxy.io) 18 | right on your local machine (or anywhere you can run our [Docker 19 | image](https://hub.docker.com/r/apoxy/proximal)). 20 | 21 | ## TL;DR 22 | 23 | Develop WebAssembly extensions for Envoy with ease. Try running `docker run -p 8080:8080 -p 18000:18000 docker.io/apoxy/proximal:latest` 24 | then visit http://localhost:8080 25 | 26 | Demo: 27 | 28 | https://github.com/apoxy-dev/proximal/assets/767232/97cea009-7f6c-47f9-b2d6-70146ef7ff3a 29 | 30 | ### What is Proxy-WASM? 31 | 32 | [Proxy-WASM](https://github.com/proxy-wasm/spec) (WebAssembly) is a powerful technology that enables 33 | you to extend the functionality of modern proxies like [Envoy](https://www.envoyproxy.io) with 34 | WebAssembly modules. By writing Proxy-WASM modules, you can write code in your L4/L7 proxy that inspects, 35 | mutates, and routes requests as they are passing through, all in a language-independent and sandboxed 36 | environment. It works with both HTTP and TCP-based connection and SDKs are available for 37 | [Rust](https://github.com/proxy-wasm/proxy-wasm-rust-sdk), 38 | [Go](https://github.com/tetratelabs/proxy-wasm-go-sdk), 39 | [C++](https://github.com/proxy-wasm/proxy-wasm-cpp-sdk), and 40 | [AssemblyScript](https://github.com/solo-io/proxy-runtime) (we're working on JavaScript and Python). 41 | 42 | These standards based WebAssembly modules can be used with [Istio](https://istio.io/latest/docs/concepts/wasm/), 43 | [MOSN](https://github.com/mosn/mosn) and 44 | [APSIX](https://apisix.apache.org/blog/2021/11/19/apisix-supports-wasm/#how-to-use-wasm-in-apache-apisix) as well. 45 | 46 | ## Why Proximal? 47 | 48 | Developing Proxy-WASM modules for Envoy traditionally involves cumbersome setups, complex 49 | toolchains, and time-consuming testing iterations, frequently on a remote environment. 50 | Proximal simplifies the development process and brings it to your local machine in a single process 51 | environment with a friendly UI and basic REST API. 52 | 53 | We believe that developers have been held back from adopting this incredibly powerful technology because 54 | the developer experience for WASM and Proxy-WASM has been a little rough around the edges. Proximal is here to help. 55 | 56 | ### Key Features: 57 | 58 | * **Local Development**: Forget about deploying to remote environments for every code change. Proximal 59 | allows you to develop and test your Proxy-WASM modules locally, saving you valuable time and 60 | effort. It features a workflow engine that compiles source code into 61 | WebAssembly binary (.wasm) and loads them into Envoy automatically. 62 | 63 | * **Rapid Iterations**: Change your code and see the results almost instantaneously. Proximal continuously 64 | watches a working directory (even Docker mounted volumes) and triggers a rebuild/reload of your module in Envoy automatically. 65 | 66 | * **Simplified Setup + Examples**: Setting up a development environment for Proxy-WASM can be daunting. Proximal 67 | streamlines the process and provides a few examples you can use to get started with minimal configuration. 68 | 69 | * **Observability**: Debugging is easier with integrated logs capture. See requests and responses in real-time. 70 | 71 | ## Getting Started 72 | 73 | Run via Docker container: 74 | 75 | ```shell 76 | docker run -p 8080:8080 -p 9901:9901 -p 9088:9088 -p 18000:18000 -v `pwd`:/mnt docker.io/apoxy/proximal:latest 77 | ``` 78 | 79 | The above command mounts your current working directory at `/mnt` inside the container so you can ingest local 80 | Proxy-WASM code (e.g. `/mnt/myprojects/myawesome-proxy-wasm-go/`). Adjust as needed. 81 | 82 | Bound ports: 83 | * `8080` - Web UI (see below) and REST API at `:8080/v1/` (see definitions in the [`//api`](https://github.com/apoxy-dev/proximal/tree/main/api) folder). 84 | * `18000` - Envoy listener - test your proxy configurations by sending requests to `localhost:18000`. 85 | * `9901` - Envoy admin UI. 86 | * `9088` - Temporal UI (for build workflow debugging). 87 | 88 | 89 | ## Architecture 90 | 91 | We rely on Envoy as the main [data plane](https://en.wikipedia.org/wiki/Forwarding_plane) processing 92 | engine for request routing and its WebAssembly (WASI) extension engine that implements the Proxy-WASM 93 | ABI. The default runtime is [Chromium V8](https://v8.dev) but other runtimes such as 94 | [Wasmtime](https://wasmtime.dev), 95 | [Wamr](https://github.com/bytecodealliance/wasm-micro-runtime), and 96 | [WAVM](https://wavm.github.io/) 97 | can be configured. 98 | 99 | The control plane server is a single Go binary that combines an Envoy control plane (using xDS 100 | protocol), a REST API server, a React app, and a [Temporal](https://temporal.io) server 101 | (which is linked directly via the awesome [temporalite](https://github.com/temporalio/temporalite) library) 102 | for managing build workflows. The same binary also acts as a Temporal worker and manages the Envoy process. 103 | 104 | Internal state is supported by an embedded SQLite instance which produces an `sqlite3.db` file on local 105 | disk. The Temporal server has its own SQLite db file - `temporalite.db`. Both of these need to be exported 106 | via Docker volume mount if you want state persisted across Docker runs. 107 | 108 | Compiled `.wasm` binaries are stored on local disk in the `/tmp/proximal/` directory. 109 | 110 | HTML/CSS/JavaScript assets currently live on local filesystem but will be embedded in the binary 111 | itself in the future. 112 | 113 | High-level Design: 114 | 115 |
116 | 117 | ![proximal-architecture](https://github.com/apoxy-dev/proximal/assets/284347/3585bbae-b014-47cd-aa38-d47a03acacc3) 118 | 119 |
120 | 121 | ## Known Limitations / Future Improvements 122 | 123 | Known Limitations: 124 | 125 | * The entire setup is a single instance, single binary deal designed for local experimentation. 126 | While it's possible to run it on remote host since it's packaged in Docker, replication features 127 | are rather lacking. 128 | * TCP filters aren't yet supported. 129 | * Currently Proximal supports re-triggering builds from a git source manually. Automatic build 130 | triggers from GitHub commit webhooks or the like aren't suppported since they would require a 131 | hosted solution with a stable webhook endpoint. 132 | 133 | Roadmap: 134 | 135 | * More SDKs + Examples - [AssemblyScript](https://github.com/apoxy-dev/proximal/issues/1), 136 | [C++](https://github.com/apoxy-dev/proximal/issues/2), 137 | [JavaScript](https://github.com/apoxy-dev/proximal/issues/3), and 138 | [Python](https://github.com/apoxy-dev/proximal/issues/4). 139 | * Istio examples - show how you take these modules into an existing Istio-enabled cluster. 140 | * K/V store integration. 141 | * Improved logging / tracing / accounting. 142 | * TCP and UDP filters. 143 | 144 | If you're interested in any of above features (or maybe something else), feel free to drop a note to the 145 | [Apoxy Team](mailto:hello@apoxy.dev) or open an issue on this repo! 146 | 147 | ## Contributing 148 | 149 | Patches Welcome! (no, really) 150 | 151 | Proximal welcomes contributions from the community. If you find bugs, or wish to contribute code, please 152 | check out our [contribution guidelines](DEVELOPING.md) for detailed instructions. 153 | 154 | ### Support and Feedback 155 | 156 | If you encounter any issues, have questions, or want to provide feedback, we want to hear from you! 157 | Feel free to join our active community on Slack, raise an issue on GitHub, or shoot us an email: 158 | 159 | * [Apoxy Community Slack](http://slack.apoxy.dev/) 160 | * [👋 Apoxy Email](mailto:hello@apoxy.dev) 161 | 162 | ## License 163 | 164 | Proximal is released under the [Apache 2.0 License](LICENSE). 165 | 166 | ## Credits 167 | 168 | Proximal is developed and maintained by the Apoxy team. We want to thank the open-source community 169 | for their contributions and support in making this project possible. Special thanks go to: [Envoy 170 | Proxy](https://www.envoyproxy.io) community, [Proxy-WASM ABI and 171 | SDKs](https://github.com/proxy-wasm/spec) contributors, and fine folks at 172 | [Temporal](https://temporal.io). 173 | 174 |
175 |
176 |

177 | 178 | Apoxy Logo 179 | 180 |

181 |
182 |
183 | 184 |

185 | Let's take Proxy-WASM development to new levels with Proximal! Happy Proxying! 🚀 186 |

187 | -------------------------------------------------------------------------------- /api/endpoint/v1/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_proto//proto:defs.bzl", "proto_library") 2 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 3 | load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") 4 | 5 | proto_library( 6 | name = "endpointsv1_proto", 7 | srcs = ["endpoint.proto"], 8 | visibility = ["//visibility:public"], 9 | deps = [ 10 | "@com_google_protobuf//:empty_proto", 11 | "@com_google_protobuf//:timestamp_proto", 12 | "@go_googleapis//google/api:annotations_proto", 13 | ], 14 | ) 15 | 16 | go_proto_library( 17 | name = "endpointv1_go_proto", 18 | compilers = [ 19 | "@io_bazel_rules_go//proto:go_grpc", 20 | "@com_github_grpc_ecosystem_grpc_gateway_v2//protoc-gen-grpc-gateway:go_gen_grpc_gateway", # keep 21 | ], 22 | importpath = "github.com/apoxy-dev/proximal/api/endpoint/v1", 23 | proto = ":endpointsv1_proto", 24 | visibility = ["//visibility:public"], 25 | deps = ["@go_googleapis//google/api:annotations_go_proto"], 26 | ) 27 | 28 | go_library( 29 | name = "endpoint", 30 | embed = [":endpointv1_go_proto"], 31 | importpath = "github.com/apoxy-dev/proximal/api/endpoint/v1", 32 | visibility = ["//visibility:public"], 33 | ) 34 | -------------------------------------------------------------------------------- /api/endpoint/v1/endpoint.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proximal.endpoints.v1; 4 | 5 | option go_package = "github.com/apoxy-dev/proximal/api/endpoint/v1;endpointv1"; 6 | 7 | import "google/api/annotations.proto"; 8 | import "google/protobuf/empty.proto"; 9 | import "google/protobuf/timestamp.proto"; 10 | 11 | message Healthcheck { 12 | // TBD 13 | } 14 | 15 | message Address { 16 | string host = 1; 17 | int32 port = 2; 18 | } 19 | 20 | message EndpointStatus { 21 | // Set to true if the endpoint is a domain name (e.g. example.com) as opposed 22 | // to an IP address. 23 | bool is_domain = 1; 24 | 25 | // The details of the endpoint status. 26 | string details = 2; 27 | } 28 | 29 | message Endpoint { 30 | string cluster = 1; 31 | 32 | bool default_upstream = 2; 33 | 34 | EndpointStatus status = 3; 35 | 36 | repeated Address addresses = 4; 37 | 38 | enum DNSLookupFamily { 39 | // Unspecified. 40 | UNSPECIFIED = 0; 41 | // Lookup first IPv4; if not available, lookup IPv6. (default) 42 | V4_FIRST = 1; 43 | // IPv4 only. 44 | V4_ONLY = 2; 45 | // V6 first; if not available, lookup IPv4. 46 | V6_FIRST = 3; 47 | // IPv6 only. 48 | V6_ONLY = 4; 49 | } 50 | DNSLookupFamily dns_lookup_family = 5; 51 | 52 | bool use_tls = 6; 53 | 54 | Healthcheck healthcheck = 7; 55 | 56 | google.protobuf.Timestamp created_at = 8; 57 | 58 | google.protobuf.Timestamp updated_at = 9; 59 | } 60 | 61 | message CreateEndpointRequest { 62 | Endpoint endpoint = 1; 63 | } 64 | 65 | message ListEndpointsRequest { 66 | string page_token = 1; 67 | int32 page_size = 2; 68 | } 69 | 70 | message ListEndpointsResponse { 71 | repeated Endpoint endpoints = 1; 72 | string next_page_token = 2; 73 | } 74 | 75 | message GetEndpointRequest { 76 | string cluster = 1; 77 | } 78 | 79 | message UpdateEndpointRequest { 80 | Endpoint endpoint = 1; 81 | } 82 | 83 | message DeleteEndpointRequest { 84 | string cluster = 1; 85 | } 86 | 87 | service EndpointService { 88 | rpc CreateEndpoint(CreateEndpointRequest) returns (Endpoint) { 89 | option (google.api.http) = { 90 | post: "/v1/endpoints" 91 | body: "*" 92 | }; 93 | } 94 | rpc ListEndpoints(ListEndpointsRequest) returns (ListEndpointsResponse) { 95 | option (google.api.http) = { 96 | get: "/v1/endpoints" 97 | }; 98 | } 99 | rpc GetEndpoint(GetEndpointRequest) returns (Endpoint) { 100 | option (google.api.http) = { 101 | get: "/v1/endpoints/{cluster}" 102 | }; 103 | } 104 | rpc UpdateEndpoint(UpdateEndpointRequest) returns (Endpoint) { 105 | option (google.api.http) = { 106 | put: "/v1/endpoints/{endpoint.cluster}" 107 | body: "*" 108 | }; 109 | } 110 | rpc DeleteEndpoint(DeleteEndpointRequest) returns (google.protobuf.Empty) { 111 | option (google.api.http) = { 112 | delete: "/v1/endpoints/{cluster}" 113 | }; 114 | } 115 | 116 | rpc InternalListEndpoints(google.protobuf.Empty) returns (ListEndpointsResponse) { 117 | option (google.api.http) = { 118 | get: "/v1/internal/endpoints" 119 | }; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /api/envoy/config/core/v3/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_proto//proto:defs.bzl", "proto_library") 2 | 3 | # gazelle:go_generate_proto false 4 | 5 | proto_library( 6 | name = "corev3_proto", 7 | srcs = [ 8 | "address.proto", 9 | "base.proto", 10 | ], 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "@com_google_protobuf//:any_proto", 14 | "@com_google_protobuf//:struct_proto", 15 | "@com_google_protobuf//:wrappers_proto", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /api/envoy/config/core/v3/address.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.envoy.config.core.v3; 4 | 5 | import "google/protobuf/wrappers.proto"; 6 | 7 | option go_package = "github.com/envoyproxy/go-control-plane/envoy/config/core/v3;corev3"; 8 | 9 | message Pipe { 10 | // Unix Domain Socket path. On Linux, paths starting with '@' will use the 11 | // abstract namespace. The starting '@' is replaced by a null byte by Envoy. 12 | // Paths starting with '@' will result in an error in environments other than 13 | // Linux. 14 | string path = 1; 15 | 16 | // The mode for the Pipe. Not applicable for abstract sockets. 17 | uint32 mode = 2; 18 | } 19 | 20 | // The address represents an envoy internal listener. 21 | message EnvoyInternalAddress { 22 | oneof address_name_specifier { 23 | // Specifies the :ref:`name ` of the 24 | // internal listener. 25 | string server_listener_name = 1; 26 | } 27 | 28 | // Specifies an endpoint identifier to distinguish between multiple endpoints for the same internal listener in a 29 | // single upstream pool. Only used in the upstream addresses for tracking changes to individual endpoints. This, for 30 | // example, may be set to the final destination IP for the target internal listener. 31 | string endpoint_id = 2; 32 | } 33 | 34 | message SocketAddress { 35 | enum Protocol { 36 | TCP = 0; 37 | UDP = 1; 38 | } 39 | 40 | Protocol protocol = 1; 41 | 42 | // The address for this socket. :ref:`Listeners ` will bind 43 | // to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` 44 | // to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: 45 | // It is possible to distinguish a Listener address via the prefix/suffix matching 46 | // in :ref:`FilterChainMatch `.] When used 47 | // within an upstream :ref:`BindConfig `, the address 48 | // controls the source address of outbound connections. For :ref:`clusters 49 | // `, the cluster type determines whether the 50 | // address must be an IP (``STATIC`` or ``EDS`` clusters) or a hostname resolved by DNS 51 | // (``STRICT_DNS`` or ``LOGICAL_DNS`` clusters). Address resolution can be customized 52 | // via :ref:`resolver_name `. 53 | string address = 2; 54 | 55 | oneof port_specifier { 56 | uint32 port_value = 3; 57 | 58 | // This is only valid if :ref:`resolver_name 59 | // ` is specified below and the 60 | // named resolver is capable of named port resolution. 61 | string named_port = 4; 62 | } 63 | 64 | // The name of the custom resolver. This must have been registered with Envoy. If 65 | // this is empty, a context dependent default applies. If the address is a concrete 66 | // IP address, no resolution will occur. If address is a hostname this 67 | // should be set for resolution other than DNS. Specifying a custom resolver with 68 | // ``STRICT_DNS`` or ``LOGICAL_DNS`` will generate an error at runtime. 69 | string resolver_name = 5; 70 | 71 | // When binding to an IPv6 address above, this enables `IPv4 compatibility 72 | // `_. Binding to ``::`` will 73 | // allow both IPv4 and IPv6 connections, with peer IPv4 addresses mapped into 74 | // IPv6 space as ``::FFFF:``. 75 | bool ipv4_compat = 6; 76 | } 77 | 78 | message TcpKeepalive { 79 | // Maximum number of keepalive probes to send without response before deciding 80 | // the connection is dead. Default is to use the OS level configuration (unless 81 | // overridden, Linux defaults to 9.) 82 | google.protobuf.UInt32Value keepalive_probes = 1; 83 | 84 | // The number of seconds a connection needs to be idle before keep-alive probes 85 | // start being sent. Default is to use the OS level configuration (unless 86 | // overridden, Linux defaults to 7200s (i.e., 2 hours.) 87 | google.protobuf.UInt32Value keepalive_time = 2; 88 | 89 | // The number of seconds between keep-alive probes. Default is to use the OS 90 | // level configuration (unless overridden, Linux defaults to 75s.) 91 | google.protobuf.UInt32Value keepalive_interval = 3; 92 | } 93 | 94 | // Addresses specify either a logical or physical address and port, which are 95 | // used to tell Envoy where to bind/listen, connect to upstream and find 96 | // management servers. 97 | message Address { 98 | oneof address { 99 | SocketAddress socket_address = 1; 100 | 101 | Pipe pipe = 2; 102 | 103 | // Specifies a user-space address handled by :ref:`internal listeners 104 | // `. 105 | EnvoyInternalAddress envoy_internal_address = 3; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /api/envoy/config/core/v3/base.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.envoy.config.core.v3; 4 | 5 | import "google/protobuf/any.proto"; 6 | import "google/protobuf/struct.proto"; 7 | 8 | option go_package = "github.com/envoyproxy/go-control-plane/envoy/config/core/v3;corev3"; 9 | 10 | // HTTP request method. 11 | enum RequestMethod { 12 | METHOD_UNSPECIFIED = 0; 13 | GET = 1; 14 | HEAD = 2; 15 | POST = 3; 16 | PUT = 4; 17 | DELETE = 5; 18 | CONNECT = 6; 19 | OPTIONS = 7; 20 | TRACE = 8; 21 | PATCH = 9; 22 | } 23 | 24 | // Identifies the direction of the traffic relative to the local Envoy. 25 | enum TrafficDirection { 26 | // Default option is unspecified. 27 | UNSPECIFIED = 0; 28 | 29 | // The transport is used for incoming traffic. 30 | INBOUND = 1; 31 | 32 | // The transport is used for outgoing traffic. 33 | OUTBOUND = 2; 34 | } 35 | 36 | // Metadata provides additional inputs to filters based on matched listeners, 37 | // filter chains, routes and endpoints. It is structured as a map, usually from 38 | // filter name (in reverse DNS format) to metadata specific to the filter. Metadata 39 | // key-values for a filter are merged as connection and request handling occurs, 40 | // with later values for the same key overriding earlier values. 41 | // 42 | // An example use of metadata is providing additional values to 43 | // http_connection_manager in the envoy.http_connection_manager.access_log 44 | // namespace. 45 | // 46 | // Another example use of metadata is to per service config info in cluster metadata, which may get 47 | // consumed by multiple filters. 48 | // 49 | // For load balancing, Metadata provides a means to subset cluster endpoints. 50 | // Endpoints have a Metadata object associated and routes contain a Metadata 51 | // object to match against. There are some well defined metadata used today for 52 | // this purpose: 53 | // 54 | // * ``{"envoy.lb": {"canary": }}`` This indicates the canary status of an 55 | // endpoint and is also used during header processing 56 | // (x-envoy-upstream-canary) and for stats purposes. 57 | // [#next-major-version: move to type/metadata/v2] 58 | message Metadata { 59 | // Key is the reverse DNS filter name, e.g. com.acme.widget. The ``envoy.*`` 60 | // namespace is reserved for Envoy's built-in filters. 61 | // If both ``filter_metadata`` and 62 | // :ref:`typed_filter_metadata ` 63 | // fields are present in the metadata with same keys, 64 | // only ``typed_filter_metadata`` field will be parsed. 65 | map filter_metadata = 1; 66 | 67 | // Key is the reverse DNS filter name, e.g. com.acme.widget. The ``envoy.*`` 68 | // namespace is reserved for Envoy's built-in filters. 69 | // The value is encoded as google.protobuf.Any. 70 | // If both :ref:`filter_metadata ` 71 | // and ``typed_filter_metadata`` fields are present in the metadata with same keys, 72 | // only ``typed_filter_metadata`` field will be parsed. 73 | map typed_filter_metadata = 2; 74 | } 75 | 76 | // Header name/value pair. 77 | message HeaderValue { 78 | // Header name. 79 | string key = 1; 80 | 81 | // Header value. 82 | // 83 | // The same :ref:`format specifier ` as used for 84 | // :ref:`HTTP access logging ` applies here, however 85 | // unknown header values are replaced with the empty string instead of ``-``. 86 | // Header value is encoded as string. This does not work for non-utf8 characters. 87 | // Only one of ``value`` or ``raw_value`` can be set. 88 | string value = 2; 89 | 90 | // Header value is encoded as bytes which can support non-utf8 characters. 91 | // Only one of ``value`` or ``raw_value`` can be set. 92 | bytes raw_value = 3; 93 | } 94 | 95 | // Wrapper for a set of headers. 96 | message HeaderMap { 97 | repeated HeaderValue headers = 1; 98 | } 99 | -------------------------------------------------------------------------------- /api/envoy/data/accesslog/v3/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_proto//proto:defs.bzl", "proto_library") 2 | 3 | # gazelle:go_generate_proto false 4 | 5 | proto_library( 6 | name = "accesslogv3_proto", 7 | srcs = ["accesslog.proto"], 8 | visibility = ["//visibility:public"], 9 | deps = [ 10 | "//api/envoy/config/core/v3:corev3_proto", 11 | "@com_google_protobuf//:any_proto", 12 | "@com_google_protobuf//:duration_proto", 13 | "@com_google_protobuf//:timestamp_proto", 14 | "@com_google_protobuf//:wrappers_proto", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /api/envoy/data/tap/v3/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_proto//proto:defs.bzl", "proto_library") 2 | 3 | # gazelle:go_generate_proto false 4 | 5 | proto_library( 6 | name = "tapv3_proto", 7 | srcs = [ 8 | "common.proto", 9 | "http.proto", 10 | ], 11 | visibility = ["//visibility:public"], 12 | deps = ["//api/envoy/config/core/v3:corev3_proto"], 13 | ) 14 | -------------------------------------------------------------------------------- /api/envoy/data/tap/v3/common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.envoy.data.tap.v3; 4 | 5 | option go_package = "github.com/envoyproxy/go-control-plane/envoy/data/tap/v3;tapv3"; 6 | 7 | // Wrapper for tapped body data. This includes HTTP request/response body, transport socket received 8 | // and transmitted data, etc. 9 | message Body { 10 | oneof body_type { 11 | // Body data as bytes. By default, tap body data will be present in this field, as the proto 12 | // ``bytes`` type can contain any valid byte. 13 | bytes as_bytes = 1; 14 | 15 | // Body data as string. This field is only used when the :ref:`JSON_BODY_AS_STRING 16 | // ` sink 17 | // format type is selected. See the documentation for that option for why this is useful. 18 | string as_string = 2; 19 | } 20 | 21 | // Specifies whether body data has been truncated to fit within the specified 22 | // :ref:`max_buffered_rx_bytes 23 | // ` and 24 | // :ref:`max_buffered_tx_bytes 25 | // ` settings. 26 | bool truncated = 3; 27 | } 28 | -------------------------------------------------------------------------------- /api/envoy/data/tap/v3/http.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.envoy.data.tap.v3; 4 | 5 | import "api/envoy/config/core/v3/base.proto"; 6 | import "api/envoy/data/tap/v3/common.proto"; 7 | 8 | option go_package = "github.com/envoyproxy/go-control-plane/envoy/data/tap/v3;tapv3"; 9 | 10 | // A fully buffered HTTP trace message. 11 | message HttpBufferedTrace { 12 | // HTTP message wrapper. 13 | message Message { 14 | // Message headers. 15 | repeated config.core.v3.HeaderValue headers = 1; 16 | 17 | // Message body. 18 | Body body = 2; 19 | 20 | // Message trailers. 21 | repeated config.core.v3.HeaderValue trailers = 3; 22 | } 23 | 24 | // Request message. 25 | Message request = 1; 26 | 27 | // Response message. 28 | Message response = 2; 29 | } 30 | 31 | // A streamed HTTP trace segment. Multiple segments make up a full trace. 32 | // [#next-free-field: 8] 33 | message HttpStreamedTraceSegment { 34 | // Trace ID unique to the originating Envoy only. Trace IDs can repeat and should not be used 35 | // for long term stable uniqueness. 36 | uint64 trace_id = 1; 37 | 38 | oneof message_piece { 39 | // Request headers. 40 | config.core.v3.HeaderMap request_headers = 2; 41 | 42 | // Request body chunk. 43 | Body request_body_chunk = 3; 44 | 45 | // Request trailers. 46 | config.core.v3.HeaderMap request_trailers = 4; 47 | 48 | // Response headers. 49 | config.core.v3.HeaderMap response_headers = 5; 50 | 51 | // Response body chunk. 52 | Body response_body_chunk = 6; 53 | 54 | // Response trailers. 55 | config.core.v3.HeaderMap response_trailers = 7; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /api/logs/v1/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_proto//proto:defs.bzl", "proto_library") 2 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 3 | load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") 4 | 5 | # gazelle:resolve proto proto envoy/data/tap/v3/http.proto //api/envoy/data/tap/v3:tapv3_proto 6 | # gazelle:resolve proto proto envoy/data/accesslog/v3/http.proto //api/envoy/data/accesslog/v3:accesslogv3_proto 7 | 8 | proto_library( 9 | name = "logsv1_proto", 10 | srcs = ["logs.proto"], 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "//api/envoy/data/accesslog/v3:accesslogv3_proto", 14 | "//api/envoy/data/tap/v3:tapv3_proto", 15 | "@com_google_protobuf//:timestamp_proto", 16 | "@go_googleapis//google/api:annotations_proto", 17 | ], 18 | ) 19 | 20 | # gazelle:resolve proto go api/envoy/data/tap/v3/http.proto @com_github_envoyproxy_go_control_plane//envoy/data/tap/v3:go_default_library 21 | # gazelle:resolve proto go api/envoy/data/accesslog/v3/accesslog.proto @com_github_envoyproxy_go_control_plane//envoy/data/accesslog/v3:go_default_library 22 | 23 | go_proto_library( 24 | name = "logsv1_go_proto", 25 | compilers = [ 26 | "@io_bazel_rules_go//proto:go_grpc", 27 | "@com_github_grpc_ecosystem_grpc_gateway_v2//protoc-gen-grpc-gateway:go_gen_grpc_gateway", # keep 28 | ], 29 | importpath = "github.com/apoxy-dev/proximal/api/logs/v1", 30 | proto = ":logsv1_proto", 31 | visibility = ["//visibility:public"], 32 | deps = [ 33 | "@com_github_envoyproxy_go_control_plane//envoy/data/accesslog/v3:go_default_library", 34 | "@com_github_envoyproxy_go_control_plane//envoy/data/tap/v3:go_default_library", 35 | "@go_googleapis//google/api:annotations_go_proto", 36 | ], 37 | ) 38 | 39 | go_library( 40 | name = "logs", 41 | embed = [":logsv1_go_proto"], 42 | importpath = "github.com/apoxy-dev/proximal/api/logs/v1", 43 | visibility = ["//visibility:public"], 44 | ) 45 | -------------------------------------------------------------------------------- /api/logs/v1/logs.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proximal.logs.v1; 4 | 5 | option go_package = "github.com/apoxy-dev/proximal/api/logs/v1;logsv1"; 6 | 7 | import "google/api/annotations.proto"; 8 | import "google/protobuf/timestamp.proto"; 9 | 10 | // TODO(dilyevsky): So this was copy-pasted from envoyproxy/envoy repo. We should 11 | // vendor it properly using Bazel. 12 | import "api/envoy/data/tap/v3/http.proto"; 13 | import "api/envoy/data/accesslog/v3/accesslog.proto"; 14 | 15 | message GetLogsRequest { 16 | google.protobuf.Timestamp start = 1; 17 | google.protobuf.Timestamp end = 2; 18 | string page_token = 3; 19 | int32 page_size = 4; 20 | } 21 | 22 | message Log { 23 | string id = 1; 24 | google.protobuf.Timestamp timestamp = 2; 25 | api.envoy.data.accesslog.v3.HTTPAccessLogEntry http = 3; 26 | } 27 | 28 | message GetLogsResponse { 29 | repeated Log logs = 1; 30 | string next_page_token = 2; 31 | } 32 | 33 | message GetFullLogRequest { 34 | uint32 id = 1; 35 | } 36 | 37 | message FullLog { 38 | uint32 id = 1; 39 | api.envoy.data.tap.v3.HttpBufferedTrace message = 2; 40 | } 41 | 42 | message GetFullLogResponse { 43 | FullLog log = 1; 44 | } 45 | 46 | service LogsService { 47 | rpc GetLogs(GetLogsRequest) returns (GetLogsResponse) { 48 | option (google.api.http) = { 49 | get: "/v1/logs" 50 | }; 51 | } 52 | rpc GetFullLog(GetFullLogRequest) returns (FullLog) { 53 | option (google.api.http) = { 54 | get: "/v1/logs/{id}" 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /api/middleware/v1/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_proto//proto:defs.bzl", "proto_library") 2 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 3 | load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") 4 | 5 | proto_library( 6 | name = "middleware_proto", 7 | srcs = ["middleware.proto"], 8 | visibility = ["//visibility:public"], 9 | deps = [ 10 | "@com_google_protobuf//:empty_proto", 11 | "@com_google_protobuf//:timestamp_proto", 12 | "@go_googleapis//google/api:annotations_proto", 13 | ], 14 | ) 15 | 16 | go_proto_library( 17 | name = "middlewarev1_go_proto", 18 | compilers = [ 19 | "@io_bazel_rules_go//proto:go_grpc", 20 | "@com_github_grpc_ecosystem_grpc_gateway_v2//protoc-gen-grpc-gateway:go_gen_grpc_gateway", # keep 21 | ], 22 | importpath = "github.com/apoxy-dev/proximal/api/middleware/v1", 23 | proto = ":middleware_proto", 24 | visibility = ["//visibility:public"], 25 | deps = ["@go_googleapis//google/api:annotations_go_proto"], 26 | ) 27 | 28 | go_library( 29 | name = "middleware", 30 | embed = [":middlewarev1_go_proto"], 31 | importpath = "github.com/apoxy-dev/proximal/api/middleware/v1", 32 | visibility = ["//visibility:public"], 33 | ) 34 | -------------------------------------------------------------------------------- /api/middleware/v1/middleware.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proximal.middleware.v1; 4 | 5 | option go_package = "github.com/apoxy-dev/proximal/api/middleware/v1;middlewarev1"; 6 | 7 | import "google/api/annotations.proto"; 8 | import "google/protobuf/empty.proto"; 9 | import "google/protobuf/timestamp.proto"; 10 | 11 | message MiddlewareIngestParams { 12 | enum MiddlewareIngestType { 13 | UNKNOWN_INGEST_TYPE = 0; 14 | DIRECT = 1; 15 | GITHUB = 2; 16 | } 17 | MiddlewareIngestType type = 1; 18 | 19 | // GitHub owner/repo for the GitHub source. Ignored for other types. 20 | string github_repo = 2; 21 | 22 | // Root directory for the build. If empty, defaults to repo root or watch_dir 23 | // for direct sources. This is useful for monorepos. For example, if the 24 | // go.mod file is in the "middleware/go" directory, set this to 25 | // "middleware/go". 26 | string root_dir = 3; 27 | 28 | // Branch name for the GitHub source. Ignored for other types. Only one of commit or branch 29 | // can be set. 30 | string branch = 4; 31 | 32 | // Commit for the GitHub source. Ignored for other types. Only one of commit or branch 33 | // can be set. 34 | string commit = 5; 35 | 36 | // Directory to ingest from the direct source. Ignored for other types. 37 | string watch_dir = 6; 38 | 39 | enum MiddlewareLanguage { 40 | UNKNOWN_LANGUAGE = 0; 41 | GO = 1; 42 | RUST = 2; 43 | } 44 | MiddlewareLanguage language = 7; 45 | 46 | repeated string build_args = 8; 47 | } 48 | 49 | message MiddlewareRuntimeParams { 50 | // config is the configuration for the middleware encoded as a string. 51 | string config_string = 1; 52 | } 53 | 54 | message Middleware { 55 | // MiddlewareType is the type of the middleware. 56 | // Currently only PROXY_WASM is supported. 57 | enum MiddlewareType { 58 | PROXY_WASM = 0; 59 | } 60 | MiddlewareType type = 1; 61 | 62 | // slug is the unique name of the middleware. 63 | string slug = 2; 64 | 65 | // ingest_params are the parameters for ingesting the middleware. 66 | MiddlewareIngestParams ingest_params = 3; 67 | 68 | // runtime_params are the parameters for running the middleware. 69 | MiddlewareRuntimeParams runtime_params = 5; 70 | 71 | // status is the status of the middleware. 72 | enum MiddlewareStatus { 73 | UNKNOWN = 0; 74 | // PENDING means the middleware is being ingested. 75 | PENDING = 1; 76 | // READY means the middleware is ready to be used. 77 | READY = 2; 78 | // PENDING_READY means middleware is already gone to ready state and 79 | // new build is in progress. 80 | PENDING_READY = 3; 81 | // ERRORED means the middleware failed to ingest. 82 | ERRORED = 4; 83 | } 84 | MiddlewareStatus status = 6; 85 | 86 | string live_build_sha = 7; 87 | 88 | // created_at is the time the middleware was created. Ignored on create and update. 89 | google.protobuf.Timestamp created_at = 8; 90 | 91 | // updated_at is the time the middleware was updated. Ignored on create and update 92 | // (updated automatically). 93 | google.protobuf.Timestamp updated_at = 9; 94 | } 95 | 96 | message ListRequest { 97 | // page_token is the token for the next page of middleware. 98 | string page_token = 1; 99 | 100 | // page_size is the number of middleware to return in a page. 101 | int32 page_size = 2; 102 | } 103 | 104 | message ListResponse { 105 | repeated Middleware middlewares = 1; 106 | 107 | // next_page_token is the token for the next page of middleware. 108 | string next_page_token = 2; 109 | } 110 | 111 | message GetRequest { 112 | string slug = 1; 113 | } 114 | 115 | message CreateRequest { 116 | Middleware middleware = 1; 117 | } 118 | 119 | message UpdateRequest { 120 | Middleware middleware = 1; 121 | } 122 | 123 | message DeleteRequest { 124 | string slug = 1; 125 | } 126 | 127 | message Build { 128 | // For VCS-based sources sha of the commit used to build the middleware. For 129 | // direct sources, we used the hash of the source directory (recursively hashing 130 | // directory contents). 131 | string sha = 1; 132 | 133 | Middleware middleware = 2; 134 | 135 | enum BuildStatus { 136 | UNKNOWN = 0; 137 | // PREPARING means the build is being prepared. 138 | PREPARING = 1; 139 | // RUNNING means the build is being built. 140 | RUNNING = 2; 141 | // READY means the build was successfully built. 142 | READY = 3; 143 | // ERRORED means the build failed to build. 144 | ERRORED = 4; 145 | } 146 | BuildStatus status = 3; 147 | 148 | string status_detail = 4; 149 | 150 | // started_at is the time the build was started. 151 | google.protobuf.Timestamp started_at = 6; 152 | 153 | // updated_at is the time the build was updated. 154 | // (updated automatically). 155 | // If status is READY, this is the time the build was successfully built. 156 | // If status is ERRORED, this is the time the build errored (last attempt). 157 | google.protobuf.Timestamp updated_at = 7; 158 | } 159 | 160 | message ListBuildsRequest { 161 | string slug = 1; 162 | 163 | // page_token is the token for the next page of builds. 164 | string page_token = 2; 165 | 166 | // page_size is the number of builds to return in a page. 167 | int32 page_size = 3; 168 | } 169 | 170 | message ListBuildsResponse { 171 | repeated Build builds = 1; 172 | 173 | // next_page_token is the token for the next page of builds. 174 | string next_page_token = 2; 175 | } 176 | 177 | message GetBuildRequest { 178 | string slug = 1; 179 | string sha = 2; 180 | } 181 | 182 | message GetLiveBuildRequest { 183 | string slug = 1; 184 | } 185 | 186 | message TriggerBuildRequest { 187 | string slug = 1; 188 | } 189 | 190 | message GetBuildOutputRequest { 191 | string slug = 1; 192 | string sha = 2; 193 | string output_type = 3; 194 | } 195 | 196 | message GetLiveBuildOutputRequest { 197 | string slug = 1; 198 | string output_type = 2; 199 | } 200 | 201 | message BuildOutput { 202 | Build build = 1; 203 | bytes output = 2; 204 | } 205 | 206 | // MiddlewareService manages middleware lifecycle. 207 | service MiddlewareService { 208 | rpc List(ListRequest) returns (ListResponse) { 209 | option (google.api.http) = { 210 | get: "/v1/middlewares" 211 | }; 212 | } 213 | rpc Get(GetRequest) returns (Middleware) { 214 | option (google.api.http) = { 215 | get: "/v1/middlewares/{slug}" 216 | }; 217 | } 218 | rpc Create(CreateRequest) returns (Middleware) { 219 | option (google.api.http) = { 220 | post: "/v1/middlewares" 221 | body: "*" 222 | }; 223 | } 224 | 225 | rpc Update(UpdateRequest) returns (Middleware) { 226 | option (google.api.http) = { 227 | put: "/v1/middlewares/{middleware.slug}" 228 | body: "*" 229 | }; 230 | } 231 | rpc Delete(DeleteRequest) returns (google.protobuf.Empty) { 232 | option (google.api.http) = { 233 | delete: "/v1/middlewares/{slug}" 234 | }; 235 | } 236 | rpc TriggerBuild(TriggerBuildRequest) returns (google.protobuf.Empty) { 237 | option (google.api.http) = { 238 | post: "/v1/middlewares/{slug}/builds" 239 | body: "*" 240 | }; 241 | } 242 | rpc ListBuilds(ListBuildsRequest) returns (ListBuildsResponse) { 243 | option (google.api.http) = { 244 | get: "/v1/middlewares/{slug}/builds" 245 | }; 246 | } 247 | rpc GetBuild(GetBuildRequest) returns (Build) { 248 | option (google.api.http) = { 249 | get: "/v1/middlewares/{slug}/builds/{sha}" 250 | }; 251 | } 252 | rpc GetLiveBuild(GetLiveBuildRequest) returns (Build) { 253 | option (google.api.http) = { 254 | get: "/v1/middlewares/{slug}/builds/live" 255 | }; 256 | } 257 | rpc GetBuildOutput(GetBuildOutputRequest) returns (BuildOutput) { 258 | option (google.api.http) = { 259 | get: "/v1/middlewares/{slug}/builds/{sha}/{output_type}" 260 | }; 261 | } 262 | rpc GetLiveBuildOutput(GetLiveBuildOutputRequest) returns (BuildOutput) { 263 | option (google.api.http) = { 264 | get: "/v1/middlewares/{slug}/builds/live/{output_type}" 265 | }; 266 | } 267 | 268 | rpc InternalList(google.protobuf.Empty) returns (ListResponse) { 269 | option (google.api.http) = { 270 | get: "/v1/internal/middlewares" 271 | }; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /cmd/server/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | load("@io_bazel_rules_docker//go:image.bzl", "go_image") 3 | load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_push") 4 | 5 | go_library( 6 | name = "server_lib", 7 | srcs = [ 8 | "main.go", 9 | "temporalite.go", 10 | ], 11 | data = [ 12 | "envoy-bootstrap.yaml", 13 | "//frontend:build", 14 | ], 15 | importpath = "github.com/apoxy-dev/proximal/cmd/server", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//api/endpoint/v1:endpoint", 19 | "//api/logs/v1:logs", 20 | "//api/middleware/v1:middleware", 21 | "//core/envoy", 22 | "//core/log", 23 | "//core/server", 24 | "//server/api", 25 | "//server/db", 26 | "//server/envoy", 27 | "//server/ingest", 28 | "//server/watcher", 29 | "@com_github_temporalio_temporalite//:temporalite", 30 | "@com_github_temporalio_ui_server_v2//server", 31 | "@com_github_temporalio_ui_server_v2//server/config", 32 | "@com_github_temporalio_ui_server_v2//server/server_options", 33 | "@io_temporal_go_sdk//client", 34 | "@io_temporal_go_sdk//worker", 35 | "@io_temporal_go_server//common/config", 36 | "@io_temporal_go_server//common/log/tag", 37 | "@io_temporal_go_server//common/primitives", 38 | "@io_temporal_go_server//temporal", 39 | "@org_golang_google_grpc//:go_default_library", 40 | "@org_golang_google_grpc//credentials/insecure", 41 | "@org_golang_x_exp//slog", 42 | ], 43 | ) 44 | 45 | go_binary( 46 | name = "server", 47 | embed = [":server_lib"], 48 | visibility = ["//visibility:public"], 49 | ) 50 | 51 | go_image( 52 | name = "go.image", 53 | base = select({ 54 | "@platforms//cpu:arm64": "@base_image_arm64//image", 55 | "@platforms//cpu:x86_64": "@base_image_amd64//image", 56 | }), 57 | embed = [":server_lib"], 58 | # Workaround for https://github.com/bazelbuild/rules_go/issues/1706 59 | env = {"GODEBUG": "netdns=go"}, 60 | importpath = "github.com/apoxy-dev/proximal/cmd/server", 61 | ) 62 | 63 | container_image( 64 | name = "image", 65 | architecture = select({ 66 | "@platforms//cpu:arm64": "arm64", 67 | "@platforms//cpu:x86_64": "amd64", 68 | }), 69 | base = ":go.image", 70 | entrypoint = [ 71 | "/app/cmd/server/go.image.binary", 72 | ], 73 | ports = [ 74 | "8080", 75 | "9901", 76 | "9088", 77 | "18000", 78 | ], 79 | visibility = ["//visibility:public"], 80 | ) 81 | 82 | container_push( 83 | name = "publish", 84 | format = "Docker", 85 | image = ":image", 86 | registry = "docker.io", 87 | repository = "apoxy/proximal", 88 | tag = "{STABLE_GIT_SHA}-" + select({ 89 | "@platforms//cpu:arm64": "arm64", 90 | "@platforms//cpu:x86_64": "amd64", 91 | }), 92 | ) 93 | -------------------------------------------------------------------------------- /cmd/server/envoy-bootstrap.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | access_log_path: /dev/stdout 3 | address: 4 | socket_address: 5 | address: 0.0.0.0 6 | port_value: 9901 7 | dynamic_resources: 8 | cds_config: 9 | resource_api_version: V3 10 | api_config_source: 11 | api_type: GRPC 12 | transport_api_version: V3 13 | grpc_services: 14 | - envoy_grpc: 15 | cluster_name: xds_cluster 16 | set_node_on_first_message_only: true 17 | lds_config: 18 | resource_api_version: V3 19 | api_config_source: 20 | api_type: GRPC 21 | transport_api_version: V3 22 | grpc_services: 23 | - envoy_grpc: 24 | cluster_name: xds_cluster 25 | set_node_on_first_message_only: true 26 | static_resources: 27 | clusters: 28 | - connect_timeout: 1s 29 | load_assignment: 30 | cluster_name: xds_cluster 31 | endpoints: 32 | - lb_endpoints: 33 | - endpoint: 34 | address: 35 | socket_address: 36 | address: 127.0.0.1 37 | port_value: 2020 38 | http2_protocol_options: {} 39 | name: xds_cluster 40 | - connect_timeout: 1s 41 | load_assignment: 42 | cluster_name: als_cluster 43 | endpoints: 44 | - lb_endpoints: 45 | - endpoint: 46 | address: 47 | socket_address: 48 | address: 127.0.0.1 49 | port_value: 2020 50 | http2_protocol_options: {} 51 | name: als_cluster 52 | 53 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "runtime" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/temporalio/temporalite" 14 | tclient "go.temporal.io/sdk/client" 15 | "go.temporal.io/sdk/worker" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials/insecure" 18 | 19 | envoyrunner "github.com/apoxy-dev/proximal/core/envoy" 20 | "github.com/apoxy-dev/proximal/core/log" 21 | "github.com/apoxy-dev/proximal/core/server" 22 | "github.com/apoxy-dev/proximal/server/api" 23 | serverdb "github.com/apoxy-dev/proximal/server/db" 24 | "github.com/apoxy-dev/proximal/server/envoy" 25 | "github.com/apoxy-dev/proximal/server/ingest" 26 | "github.com/apoxy-dev/proximal/server/watcher" 27 | 28 | endpointv1 "github.com/apoxy-dev/proximal/api/endpoint/v1" 29 | logsv1 "github.com/apoxy-dev/proximal/api/logs/v1" 30 | middlewarev1 "github.com/apoxy-dev/proximal/api/middleware/v1" 31 | ) 32 | 33 | var ( 34 | temporalHost = flag.String("temporal_host", "localhost:8088", "Temporal host.") 35 | temporalNs = flag.String("temporal_namespace", "default", "Temporal namespace.") 36 | 37 | localMode = flag.Bool("local_mode", true, "Run in local mode. (Launches a Temporalite server.)") 38 | buildDir = flag.String("build_work_dir", "/tmp/proximal", "Base path for build artifacts.") 39 | watchIgnores = flag.String("watch_ignores", "^README.md$", "Comma-separated list of paths to ignore when watching for changes.") 40 | 41 | envoyPath = flag.String("envoy_path", "", "Path to Envoy binary.") 42 | xdsListenHost = flag.String("xds_host", "0.0.0.0", "XDS host.") 43 | xdsListenPort = flag.Int("xds_port", 18000, "XDS port.") 44 | xdsSyncInterval = flag.Duration("xds_sync_interval", 10*time.Second, "How often to sync XSDs snapshot.") 45 | ) 46 | 47 | func main() { 48 | s := server.NewApoxyServer( 49 | server.WithHandlers(middlewarev1.RegisterMiddlewareServiceHandlerFromEndpoint), 50 | server.WithHandlers(logsv1.RegisterLogsServiceHandlerFromEndpoint), 51 | server.WithHandlers(endpointv1.RegisterEndpointServiceHandlerFromEndpoint), 52 | ) 53 | 54 | var ts *temporalite.Server 55 | var err error 56 | if *localMode { 57 | if ts, err = createTemporaliteServer(); err != nil { 58 | log.Fatalf("error creating Temporalite Server: %v", err) 59 | } 60 | go func() { 61 | if err := ts.Start(); err != nil { 62 | log.Fatalf("error starting Temporalite Server: %v", err) 63 | } 64 | }() 65 | } 66 | 67 | tc, err := dialTemporalWithRetries(s.Context, *temporalHost, *temporalNs) 68 | if err != nil { 69 | log.Fatalf("tclient.Dial() error: %v", err) 70 | } 71 | defer tc.Close() 72 | 73 | wOpts := worker.Options{ 74 | MaxConcurrentActivityExecutionSize: runtime.NumCPU(), 75 | MaxConcurrentWorkflowTaskExecutionSize: runtime.NumCPU(), 76 | EnableSessionWorker: true, 77 | } 78 | w := worker.New(tc, ingest.MiddlewareIngestQueue, wOpts) 79 | 80 | w.RegisterWorkflow(ingest.StartBuildWorkflow) 81 | w.RegisterWorkflow(ingest.DoBuildWorkflow) 82 | 83 | db, err := serverdb.New() 84 | if err != nil { 85 | log.Fatalf("serverdb.New() error: %v", err) 86 | } 87 | defer db.Close() 88 | if err = serverdb.DoMigrations(); err != nil { 89 | log.Fatalf("serverdb.DoMigrations() error: %v", err) 90 | } 91 | 92 | grpcClient, err := grpc.DialContext( 93 | s.Context, 94 | fmt.Sprintf("127.0.0.1:%d", *server.GRPCPort), 95 | grpc.WithTransportCredentials(insecure.NewCredentials()), 96 | ) 97 | fw := watcher.NewWatcher( 98 | middlewarev1.NewMiddlewareServiceClient(grpcClient), 99 | *watchIgnores, 100 | ) 101 | go func() { 102 | if err := fw.Run(s.Context); err != nil { 103 | log.Errorf("fw.Run() error: %v", err) 104 | } 105 | }() 106 | 107 | ww := ingest.NewIngestWorker(*buildDir, db, fw) 108 | w.RegisterActivity(ww.PrepareGithubBuildActivity) 109 | w.RegisterActivity(ww.PrepareLocalBuildActivity) 110 | w.RegisterActivity(ww.BuildActivity) 111 | w.RegisterActivity(ww.UploadWasmOutputActivity) 112 | w.RegisterActivity(ww.FinalizeActivity) 113 | go func() { 114 | err = w.Run(worker.InterruptCh()) 115 | if err != nil { 116 | log.Fatalf("w.Run() error: %v", err) 117 | } 118 | }() 119 | 120 | lsvc := api.NewLogsService(db) 121 | lsvc.RegisterALS(s.GRPC) 122 | 123 | middlewarev1.RegisterMiddlewareServiceServer(s.GRPC, api.NewMiddlewareService(db, tc, fw)) 124 | logsv1.RegisterLogsServiceServer(s.GRPC, lsvc) 125 | endpointv1.RegisterEndpointServiceServer(s.GRPC, api.NewEndpointService(db, tc)) 126 | 127 | envoyMgr := envoy.NewSnapshotManager( 128 | s.Context, 129 | middlewarev1.NewMiddlewareServiceClient(grpcClient), 130 | endpointv1.NewEndpointServiceClient(grpcClient), 131 | *buildDir, 132 | *xdsListenHost, 133 | *xdsListenPort, 134 | *xdsSyncInterval, 135 | ) 136 | envoyMgr.RegisterXDS(s.GRPC) 137 | go func() { 138 | if err := envoyMgr.Run(s.Context); err != nil { 139 | log.Fatalf("envoyMgr.Run() error: %v", err) 140 | } 141 | }() 142 | 143 | e := &envoyrunner.Runtime{ 144 | EnvoyPath: *envoyPath, 145 | BootstrapConfigPath: "./cmd/server/envoy-bootstrap.yaml", 146 | Release: &envoyrunner.Release{ 147 | Version: "v1.26.3", 148 | }, 149 | } 150 | go func() { 151 | if err := e.Run(s.Context); err != nil { 152 | log.Fatalf("e.Run() error: %v", err) 153 | } 154 | }() 155 | 156 | go func() { 157 | exitCh := make(chan os.Signal, 1) // Buffered because sender is not waiting. 158 | signal.Notify(exitCh, syscall.SIGINT, syscall.SIGTERM) 159 | select { 160 | case <-exitCh: 161 | case <-s.Context.Done(): 162 | } 163 | 164 | if *localMode { 165 | ts.Stop() 166 | } 167 | 168 | s.Shutdown() 169 | }() 170 | 171 | s.Run() 172 | } 173 | 174 | func dialTemporalWithRetries(ctx context.Context, hostPort, namespace string) (tclient.Client, error) { 175 | var tc tclient.Client 176 | var err error 177 | for i := 0; i < 10; i++ { 178 | tc, err = tclient.Dial(tclient.Options{ 179 | HostPort: hostPort, 180 | Namespace: namespace, 181 | ConnectionOptions: tclient.ConnectionOptions{ 182 | EnableKeepAliveCheck: true, 183 | }, 184 | }) 185 | if err == nil { 186 | return tc, nil 187 | } 188 | log.Errorf("tclient.Dial() error: %v", err) 189 | select { 190 | case <-ctx.Done(): 191 | return nil, ctx.Err() 192 | case <-time.After(time.Second): 193 | } 194 | } 195 | return nil, err 196 | } 197 | -------------------------------------------------------------------------------- /cmd/server/temporalite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/temporalio/temporalite" 9 | uiserver "github.com/temporalio/ui-server/v2/server" 10 | uiconfig "github.com/temporalio/ui-server/v2/server/config" 11 | uiserveroptions "github.com/temporalio/ui-server/v2/server/server_options" 12 | temporalconfig "go.temporal.io/server/common/config" 13 | "go.temporal.io/server/common/log/tag" 14 | "go.temporal.io/server/common/primitives" 15 | "go.temporal.io/server/temporal" 16 | "golang.org/x/exp/slog" 17 | ) 18 | 19 | var ( 20 | temporalitePort = flag.Int("temporalite_port", 8088, "Temporalite port (local mode only).") 21 | temporaliteUIPort = flag.Int("temporalite_ui_port", 9088, "Temporalite UI port (local mode only).") 22 | temporaliteEphem = flag.Bool( 23 | "temporalite_ephemeral", 24 | false, 25 | "Use an ephemeral database for Temporalite (local mode only).", 26 | ) 27 | temporaliteDB = flag.String("temporalite_db", "temporalite.db", "Temporalite database file path.") 28 | ) 29 | 30 | func createTemporaliteServer() (*temporalite.Server, error) { 31 | opts := []temporalite.ServerOption{ 32 | temporalite.WithDynamicPorts(), 33 | temporalite.WithNamespaces(*temporalNs), 34 | temporalite.WithFrontendPort(*temporalitePort), 35 | temporalite.WithFrontendIP("0.0.0.0"), 36 | temporalite.WithUpstreamOptions( 37 | temporal.WithLogger(temporaliteLogger{}), 38 | temporal.ForServices([]string{ 39 | string(primitives.FrontendService), 40 | string(primitives.HistoryService), 41 | string(primitives.MatchingService), 42 | string(primitives.WorkerService), 43 | }), 44 | ), 45 | temporalite.WithBaseConfig(&temporalconfig.Config{}), 46 | temporalite.WithUI(uiserver.NewServer(uiserveroptions.WithConfigProvider(&uiconfig.Config{ 47 | Host: "0.0.0.0", 48 | Port: *temporaliteUIPort, 49 | TemporalGRPCAddress: fmt.Sprintf("0.0.0.0:%d", *temporalitePort), 50 | EnableUI: true, 51 | }))), 52 | } 53 | if *temporaliteEphem { 54 | opts = append(opts, temporalite.WithPersistenceDisabled()) 55 | } else { 56 | opts = append(opts, temporalite.WithDatabaseFilePath(*temporaliteDB)) 57 | } 58 | return temporalite.NewServer(opts...) 59 | } 60 | 61 | type temporaliteLogger struct{} 62 | 63 | func (l temporaliteLogger) kv(msg string, tags []tag.Tag) []interface{} { 64 | kvs := make([]interface{}, len(tags)) 65 | for i, tag := range tags { 66 | kvs[i] = slog.Any(tag.Key(), tag.Value()) 67 | } 68 | return kvs 69 | } 70 | 71 | func (l temporaliteLogger) Debug(msg string, tags ...tag.Tag) { 72 | slog.Debug(msg, l.kv(msg, tags)...) 73 | } 74 | 75 | func (l temporaliteLogger) Info(msg string, tags ...tag.Tag) { 76 | slog.Info(msg, l.kv(msg, tags)...) 77 | } 78 | 79 | func (l temporaliteLogger) Warn(msg string, tags ...tag.Tag) { 80 | slog.Warn(msg, l.kv(msg, tags)...) 81 | } 82 | 83 | func (l temporaliteLogger) Error(msg string, tags ...tag.Tag) { 84 | slog.Error(msg, l.kv(msg, tags)...) 85 | } 86 | 87 | func (l temporaliteLogger) DPanic(msg string, tags ...tag.Tag) { 88 | panic(fmt.Sprintf(msg, l.kv(msg, tags)...)) 89 | } 90 | 91 | func (l temporaliteLogger) Panic(msg string, tags ...tag.Tag) { 92 | panic(fmt.Sprintf(msg, l.kv(msg, tags)...)) 93 | } 94 | 95 | func (l temporaliteLogger) Fatal(msg string, tags ...tag.Tag) { 96 | slog.Error(msg, l.kv(msg, tags)...) 97 | os.Exit(1) 98 | } 99 | -------------------------------------------------------------------------------- /core/envoy/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "envoy", 5 | srcs = ["run.go"], 6 | importpath = "github.com/apoxy-dev/proximal/core/envoy", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//core/log", 10 | "@com_github_google_uuid//:uuid", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /core/envoy/run.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "runtime" 12 | 13 | "github.com/apoxy-dev/proximal/core/log" 14 | "github.com/google/uuid" 15 | ) 16 | 17 | const ( 18 | githubURL = "github.com/envoyproxy/envoy/releases/download" 19 | ) 20 | 21 | var ( 22 | goArchToPlatform = map[string]string{ 23 | "amd64": "x86_64", 24 | "arm64": "aarch_64", 25 | } 26 | ) 27 | 28 | type Release struct { 29 | Version string 30 | Sha string 31 | } 32 | 33 | func (r *Release) String() string { 34 | if r.Sha == "" { 35 | return r.Version 36 | } 37 | return fmt.Sprintf("%s@sha256:%s", r.Version, r.Sha) 38 | } 39 | 40 | func (r *Release) DownloadBinaryFromGitHub(ctx context.Context) (io.ReadCloser, error) { 41 | downloadURL := filepath.Join( 42 | githubURL, 43 | r.Version, 44 | fmt.Sprintf("envoy-%s-%s-%s", r.Version[1:], runtime.GOOS, goArchToPlatform[runtime.GOARCH]), 45 | ) 46 | 47 | log.Infof("downloading envoy %s from https://%s", r, downloadURL) 48 | 49 | resp, err := http.Get("https://" + downloadURL) 50 | if err != nil { 51 | return nil, fmt.Errorf("failed to download envoy: %w", err) 52 | } 53 | return resp.Body, nil 54 | } 55 | 56 | // Runtime vendors the Envoy binary and runs it. 57 | type Runtime struct { 58 | EnvoyPath string 59 | BootstrapConfigPath string 60 | BootstrapConfigYAML string 61 | Release *Release 62 | // Args are additional arguments to pass to Envoy. 63 | Args []string 64 | 65 | cmd *exec.Cmd 66 | } 67 | 68 | func (r *Runtime) run(ctx context.Context) error { 69 | id := uuid.New().String() 70 | configYAML := fmt.Sprintf(`node: { id: "%s", cluster: "proximal" }`, id) 71 | if r.BootstrapConfigYAML != "" { 72 | configYAML = r.BootstrapConfigYAML 73 | } 74 | log.Infof("envoy YAML config: %s", configYAML) 75 | 76 | args := []string{ 77 | "--config-yaml", configYAML, 78 | } 79 | 80 | if r.BootstrapConfigPath != "" { 81 | args = append(args, "-c", r.BootstrapConfigPath) 82 | } 83 | 84 | args = append(args, r.Args...) 85 | 86 | r.cmd = exec.CommandContext(ctx, r.envoyPath(), args...) 87 | r.cmd.Stdout = os.Stdout 88 | r.cmd.Stderr = os.Stderr 89 | 90 | if err := r.cmd.Start(); err != nil { 91 | return fmt.Errorf("failed to start envoy: %w", err) 92 | } 93 | 94 | // Restart envoy if it exits. 95 | if err := r.cmd.Wait(); err != nil { 96 | return fmt.Errorf("envoy exited with error: %w", err) 97 | } 98 | 99 | return nil 100 | } 101 | 102 | // envoyPath returns the path to the Envoy binary. If EnvoyPath is set, it will 103 | // be used. Otherwise, the binary will be downloaded and cached in 104 | // ~/.proximal/envoy for each release. 105 | func (r *Runtime) envoyPath() string { 106 | if r.EnvoyPath != "" { 107 | return r.EnvoyPath 108 | } 109 | return filepath.Join(os.Getenv("HOME"), ".proximal", "envoy", r.Release.String(), "bin", "envoy") 110 | } 111 | 112 | // vendorEnvoyIfNotExists vendors the Envoy binary for the release if it does 113 | // not exist. 114 | func (r *Runtime) vendorEnvoyIfNotExists(ctx context.Context) error { 115 | if _, err := os.Stat(r.envoyPath()); err == nil { 116 | return nil 117 | } 118 | 119 | // Download the Envoy binary for the release. 120 | bin, err := r.Release.DownloadBinaryFromGitHub(ctx) 121 | if err != nil { 122 | return fmt.Errorf("failed to download envoy: %w", err) 123 | } 124 | defer bin.Close() 125 | 126 | // Extract the Envoy binary. 127 | if err := os.MkdirAll(filepath.Dir(r.envoyPath()), 0755); err != nil { 128 | return fmt.Errorf("failed to create envoy directory: %w", err) 129 | } 130 | w, err := os.OpenFile(r.envoyPath(), os.O_CREATE|os.O_WRONLY, 0755) 131 | if err != nil { 132 | return fmt.Errorf("failed to open envoy: %w", err) 133 | } 134 | defer w.Close() 135 | if _, err := io.Copy(w, bin); err != nil { 136 | return fmt.Errorf("failed to copy envoy: %w", err) 137 | } 138 | if err := os.Chmod(r.envoyPath(), 0755); err != nil { 139 | return fmt.Errorf("failed to chmod envoy: %w", err) 140 | } 141 | 142 | if err := os.MkdirAll(filepath.Dir(r.BootstrapConfigPath), 0755); err != nil { 143 | return fmt.Errorf("failed to create envoy directory: %w", err) 144 | } 145 | 146 | return nil 147 | } 148 | 149 | // Run runs the Envoy binary. 150 | func (r *Runtime) Run(ctx context.Context) error { 151 | log.Infof("running envoy %s", r.Release) 152 | 153 | if err := r.vendorEnvoyIfNotExists(ctx); err != nil { 154 | return fmt.Errorf("failed to vendor envoy: %w", err) 155 | } 156 | 157 | // Run the Envoy binary. 158 | for { 159 | select { 160 | case <-ctx.Done(): 161 | return nil 162 | default: 163 | } 164 | 165 | exitCh := make(chan struct{}) 166 | go func() { 167 | if err := r.run(ctx); err != nil { 168 | log.Errorf("envoy exited with error: %v", err) 169 | } 170 | close(exitCh) 171 | }() 172 | 173 | select { 174 | case <-ctx.Done(): 175 | return nil 176 | case <-exitCh: 177 | } 178 | } 179 | 180 | return nil 181 | } 182 | 183 | // Stop stops the Envoy process. 184 | func (r *Runtime) Stop() error { 185 | if r.cmd == nil { 186 | return nil 187 | } 188 | return r.cmd.Process.Kill() 189 | } 190 | -------------------------------------------------------------------------------- /core/ids/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "ids", 5 | srcs = ["uuid.go"], 6 | importpath = "github.com/apoxy-dev/proximal/core/ids", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_google_uuid//:uuid", 10 | "@com_github_martinlindhe_base36//:base36", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /core/ids/uuid.go: -------------------------------------------------------------------------------- 1 | package ids 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/google/uuid" 8 | "github.com/martinlindhe/base36" 9 | ) 10 | 11 | func uuidToExternalID(id uuid.UUID) string { 12 | a := [16]byte(id) 13 | return strings.ToLower(base36.EncodeBytes(a[:])) 14 | } 15 | 16 | func externalIDToUUID(id string) (uuid.UUID, error) { 17 | dec := base36.DecodeToBytes(strings.ToUpper(id)) 18 | return uuid.FromBytes(dec) 19 | } 20 | 21 | type IDPrefix string 22 | 23 | const ( 24 | Key IDPrefix = "k" 25 | ) 26 | 27 | type PrefixedID struct { 28 | Prefix IDPrefix 29 | ID uuid.UUID 30 | } 31 | 32 | func ParsePrefixedID(id string) (PrefixedID, error) { 33 | prefix := IDPrefix(id[0]) 34 | uid, err := externalIDToUUID(id[1:]) 35 | return PrefixedID{ 36 | Prefix: prefix, 37 | ID: uid, 38 | }, err 39 | } 40 | 41 | func (pid *PrefixedID) String() string { 42 | return fmt.Sprintf("%s%s", pid.Prefix, uuidToExternalID(pid.ID)) 43 | } 44 | 45 | // NewKey returns a new PrefixedID with the Key prefix and a new uuid. 46 | func NewKey() PrefixedID { 47 | return PrefixedID{ 48 | Prefix: Key, 49 | ID: uuid.New(), 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/log/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "log", 5 | srcs = ["logger.go"], 6 | importpath = "github.com/apoxy-dev/proximal/core/log", 7 | visibility = ["//visibility:public"], 8 | deps = ["@org_golang_x_exp//slog"], 9 | ) 10 | -------------------------------------------------------------------------------- /core/log/logger.go: -------------------------------------------------------------------------------- 1 | // Package log provides logging routines based on slog package. 2 | package log 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "time" 11 | 12 | "golang.org/x/exp/slog" 13 | ) 14 | 15 | var logger *slog.Logger 16 | 17 | func init() { 18 | replace := func(groups []string, a slog.Attr) slog.Attr { 19 | // Remove time. 20 | if a.Key == slog.TimeKey && len(groups) == 0 { 21 | return slog.Attr{} 22 | } 23 | // Remove the directory from the source's filename. 24 | if a.Key == slog.SourceKey { 25 | a.Value = slog.StringValue(filepath.Base(a.Value.String())) 26 | } 27 | return a 28 | } 29 | logger = slog.New(slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}.NewTextHandler(os.Stdout)) 30 | slog.SetDefault(logger) 31 | } 32 | 33 | func logf(level slog.Level, format string, args ...any) { 34 | ctx := context.Background() 35 | if !logger.Enabled(ctx, level) { 36 | return 37 | } 38 | var pcs [1]uintptr 39 | runtime.Callers(3, pcs[:]) // skip [Callers, logf, Infof] 40 | r := slog.NewRecord(time.Now(), level, fmt.Sprintf(format, args...), pcs[0]) 41 | _ = logger.Handler().Handle(ctx, r) 42 | } 43 | 44 | // Debugf logs a debug message. 45 | func Debugf(format string, args ...any) { 46 | level := slog.LevelDebug 47 | logf(level, format, args...) 48 | } 49 | 50 | // Infof logs an info message. 51 | func Infof(format string, args ...any) { 52 | level := slog.LevelInfo 53 | logf(level, format, args...) 54 | } 55 | 56 | // Warnf logs a warning message. 57 | func Warnf(format string, args ...any) { 58 | level := slog.LevelWarn 59 | logf(level, format, args...) 60 | } 61 | 62 | // Errorf logs an error message. 63 | func Errorf(format string, args ...any) { 64 | level := slog.LevelError 65 | logf(level, format, args...) 66 | } 67 | 68 | // Fatalf logs a fatal message. 69 | func Fatalf(format string, args ...any) { 70 | level := slog.LevelError 71 | logf(level, format, args...) 72 | os.Exit(1) 73 | } 74 | -------------------------------------------------------------------------------- /core/server/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "server", 5 | srcs = [ 6 | "middleware.go", 7 | "server.go", 8 | ], 9 | importpath = "github.com/apoxy-dev/proximal/core/server", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "@com_github_gogo_status//:status", 13 | "@com_github_grpc_ecosystem_grpc_gateway_v2//runtime", 14 | "@org_golang_google_grpc//:go_default_library", 15 | "@org_golang_google_grpc//codes", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /core/server/middleware.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/gogo/status" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/codes" 10 | ) 11 | 12 | // Validator contains methods generated by the validator plugin. 13 | type Validator interface { 14 | Validate() error 15 | } 16 | 17 | // ServerValidationUnaryInterceptor returns a new unary server interceptor that validates incoming requests. 18 | func ServerValidationUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 19 | if r, ok := req.(Validator); ok { 20 | if err := r.Validate(); err != nil { 21 | log.Printf("ERROR: Validate: %+v", err) 22 | return nil, status.Error(codes.InvalidArgument, err.Error()) 23 | } 24 | } 25 | 26 | return handler(ctx, req) 27 | } 28 | -------------------------------------------------------------------------------- /core/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | "net/http/pprof" 12 | "os" 13 | "os/signal" 14 | "strings" 15 | "sync" 16 | "syscall" 17 | "time" 18 | 19 | runtime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 20 | "google.golang.org/grpc" 21 | ) 22 | 23 | var ( 24 | GRPCPort = flag.Int("grpc_port", 2020, "Port of a gRPC listener.") 25 | GatewayPort = flag.Int("grpc_gateway_port", 8080, "Port of a gRPC gateway instance.") 26 | HTTPPort = flag.Int("http_port", 8888, "Port of a HTTP listener.") 27 | ShutdownWait = flag.Duration("shutdown_wait", 15*time.Second, "How long to wait for server connections to drain.") 28 | CORSAllowAll = flag.Bool("cors_allow_all", true, "Set CORS headers to allow all requests?") 29 | SlowReplies = flag.Bool("slow_replies", false, "Make all requests take an extra second.") 30 | StaticDir = flag.String("static_dir", "./frontend/build", "Directory to serve static files from.") 31 | 32 | passedHeaders = map[string]struct{}{ 33 | // Add headers here that you need to pass to the gRPC handler. For example: 34 | // "stripe-signature": {}, 35 | } 36 | ) 37 | 38 | func slowReplyWrapper(h http.Handler) http.Handler { 39 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 | if *SlowReplies { 41 | time.Sleep(1 * time.Second) 42 | } 43 | h.ServeHTTP(w, r) 44 | }) 45 | } 46 | 47 | func fileExists(filename string) bool { 48 | info, err := os.Stat(filename) 49 | if os.IsNotExist(err) { 50 | return false 51 | } 52 | return !info.IsDir() 53 | } 54 | 55 | func staticFileServer(h http.Handler) http.Handler { 56 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 57 | if strings.HasPrefix(r.URL.Path, "/v1/") { 58 | h.ServeHTTP(w, r) 59 | return 60 | } else { 61 | if r.URL.Path == "/" || !fileExists(*StaticDir+"/"+r.URL.Path) { 62 | http.ServeFile(w, r, *StaticDir+"/index.html") 63 | } else { 64 | http.FileServer(http.Dir(*StaticDir)).ServeHTTP(w, r) 65 | } 66 | } 67 | }) 68 | } 69 | 70 | func corsAllowAllWrapper(h http.Handler) http.Handler { 71 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 72 | if r.Method == "OPTIONS" && *CORSAllowAll { 73 | w.Header().Add("Access-Control-Allow-Origin", "*") 74 | w.Header().Add("Access-Control-Allow-Credentials", "true") 75 | w.Header().Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE") 76 | w.Header().Add("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) 77 | w.WriteHeader(http.StatusNoContent) 78 | return 79 | } else if r.Method != "OPTIONS" && *CORSAllowAll { 80 | w.Header().Add("Access-Control-Allow-Origin", "*") 81 | w.Header().Add("Access-Control-Allow-Credentials", "true") 82 | } 83 | h.ServeHTTP(w, r) 84 | }) 85 | } 86 | 87 | type GatewayHandlerFn func(context.Context, *runtime.ServeMux, string, []grpc.DialOption) error 88 | 89 | type ApoxyServer struct { 90 | Mux *http.ServeMux 91 | Srv *http.Server 92 | Context context.Context 93 | ctxCancel context.CancelFunc 94 | 95 | GRPC *grpc.Server 96 | 97 | Gateway *runtime.ServeMux 98 | GwSrv *http.Server 99 | 100 | l sync.Mutex 101 | terminating bool 102 | } 103 | 104 | type serverOptions struct { 105 | handlers []GatewayHandlerFn 106 | passedHeaders map[string]struct{} 107 | } 108 | 109 | func defaultServerOptions() *serverOptions { 110 | return &serverOptions{ 111 | passedHeaders: passedHeaders, 112 | } 113 | } 114 | 115 | // ServerOption sets Apoxy server options. 116 | type ServerOption func(*serverOptions) 117 | 118 | // WithHandlers appends handlers to the list of gRPC handlers of the server. 119 | func WithHandlers(handlers ...GatewayHandlerFn) ServerOption { 120 | return func(opts *serverOptions) { 121 | opts.handlers = append(opts.handlers, handlers...) 122 | } 123 | } 124 | 125 | // WithPassedHeader enables passing of HTTP header to the corresponding gRPC handler 126 | // via context. The header will be prefixed with grpcserver-. 127 | func WithPassedHeader(key string) ServerOption { 128 | return func(opts *serverOptions) { 129 | opts.passedHeaders[strings.ToLower(key)] = struct{}{} 130 | } 131 | } 132 | 133 | // attachPprofHandlers attaches pprof handlers to the server. 134 | func attachPprofHandlers(mux *http.ServeMux) { 135 | mux.HandleFunc("/debug/pprof/", http.HandlerFunc(pprof.Index)) 136 | mux.HandleFunc("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) 137 | mux.HandleFunc("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) 138 | mux.HandleFunc("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) 139 | mux.HandleFunc("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) 140 | } 141 | 142 | // NewApoxyServer returns a new gRPC server. 143 | func NewApoxyServer(opts ...ServerOption) *ApoxyServer { 144 | flag.Parse() 145 | rand.Seed(time.Now().UnixNano()) 146 | 147 | sOpts := defaultServerOptions() 148 | for _, o := range opts { 149 | o(sOpts) 150 | } 151 | 152 | s := &ApoxyServer{} 153 | s.Context, s.ctxCancel = context.WithCancel(context.Background()) 154 | s.Mux = http.NewServeMux() 155 | s.Srv = &http.Server{ 156 | Addr: fmt.Sprintf(":%d", *HTTPPort), 157 | Handler: s.Mux, 158 | ReadTimeout: 10 * time.Second, 159 | WriteTimeout: 30 * time.Second, 160 | MaxHeaderBytes: 1 << 20, 161 | } 162 | attachPprofHandlers(s.Mux) 163 | 164 | jsonpb := &runtime.JSONPb{} 165 | s.Gateway = runtime.NewServeMux( 166 | runtime.WithMarshalerOption(runtime.MIMEWildcard, jsonpb), 167 | runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) { 168 | if _, ok := sOpts.passedHeaders[strings.ToLower(key)]; ok { 169 | return runtime.MetadataPrefix + key, true 170 | } 171 | return runtime.DefaultHeaderMatcher(key) 172 | }), 173 | ) 174 | s.GwSrv = &http.Server{ 175 | Addr: fmt.Sprintf(":%d", *GatewayPort), 176 | Handler: staticFileServer(slowReplyWrapper(corsAllowAllWrapper(s.Gateway))), 177 | ReadTimeout: 10 * time.Second, 178 | WriteTimeout: 30 * time.Second, 179 | MaxHeaderBytes: 1 << 20, 180 | } 181 | gwOpts := []grpc.DialOption{grpc.WithInsecure()} 182 | for _, h := range sOpts.handlers { 183 | h(s.Context, s.Gateway, fmt.Sprintf("localhost:%d", *GRPCPort), gwOpts) 184 | } 185 | 186 | gOpts := []grpc.ServerOption{ 187 | grpc.ChainUnaryInterceptor( 188 | ServerValidationUnaryInterceptor, 189 | ), 190 | } 191 | s.GRPC = grpc.NewServer(gOpts...) 192 | 193 | return s 194 | } 195 | 196 | func (s *ApoxyServer) Run() { 197 | doneCh := make(chan struct{}) 198 | go func() { 199 | exitCh := make(chan os.Signal, 1) // Buffered because sender is not waiting. 200 | signal.Notify(exitCh, syscall.SIGTERM) 201 | select { 202 | case <-exitCh: 203 | case <-s.Context.Done(): 204 | } 205 | 206 | sCtx, cancelFn := context.WithTimeout(s.Context, *ShutdownWait) 207 | defer cancelFn() 208 | if err := s.GwSrv.Shutdown(sCtx); err != nil { 209 | // Error from closing listeners, or context timeout. 210 | log.Printf("gateway server shutdown: %v\n", err) 211 | } 212 | if err := s.Srv.Shutdown(sCtx); err != nil { 213 | log.Printf("HTTP server shutdown: %v\n", err) 214 | } 215 | s.GRPC.GracefulStop() // Block until all existing grpc connections return. 216 | close(doneCh) 217 | }() 218 | 219 | l, err := net.Listen("tcp", fmt.Sprintf(":%d", *GRPCPort)) 220 | if err != nil { 221 | log.Fatalf("Failed to listen on gRPC port %d: %v", *GRPCPort, err) 222 | } 223 | log.Printf("starting gRPC server on port: %d\n", *GRPCPort) 224 | go s.GRPC.Serve(l) 225 | 226 | go func() { 227 | log.Printf("starting http server on port: %d\n", *HTTPPort) 228 | if err := s.Srv.ListenAndServe(); err != http.ErrServerClosed { 229 | // Error starting or closing listener. 230 | log.Fatalf("HTTP server ListenAndServe: %v", err) 231 | } 232 | }() 233 | 234 | log.Printf("starting gateway server on port: %d\n", *GatewayPort) 235 | if err := s.GwSrv.ListenAndServe(); err != http.ErrServerClosed { 236 | log.Fatalf("gateway server ListenAndServe: %v", err) 237 | } 238 | 239 | <-doneCh 240 | } 241 | 242 | func (s *ApoxyServer) Shutdown() { 243 | s.l.Lock() 244 | defer s.l.Unlock() 245 | if !s.terminating { 246 | s.ctxCancel() 247 | } 248 | s.terminating = true 249 | } 250 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_API_HOST=http://localhost:8080 2 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | 'env': { 4 | 'browser': true, 5 | 'es2021': true, 6 | }, 7 | 'extends': [ 8 | 'eslint:recommended', 9 | ], 10 | 'settings': { 11 | 'react': { 12 | 'version': 'detect', 13 | }, 14 | }, 15 | 'parserOptions': { 16 | 'ecmaFeatures': { 17 | 'jsx': true, 18 | }, 19 | 'ecmaVersion': 12, 20 | 'sourceType': 'module', 21 | }, 22 | 'rules': { 23 | 'indent': [ 24 | 'error', 25 | 2, 26 | { 'SwitchCase': 1 }, 27 | ], 28 | 'linebreak-style': [ 29 | 'error', 30 | 'unix', 31 | ], 32 | 'quotes': [ 33 | 'error', 34 | 'single', 35 | ], 36 | 'semi': [ 37 | 'error', 38 | 'always', 39 | ], 40 | 'comma-dangle': [ 41 | 'error', 42 | 'only-multiline', 43 | ], 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /frontend/BUILD.bazel: -------------------------------------------------------------------------------- 1 | filegroup( 2 | name = "build", 3 | srcs = glob(["build/**"]), 4 | visibility = ["//visibility:public"], 5 | ) 6 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /frontend/build/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/build/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/build/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/build/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/build/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/build/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.e6c13ad2.css", 4 | "main.js": "/static/js/main.55d208c5.js", 5 | "index.html": "/index.html", 6 | "main.e6c13ad2.css.map": "/static/css/main.e6c13ad2.css.map", 7 | "main.55d208c5.js.map": "/static/js/main.55d208c5.js.map" 8 | }, 9 | "entrypoints": [ 10 | "static/css/main.e6c13ad2.css", 11 | "static/js/main.55d208c5.js" 12 | ] 13 | } -------------------------------------------------------------------------------- /frontend/build/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/build/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/build/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/build/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/build/favicon.ico -------------------------------------------------------------------------------- /frontend/build/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/build/index.html: -------------------------------------------------------------------------------- 1 | Proximal
-------------------------------------------------------------------------------- /frontend/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/build/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"Proximal","short_name":"Proximal","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} 2 | -------------------------------------------------------------------------------- /frontend/build/static/css/main.e6c13ad2.css: -------------------------------------------------------------------------------- 1 | body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace} 2 | /*# sourceMappingURL=main.e6c13ad2.css.map*/ -------------------------------------------------------------------------------- /frontend/build/static/css/main.e6c13ad2.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"static/css/main.e6c13ad2.css","mappings":"AAAA,KAKE,kCAAmC,CACnC,iCAAkC,CAJlC,mIAEY,CAHZ,QAMF,CAEA,KACE,uEAEF","sources":["index.css"],"sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n"],"names":[],"sourceRoot":""} -------------------------------------------------------------------------------- /frontend/build/static/js/main.55d208c5.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 2 | 3 | /** 4 | * @license React 5 | * react-dom.production.min.js 6 | * 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | */ 12 | 13 | /** 14 | * @license React 15 | * react-is.production.min.js 16 | * 17 | * Copyright (c) Facebook, Inc. and its affiliates. 18 | * 19 | * This source code is licensed under the MIT license found in the 20 | * LICENSE file in the root directory of this source tree. 21 | */ 22 | 23 | /** 24 | * @license React 25 | * react-jsx-runtime.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** 34 | * @license React 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** 44 | * @license React 45 | * scheduler.production.min.js 46 | * 47 | * Copyright (c) Facebook, Inc. and its affiliates. 48 | * 49 | * This source code is licensed under the MIT license found in the 50 | * LICENSE file in the root directory of this source tree. 51 | */ 52 | 53 | /** 54 | * @mui/styled-engine v5.13.2 55 | * 56 | * @license MIT 57 | * This source code is licensed under the MIT license found in the 58 | * LICENSE file in the root directory of this source tree. 59 | */ 60 | 61 | /** 62 | * @remix-run/router v1.7.2 63 | * 64 | * Copyright (c) Remix Software Inc. 65 | * 66 | * This source code is licensed under the MIT license found in the 67 | * LICENSE.md file in the root directory of this source tree. 68 | * 69 | * @license MIT 70 | */ 71 | 72 | /** 73 | * React Router DOM v6.14.2 74 | * 75 | * Copyright (c) Remix Software Inc. 76 | * 77 | * This source code is licensed under the MIT license found in the 78 | * LICENSE.md file in the root directory of this source tree. 79 | * 80 | * @license MIT 81 | */ 82 | 83 | /** 84 | * React Router v6.14.2 85 | * 86 | * Copyright (c) Remix Software Inc. 87 | * 88 | * This source code is licensed under the MIT license found in the 89 | * LICENSE.md file in the root directory of this source tree. 90 | * 91 | * @license MIT 92 | */ 93 | 94 | /** 95 | * uuidv7: An experimental implementation of the proposed UUID Version 7 96 | * 97 | * @license Apache-2.0 98 | * @copyright 2021-2023 LiosK 99 | * @packageDocumentation 100 | * 101 | * from https://github.com/LiosK/uuidv7/blob/e501462ea3d23241de13192ceae726956f9b3b7d/src/index.ts 102 | */ 103 | 104 | /** @license React v16.13.1 105 | * react-is.production.min.js 106 | * 107 | * Copyright (c) Facebook, Inc. and its affiliates. 108 | * 109 | * This source code is licensed under the MIT license found in the 110 | * LICENSE file in the root directory of this source tree. 111 | */ 112 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 7 | "@emotion/react": "^11.11.1", 8 | "@emotion/styled": "^11.11.0", 9 | "@mui/icons-material": "^5.13.7", 10 | "@mui/material": "^5.13.7", 11 | "@testing-library/jest-dom": "^5.16.5", 12 | "@testing-library/react": "^13.4.0", 13 | "@testing-library/user-event": "^13.5.0", 14 | "iconsax-react": "^0.0.8", 15 | "posthog-js": "^1.72.1", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "react-router-dom": "^6.14.1", 19 | "react-scripts": "5.0.1", 20 | "web-vitals": "^2.1.4" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | Proximal 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"Proximal","short_name":"Proximal","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} 2 | -------------------------------------------------------------------------------- /frontend/src/api/Endpoints.js: -------------------------------------------------------------------------------- 1 | const List = async () => { 2 | return fetch(process.env.REACT_APP_API_HOST + '/v1/endpoints').then((res) => res.json()); 3 | }; 4 | 5 | const Create = async (endpoint) => { 6 | return fetch(process.env.REACT_APP_API_HOST + '/v1/endpoints', { 7 | method: 'POST', 8 | headers: { 9 | 'Content-Type': 'application/json', 10 | }, 11 | body: JSON.stringify({ endpoint }), 12 | }).then((res) => res.json()); 13 | }; 14 | 15 | const Delete = async (cluster) => { 16 | return fetch(process.env.REACT_APP_API_HOST + '/v1/endpoints/' + cluster, { 17 | method: 'DELETE', 18 | }).then((res) => res.json()); 19 | }; 20 | 21 | const SetDefault = async (endpoint) => { 22 | endpoint.default_upstream = true; 23 | return fetch(process.env.REACT_APP_API_HOST + '/v1/endpoints/' + endpoint.cluster, { 24 | method: 'PUT', 25 | headers: { 26 | 'Content-Type': 'application/json', 27 | }, 28 | body: JSON.stringify({ endpoint }), 29 | }); 30 | }; 31 | 32 | export default { 33 | List, 34 | Create, 35 | Delete, 36 | SetDefault, 37 | }; 38 | -------------------------------------------------------------------------------- /frontend/src/api/Logs.js: -------------------------------------------------------------------------------- 1 | const List = async () => { 2 | return fetch(process.env.REACT_APP_API_HOST + '/v1/logs').then((res) => res.json()); 3 | }; 4 | 5 | export default { 6 | List, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/api/Middleware.js: -------------------------------------------------------------------------------- 1 | const List = async () => { 2 | return fetch(process.env.REACT_APP_API_HOST + '/v1/middlewares').then((res) => res.json()); 3 | }; 4 | 5 | const Create = async (data) => { 6 | return fetch(process.env.REACT_APP_API_HOST + '/v1/middlewares', { 7 | method: 'POST', 8 | headers: { 9 | 'Content-Type': 'application/json', 10 | }, 11 | body: JSON.stringify({ middleware: data }), 12 | }).then((res) => res.json()); 13 | }; 14 | 15 | const Delete = async (slug) => { 16 | return fetch(process.env.REACT_APP_API_HOST + '/v1/middlewares/' + slug, { 17 | method: 'DELETE', 18 | }).then((res) => res.json()); 19 | }; 20 | 21 | const TriggerBuild = async (slug) => { 22 | return fetch(process.env.REACT_APP_API_HOST + '/v1/middlewares/' + slug + '/builds', { 23 | method: 'POST', 24 | }).then((res) => res.json()); 25 | }; 26 | 27 | export default { 28 | List, 29 | Create, 30 | Delete, 31 | TriggerBuild, 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/src/components/InputExample.js: -------------------------------------------------------------------------------- 1 | import Typography from '@mui/material/Typography'; 2 | import styled from '@mui/material/styles/styled'; 3 | 4 | const InputExample = styled(Typography)({ 5 | fontSize: '0.8rem', 6 | margin: '0 0 16px 16px', 7 | }); 8 | 9 | export default InputExample; 10 | -------------------------------------------------------------------------------- /frontend/src/components/SimpleModal.js: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import styled from '@mui/material/styles/styled'; 3 | 4 | const SimpleModal = styled(Box)({ 5 | position: 'absolute', 6 | top: '50%', 7 | left: '50%', 8 | transform: 'translate(-50%, -50%)', 9 | minWidth: 400, 10 | background: '#FFF', 11 | border: '1px solid #000', 12 | borderRadius: '3px', 13 | padding: '2rem', 14 | }); 15 | 16 | export default SimpleModal; 17 | -------------------------------------------------------------------------------- /frontend/src/components/TextInput.js: -------------------------------------------------------------------------------- 1 | import InputBase from '@mui/material/InputBase'; 2 | import { styled } from '@mui/material/styles'; 3 | 4 | const TextInput = styled(InputBase)(({ theme }) => ({ 5 | border: '1px solid', 6 | borderColor: theme.palette.text.secondary, 7 | padding: '8px 16px', 8 | margin: '8px 0', 9 | borderRadius: '3px', 10 | })); 11 | 12 | export default TextInput; 13 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import posthog from 'posthog-js'; 4 | import './index.css'; 5 | import { 6 | createBrowserRouter, 7 | RouterProvider 8 | } from 'react-router-dom'; 9 | import { createTheme, ThemeProvider } from '@mui/material'; 10 | import Routes from './routes'; 11 | import Theme from './theme'; 12 | 13 | // Apoxy tracks usage of Proximal to help us improve the product 14 | // and justify the continued investment in maintaining this tool. 15 | // No personal information is collected. 16 | // You can learn more about Apoxy here: https://apoxy.dev 17 | // or you can email us at hello@apoxy.dev with questions. 18 | // You can opt-out of tracking by commenting out the following line. 19 | posthog.init('phc_k3l3Nr2HeDJwQMsPlIg2By3Beta8c4wZijdDmbQGEtE', { api_host: 'https://e.apoxy.dev' }); 20 | 21 | const router = createBrowserRouter(Routes); 22 | const theme = createTheme(Theme); 23 | 24 | const root = ReactDOM.createRoot(document.getElementById('root')); 25 | root.render( 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /frontend/src/pages/NotFound.js: -------------------------------------------------------------------------------- 1 | import { MinusCirlce } from 'iconsax-react'; 2 | import { Container, Grid, Paper, Typography } from '@mui/material'; 3 | 4 | const NotFound = () => ( 5 | 6 | 13 | 14 | 15 | 19 | 20 | 21 | 404 24 | Not Found 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | export default NotFound; 32 | -------------------------------------------------------------------------------- /frontend/src/pages/home/Home.js: -------------------------------------------------------------------------------- 1 | import Tabs from '@mui/material/Tabs'; 2 | import Tab from '@mui/material/Tab'; 3 | import Box from '@mui/material/Box'; 4 | 5 | import { useLocation, useNavigate } from 'react-router-dom'; 6 | 7 | import CustomTabPanel from './components/CustomTabPanel'; 8 | import ExtensionsTab from './components/ExtensionsTab'; 9 | import EndpointsTab from './components/EndpointsTab'; 10 | import LogsTab from './components/LogsTab'; 11 | 12 | const a11yProps = (index) => { 13 | return { 14 | id: `simple-tab-${index}`, 15 | 'aria-controls': `simple-tabpanel-${index}`, 16 | }; 17 | }; 18 | 19 | const Home = () => { 20 | // TODO: const [extState, setExtState] = useState('waiting'); 21 | // TODO: const [endpointsState, setEndpointsState] = useState('waiting'); 22 | 23 | const tabs = { 24 | 0: '/extensions', 25 | 1: '/endpoints', 26 | 2: '/logs', 27 | }; 28 | let paths = {}; 29 | Object.keys(tabs).forEach((key) => { 30 | paths[tabs[key]] = Number(key); 31 | }); 32 | 33 | const location = useLocation(); 34 | const value = paths[location.pathname]; 35 | 36 | const navigate = useNavigate(); 37 | const handleChange = (event, newValue) => { 38 | navigate(tabs[newValue]); 39 | }; 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default Home; 64 | -------------------------------------------------------------------------------- /frontend/src/pages/home/components/CustomTabPanel.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import Box from '@mui/material/Box'; 3 | 4 | function CustomTabPanel(props) { 5 | const { children, value, index, ...other } = props; 6 | return ( 7 | 20 | ); 21 | } 22 | 23 | CustomTabPanel.propTypes = { 24 | children: PropTypes.node, 25 | index: PropTypes.number.isRequired, 26 | value: PropTypes.number.isRequired, 27 | }; 28 | 29 | export default CustomTabPanel; 30 | -------------------------------------------------------------------------------- /frontend/src/pages/home/components/LogsTab.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import Table from '@mui/material/Table'; 4 | import TableBody from '@mui/material/TableBody'; 5 | import TableCell from '@mui/material/TableCell'; 6 | import TableContainer from '@mui/material/TableContainer'; 7 | import TableHead from '@mui/material/TableHead'; 8 | import TableFooter from '@mui/material/TableFooter'; 9 | import TablePagination from '@mui/material/TablePagination'; 10 | import TableRow from '@mui/material/TableRow'; 11 | import IconButton from '@mui/material/IconButton'; 12 | import FirstPageIcon from '@mui/icons-material/FirstPage'; 13 | import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'; 14 | import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'; 15 | import LastPageIcon from '@mui/icons-material/LastPage'; 16 | import LinearProgress from '@mui/material/LinearProgress'; 17 | import Container from '@mui/material/Container'; 18 | import Typography from '@mui/material/Typography'; 19 | 20 | import Logs from '../../../api/Logs'; 21 | 22 | const TablePaginationActions = ({ count, page, rowsPerPage, onPageChange }) => { 23 | const handleFirstPageButtonClick = (event) => { 24 | onPageChange(event, 0); 25 | }; 26 | 27 | const handleBackButtonClick = (event) => { 28 | onPageChange(event, page - 1); 29 | }; 30 | 31 | const handleNextButtonClick = (event) => { 32 | onPageChange(event, page + 1); 33 | }; 34 | 35 | const handleLastPageButtonClick = (event) => { 36 | onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); 37 | }; 38 | 39 | return ( 40 | 41 | 46 | 47 | 48 | 53 | 54 | 55 | = Math.ceil(count / rowsPerPage) - 1} 58 | aria-label="next page" 59 | > 60 | 61 | 62 | = Math.ceil(count / rowsPerPage) - 1} 65 | aria-label="last page" 66 | > 67 | 68 | 69 | 70 | ); 71 | }; 72 | 73 | const GettingStarted = () => { 74 | return ( 75 | <> 76 | 77 | No Logs 78 | 79 | Place a request to the Envoy server listening on port 18000 and then logs will appear here. 80 | 81 | 86 | curl http://localhost:18000 87 | 88 | 89 | 90 | ); 91 | }; 92 | 93 | const LogsTab = () => { 94 | const [ page, setPage ] = useState(0); 95 | const [ rowsPerPage, setRowsPerPage ] = useState(10); 96 | const [ loading, setLoading ] = useState(true); 97 | const [ logs, setLogs ] = useState([]); 98 | 99 | let reloadTimeout = null; 100 | 101 | const load = async () => { 102 | let r = await Logs.List(); 103 | if (r.logs?.length > 0) { 104 | setLogs(r.logs); 105 | // Complete the tutorial since they've sent a request. 106 | localStorage.setItem('proximalTutorialComplete', true); 107 | } 108 | setLoading(false); 109 | reloadTimeout = setTimeout(load, 1000); 110 | }; 111 | useEffect(() => { 112 | load(); 113 | return () => clearTimeout(reloadTimeout); 114 | }, []); 115 | 116 | if (loading) { 117 | return ; 118 | } 119 | 120 | if (logs.length === 0) { 121 | return ; 122 | } 123 | 124 | // Avoid a layout jump when reaching the last page with empty rows. 125 | const emptyRows = 126 | page > 0 ? Math.max(0, (1 + page) * rowsPerPage - logs.length) : 0; 127 | 128 | const handleChangePage = (event, newPage) => { 129 | setPage(newPage); 130 | }; 131 | 132 | const handleChangeRowsPerPage = (event) => { 133 | setRowsPerPage(parseInt(event.target.value, 10)); 134 | setPage(0); 135 | }; 136 | 137 | return ( 138 | 139 | 140 | 141 | 142 | Timestamp 143 | Method 144 | Path 145 | Response Code 146 | Duration 147 | 148 | 149 | 150 | {(logs > 0 151 | ? logs.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) 152 | : logs 153 | ).map((row) => ( 154 | 155 | 156 | {row.timestamp} 157 | 158 | 159 | {row.http.request.requestMethod} 160 | 161 | 162 | {row.http.request.path} 163 | 164 | 165 | {row.http.response.responseCode} 166 | 167 | 168 | {row.http.commonProperties.duration} 169 | 170 | 171 | ))} 172 | {emptyRows > 0 && ( 173 | 174 | 175 | 176 | )} 177 | 178 | 179 | 180 | 196 | 197 | 198 |
199 |
200 | ); 201 | }; 202 | 203 | export default LogsTab; 204 | -------------------------------------------------------------------------------- /frontend/src/pages/home/components/StatusIndicator.js: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@mui/material/styles'; 2 | import Typography from '@mui/material/Typography'; 3 | 4 | const StatusIndicator = ({ icon, children, variant }) => { 5 | const theme = useTheme(); 6 | const color = variant === 'disabled' ? theme.palette.text.disabled : theme.palette.secondary.main; 7 | return ( 8 | 21 | {icon} 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | export default StatusIndicator; 28 | -------------------------------------------------------------------------------- /frontend/src/pages/home/index.js: -------------------------------------------------------------------------------- 1 | import Home from './Home'; 2 | 3 | export default Home; 4 | -------------------------------------------------------------------------------- /frontend/src/routes.js: -------------------------------------------------------------------------------- 1 | import { Navigate } from 'react-router-dom'; 2 | 3 | import Main from './templates/Main'; 4 | 5 | import NotFound from './pages/NotFound'; 6 | import Home from './pages/home'; 7 | 8 | const Routes = [ 9 | { 10 | path: '/', 11 | element:
, 12 | errorElement: , 13 | children: [ 14 | { 15 | index: true, 16 | element: , 17 | }, 18 | { 19 | path: '/extensions', 20 | element: , 21 | }, 22 | { 23 | path: '/endpoints', 24 | element: , 25 | }, 26 | { 27 | path: '/logs', 28 | element: , 29 | }, 30 | ] 31 | }, 32 | ]; 33 | 34 | export default Routes; 35 | -------------------------------------------------------------------------------- /frontend/src/templates/Main.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Outlet } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | import { useTheme } from '@mui/material/styles'; 5 | import AppBar from '@mui/material/AppBar'; 6 | import Toolbar from '@mui/material/Toolbar'; 7 | import Typography from '@mui/material/Typography'; 8 | import CssBaseline from '@mui/material/CssBaseline'; 9 | import useScrollTrigger from '@mui/material/useScrollTrigger'; 10 | import Button from '@mui/material/Button'; 11 | import Box from '@mui/material/Box'; 12 | import Container from '@mui/material/Container'; 13 | import GithubIcon from '@mui/icons-material/GitHub'; 14 | import { ArrangeHorizontalCircle } from 'iconsax-react'; 15 | import { Link } from 'react-router-dom'; 16 | 17 | function ElevationScroll(props) { 18 | const { children, window } = props; 19 | // Note that you normally won't need to set the window ref as useScrollTrigger 20 | // will default to window. 21 | // This is only being set here because the demo is in an iframe. 22 | const trigger = useScrollTrigger({ 23 | disableHysteresis: true, 24 | threshold: 0, 25 | target: window ? window() : undefined, 26 | }); 27 | 28 | return React.cloneElement(children, { 29 | elevation: trigger ? 4 : 0, 30 | }); 31 | } 32 | 33 | ElevationScroll.propTypes = { 34 | children: PropTypes.element.isRequired, 35 | /** 36 | * Injected by the documentation to work in an iframe. 37 | * You won't need it on your project. 38 | */ 39 | window: PropTypes.func, 40 | }; 41 | 42 | const Main = (props) => { 43 | const theme = useTheme(); 44 | return ( 45 | <> 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | Proximal 61 | 62 | 63 | 67 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | ); 84 | }; 85 | 86 | export default Main; 87 | -------------------------------------------------------------------------------- /frontend/src/theme.js: -------------------------------------------------------------------------------- 1 | const Theme = { 2 | palette: { 3 | primary: { 4 | main: '#1F1C1C', 5 | }, 6 | secondary: { 7 | main: '#48CD84', 8 | }, 9 | }, 10 | typography: { 11 | fontFamily: '"IBM Plex Sans", sans-serif', 12 | h1: { 13 | fontFamily: '"IBM Plex Mono", monospace', 14 | }, 15 | h2: { 16 | fontFamily: '"IBM Plex Mono", monospace', 17 | }, 18 | h3: { 19 | fontFamily: '"IBM Plex Mono", monospace', 20 | }, 21 | h4: { 22 | fontFamily: '"IBM Plex Mono", monospace', 23 | }, 24 | h5: { 25 | fontFamily: '"IBM Plex Mono", monospace', 26 | }, 27 | h6: { 28 | fontFamily: '"IBM Plex Mono", monospace', 29 | }, 30 | button: { 31 | fontWeight: 'bold', 32 | textTransform: 'none', 33 | }, 34 | }, 35 | components: { 36 | MuiTab: { 37 | styleOverrides: { 38 | root: { 39 | fontSize: '1rem', 40 | textTransform: 'uppercase', 41 | }, 42 | }, 43 | }, 44 | MuiButton: { 45 | styleOverrides: { 46 | root: { 47 | boxShadow: 'none', 48 | borderRadius: '3px', 49 | '&:hover': { 50 | backgroundColor: '#1F1C1C', 51 | color: '#48CD84', 52 | }, 53 | }, 54 | }, 55 | }, 56 | MuiTableHead: { 57 | styleOverrides: { 58 | root: { 59 | '& th': { 60 | fontWeight: 'bold', 61 | }, 62 | }, 63 | }, 64 | } 65 | }, 66 | }; 67 | 68 | export default Theme; 69 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/apoxy-dev/proximal 2 | 3 | go 1.20 4 | 5 | require ( 6 | cloud.google.com/go v0.110.0 // indirect 7 | cloud.google.com/go/compute v1.19.0 // indirect 8 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 9 | cloud.google.com/go/iam v0.13.0 // indirect 10 | cloud.google.com/go/storage v1.30.1 // indirect 11 | github.com/Microsoft/go-winio v0.6.1 // indirect 12 | github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect 13 | github.com/acomagu/bufpipe v1.0.4 // indirect 14 | github.com/apache/thrift v0.18.0 // indirect 15 | github.com/aws/aws-sdk-go v1.44.203 // indirect 16 | github.com/benbjohnson/clock v1.3.0 // indirect 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/blang/semver/v4 v4.0.0 // indirect 19 | github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c // indirect 20 | github.com/cenkalti/backoff/v4 v4.2.1 // indirect 21 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 22 | github.com/cloudflare/circl v1.3.3 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect 25 | github.com/emirpasic/gods v1.18.1 // indirect 26 | github.com/envoyproxy/go-control-plane v0.11.1 // indirect 27 | github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect 28 | github.com/fsnotify/fsnotify v1.6.0 // indirect 29 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 30 | github.com/go-git/go-billy/v5 v5.4.1 // indirect 31 | github.com/go-git/go-git/v5 v5.7.0 // indirect 32 | github.com/go-logr/logr v1.2.4 // indirect 33 | github.com/go-logr/stdr v1.2.2 // indirect 34 | github.com/gocql/gocql v1.4.0 // indirect 35 | github.com/gogo/googleapis v1.4.1 // indirect 36 | github.com/gogo/protobuf v1.3.2 // indirect 37 | github.com/gogo/status v1.1.1 // indirect 38 | github.com/golang-jwt/jwt/v4 v4.4.3 // indirect 39 | github.com/golang-migrate/migrate/v4 v4.16.2 // indirect 40 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 41 | github.com/golang/mock v1.7.0-rc.1 // indirect 42 | github.com/golang/protobuf v1.5.3 // indirect 43 | github.com/golang/snappy v0.0.4 // indirect 44 | github.com/google/go-cmp v0.5.9 // indirect 45 | github.com/google/uuid v1.3.0 // indirect 46 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect 47 | github.com/googleapis/gax-go/v2 v2.7.1 // indirect 48 | github.com/googleapis/go-type-adapters v1.0.0 // indirect 49 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect 50 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect 51 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 52 | github.com/hashicorp/errwrap v1.1.0 // indirect 53 | github.com/hashicorp/go-multierror v1.1.1 // indirect 54 | github.com/iancoleman/strcase v0.2.0 // indirect 55 | github.com/imdario/mergo v0.3.15 // indirect 56 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 57 | github.com/jmespath/go-jmespath v0.4.0 // indirect 58 | github.com/jmoiron/sqlx v1.3.4 // indirect 59 | github.com/jonboulle/clockwork v0.4.0 // indirect 60 | github.com/josharian/intern v1.0.0 // indirect 61 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 62 | github.com/kevinburke/ssh_config v1.2.0 // indirect 63 | github.com/mailru/easyjson v0.7.7 // indirect 64 | github.com/martinlindhe/base36 v1.1.1 // indirect 65 | github.com/mattn/go-isatty v0.0.17 // indirect 66 | github.com/mattn/go-sqlite3 v1.14.17 // indirect 67 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 68 | github.com/olivere/elastic/v7 v7.0.32 // indirect 69 | github.com/opentracing/opentracing-go v1.2.0 // indirect 70 | github.com/pborman/uuid v1.2.1 // indirect 71 | github.com/pjbgf/sha1cd v0.3.0 // indirect 72 | github.com/pkg/errors v0.9.1 // indirect 73 | github.com/pmezard/go-difflib v1.0.0 // indirect 74 | github.com/prometheus/client_golang v1.15.1 // indirect 75 | github.com/prometheus/client_model v0.4.0 // indirect 76 | github.com/prometheus/common v0.44.0 // indirect 77 | github.com/prometheus/procfs v0.10.1 // indirect 78 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 79 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 80 | github.com/robfig/cron v1.2.0 // indirect 81 | github.com/robfig/cron/v3 v3.0.1 // indirect 82 | github.com/sergi/go-diff v1.1.0 // indirect 83 | github.com/sirupsen/logrus v1.9.2 // indirect 84 | github.com/skeema/knownhosts v1.1.1 // indirect 85 | github.com/stretchr/objx v0.5.0 // indirect 86 | github.com/stretchr/testify v1.8.4 // indirect 87 | github.com/temporalio/ringpop-go v0.0.0-20220818230611-30bf23b490b2 // indirect 88 | github.com/temporalio/tchannel-go v1.22.1-0.20220818200552-1be8d8cffa5b // indirect 89 | github.com/temporalio/temporalite v0.3.0 // indirect 90 | github.com/twmb/murmur3 v1.1.6 // indirect 91 | github.com/uber-common/bark v1.3.0 // indirect 92 | github.com/uber-go/tally/v4 v4.1.6 // indirect 93 | github.com/xanzy/ssh-agent v0.3.3 // indirect 94 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // indirect 95 | go.opencensus.io v0.24.0 // indirect 96 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 // indirect 97 | go.opentelemetry.io/otel v1.16.0 // indirect 98 | go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect 99 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect 100 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.39.0 // indirect 101 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect 102 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 // indirect 103 | go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect 104 | go.opentelemetry.io/otel/metric v1.16.0 // indirect 105 | go.opentelemetry.io/otel/sdk v1.16.0 // indirect 106 | go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect 107 | go.opentelemetry.io/otel/trace v1.16.0 // indirect 108 | go.opentelemetry.io/proto/otlp v0.19.0 // indirect 109 | go.temporal.io/api v1.23.0 // indirect 110 | go.temporal.io/sdk v1.23.0 // indirect 111 | go.temporal.io/server v1.21.1 // indirect 112 | go.temporal.io/version v0.3.0 // indirect 113 | go.uber.org/atomic v1.10.0 // indirect 114 | go.uber.org/dig v1.16.1 // indirect 115 | go.uber.org/fx v1.19.1 // indirect 116 | go.uber.org/multierr v1.9.0 // indirect 117 | go.uber.org/zap v1.24.0 // indirect 118 | golang.org/x/crypto v0.9.0 // indirect 119 | golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect 120 | golang.org/x/mod v0.10.0 // indirect 121 | golang.org/x/net v0.10.0 // indirect 122 | golang.org/x/oauth2 v0.8.0 // indirect 123 | golang.org/x/sys v0.8.0 // indirect 124 | golang.org/x/text v0.9.0 // indirect 125 | golang.org/x/time v0.3.0 // indirect 126 | golang.org/x/tools v0.9.1 // indirect 127 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 128 | google.golang.org/api v0.114.0 // indirect 129 | google.golang.org/appengine v1.6.7 // indirect 130 | google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect 131 | google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect 132 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect 133 | google.golang.org/grpc v1.55.0 // indirect 134 | google.golang.org/protobuf v1.30.0 // indirect 135 | gopkg.in/inf.v0 v0.9.1 // indirect 136 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect 137 | gopkg.in/validator.v2 v2.0.1 // indirect 138 | gopkg.in/warnings.v0 v0.1.2 // indirect 139 | gopkg.in/yaml.v3 v3.0.1 // indirect 140 | lukechampine.com/uint128 v1.2.0 // indirect 141 | modernc.org/cc/v3 v3.40.0 // indirect 142 | modernc.org/ccgo/v3 v3.16.13 // indirect 143 | modernc.org/libc v1.22.3 // indirect 144 | modernc.org/mathutil v1.5.0 // indirect 145 | modernc.org/memory v1.5.0 // indirect 146 | modernc.org/opt v0.1.3 // indirect 147 | modernc.org/sqlite v1.21.0 // indirect 148 | modernc.org/strutil v1.1.3 // indirect 149 | modernc.org/token v1.1.0 // indirect 150 | ) 151 | -------------------------------------------------------------------------------- /images/Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM fedora:38 2 | 3 | RUN dnf install -y tinygo binaryen 4 | 5 | # Install Rust toolchain. 6 | ARG RUST_TOOLCHAIN="1.71.0" 7 | ENV CARGO_HOME=/usr/local/rust 8 | ENV RUSTUP_HOME=/usr/local/rust 9 | ENV PATH="$PATH:$CARGO_HOME/bin" 10 | 11 | RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" && \ 12 | rustup target add wasm32-wasi 13 | 14 | # Install Go toolchain. 15 | ARG TARGETARCH 16 | RUN curl -sSfL https://go.dev/dl/go1.20.6.linux-${TARGETARCH}.tar.gz | tar -C /usr/local -xzf - 17 | -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- 1 | # Worker Base Images 2 | 3 | There images include all necessary toolchains to perform Wasm/WASI builds. The Proximal 4 | server itself is mostly statically linked. 5 | 6 | ## Re-building and the images 7 | 8 | 1. Re-build multi-arch image and push to registry: 9 | ``` 10 | REPO= 11 | TAG= 12 | docker buildx build --push --tag docker.io/$REPO/proximal-builder:$TAG --platform linux/amd64,linux/arm64 -f Dockerfile.build . 13 | ``` 14 | 15 | 2. Update both `base_image_arm64` and `base_image_amd64` container image bases in the `WORKSPACE` file. 16 | -------------------------------------------------------------------------------- /server/api/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "api", 5 | srcs = [ 6 | "build.go", 7 | "endpoint.go", 8 | "logs.go", 9 | "middleware.go", 10 | "utils.go", 11 | ], 12 | importpath = "github.com/apoxy-dev/proximal/server/api", 13 | visibility = ["//visibility:public"], 14 | deps = [ 15 | "//api/endpoint/v1:endpoint", 16 | "//api/logs/v1:logs", 17 | "//api/middleware/v1:middleware", 18 | "//core/log", 19 | "//server/db", 20 | "//server/db/sql:sql_library", 21 | "//server/ingest", 22 | "//server/watcher", 23 | "@com_github_envoyproxy_go_control_plane//envoy/data/accesslog/v3:accesslog", 24 | "@com_github_envoyproxy_go_control_plane//envoy/service/accesslog/v3:accesslog", 25 | "@com_github_gogo_status//:status", 26 | "@com_github_google_uuid//:uuid", 27 | "@io_bazel_rules_go//proto/wkt:empty_go_proto", 28 | "@io_temporal_go_sdk//client", 29 | "@org_golang_google_grpc//:go_default_library", 30 | "@org_golang_google_grpc//codes", 31 | "@org_golang_google_protobuf//encoding/protojson", 32 | "@org_golang_google_protobuf//types/known/emptypb", 33 | "@org_golang_google_protobuf//types/known/timestamppb", 34 | "@org_golang_x_exp//maps", 35 | ], 36 | ) 37 | -------------------------------------------------------------------------------- /server/api/build.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "time" 11 | 12 | "github.com/gogo/status" 13 | "github.com/google/uuid" 14 | tclient "go.temporal.io/sdk/client" 15 | "golang.org/x/exp/maps" 16 | "google.golang.org/grpc/codes" 17 | "google.golang.org/protobuf/encoding/protojson" 18 | "google.golang.org/protobuf/types/known/emptypb" 19 | "google.golang.org/protobuf/types/known/timestamppb" 20 | 21 | "github.com/apoxy-dev/proximal/core/log" 22 | serverdb "github.com/apoxy-dev/proximal/server/db" 23 | sqlc "github.com/apoxy-dev/proximal/server/db/sql" 24 | "github.com/apoxy-dev/proximal/server/ingest" 25 | 26 | middlewarev1 "github.com/apoxy-dev/proximal/api/middleware/v1" 27 | ) 28 | 29 | func buildFromRow(b *sqlc.Build) *middlewarev1.Build { 30 | return &middlewarev1.Build{ 31 | Sha: b.Sha, 32 | Status: middlewarev1.Build_BuildStatus(middlewarev1.Build_BuildStatus_value[b.Status]), 33 | StatusDetail: b.StatusDetail, 34 | StartedAt: timestamppb.New(b.StartedAt.Time), 35 | UpdatedAt: timestamppb.New(b.UpdatedAt.Time), 36 | } 37 | } 38 | 39 | func (s *MiddlewareService) ListBuilds(ctx context.Context, req *middlewarev1.ListBuildsRequest) (*middlewarev1.ListBuildsResponse, error) { 40 | log.Infof("list middlewares: %s", protojson.Format(req)) 41 | 42 | ts := time.Now() 43 | if req.PageToken != "" { 44 | dTs, err := decodeNextPageToken(req.PageToken) 45 | if err != nil { 46 | log.Errorf("failed to decode page token: %v", err) 47 | return nil, status.Error(codes.InvalidArgument, "invalid page token") 48 | } 49 | 50 | log.Debugf("decoded page token: %s", string(dTs)) 51 | 52 | ts, err = time.Parse(time.RFC3339Nano, string(dTs)) 53 | if err != nil { 54 | log.Errorf("failed to parse page token: %v", err) 55 | return nil, status.Error(codes.InvalidArgument, "invalid page token") 56 | } 57 | } 58 | pageSize := req.PageSize 59 | if pageSize == 0 { 60 | pageSize = 100 61 | } 62 | 63 | bs, err := s.db.Queries().ListBuildsByMiddlewareSlug(ctx, sqlc.ListBuildsByMiddlewareSlugParams{ 64 | MiddlewareSlug: req.Slug, 65 | Datetime: ts, 66 | Limit: int64(pageSize), 67 | }) 68 | if err != nil { 69 | return nil, fmt.Errorf("failed to list middleware: %w", err) 70 | } 71 | 72 | builds := make([]*middlewarev1.Build, 0, len(bs)) 73 | for _, b := range bs { 74 | builds = append(builds, buildFromRow(&b)) 75 | } 76 | 77 | var nextPageToken string 78 | if len(builds) == int(pageSize) { 79 | nextPageToken = encodeNextPageToken(bs[len(bs)-1].StartedAt.Time.Format(time.RFC3339Nano)) 80 | } 81 | 82 | return &middlewarev1.ListBuildsResponse{ 83 | Builds: builds, 84 | NextPageToken: nextPageToken, 85 | }, nil 86 | } 87 | 88 | func (s *MiddlewareService) GetBuild(ctx context.Context, req *middlewarev1.GetBuildRequest) (*middlewarev1.Build, error) { 89 | b, err := s.db.Queries().GetBuildByMiddlewareSlugAndSha(ctx, sqlc.GetBuildByMiddlewareSlugAndShaParams{ 90 | MiddlewareSlug: req.Slug, 91 | Sha: req.Sha, 92 | }) 93 | if err != nil { 94 | if err == sql.ErrNoRows { 95 | return nil, status.Errorf(codes.NotFound, "build not found for middleware", req.Slug) 96 | } 97 | return nil, status.Errorf(codes.Internal, "unable to get build: %v", err) 98 | } 99 | return buildFromRow(&b), nil 100 | } 101 | 102 | func (s *MiddlewareService) GetLiveBuild(ctx context.Context, req *middlewarev1.GetLiveBuildRequest) (*middlewarev1.Build, error) { 103 | b, err := s.db.Queries().GetLiveReadyBuildByMiddlewareSlug(ctx, req.Slug) 104 | if err != nil { 105 | if err == sql.ErrNoRows { 106 | return nil, status.Errorf(codes.NotFound, "build not found for middleware", req.Slug) 107 | } 108 | return nil, status.Errorf(codes.Internal, "unable to get latest build: %v", err) 109 | } 110 | return buildFromRow(&b), nil 111 | } 112 | 113 | func (s *MiddlewareService) startBuildWorkflow(ctx context.Context, slug string, params *middlewarev1.MiddlewareIngestParams) error { 114 | tOpts := tclient.StartWorkflowOptions{ 115 | ID: fmt.Sprintf("%s-%s", slug, uuid.New()), 116 | TaskQueue: ingest.MiddlewareIngestQueue, 117 | WorkflowExecutionTimeout: 60 * time.Second, 118 | } 119 | in := &ingest.MiddlewareIngestParams{ 120 | Slug: slug, 121 | Params: params, 122 | } 123 | sw, err := s.tc.ExecuteWorkflow(ctx, tOpts, ingest.StartBuildWorkflow, in) 124 | if err != nil { 125 | return fmt.Errorf("unable to start ingest workflow: %v", err) 126 | } 127 | return sw.Get(ctx, nil) 128 | } 129 | 130 | func (s *MiddlewareService) TriggerBuild(ctx context.Context, req *middlewarev1.TriggerBuildRequest) (*emptypb.Empty, error) { 131 | log.Infof("TriggerBuild: %v", req) 132 | 133 | tx, err := s.db.Begin() 134 | if err != nil { 135 | log.Errorf("failed to begin transaction: %v", err) 136 | return nil, status.Error(codes.Internal, "failed to trigger build") 137 | } 138 | defer tx.Rollback() 139 | qtx := s.db.Queries().WithTx(tx) 140 | 141 | m, err := qtx.GetMiddlewareBySlug(ctx, req.Slug) 142 | if err != nil { 143 | if err == sql.ErrNoRows { 144 | return nil, status.Errorf(codes.NotFound, "middleware %q not found", req.Slug) 145 | } 146 | log.Errorf("unable to get middleware: %v", err) 147 | return nil, status.Errorf(codes.Internal, "unable to get middleware: %v", err) 148 | } 149 | 150 | var ingestParams middlewarev1.MiddlewareIngestParams 151 | if err := protojson.Unmarshal(m.IngestParamsJson, &ingestParams); err != nil { 152 | log.Errorf("unable to unmarshal ingest params: %v", err) 153 | return nil, status.Errorf(codes.Internal, "unable to unmarshal ingest params: %v", err) 154 | } 155 | 156 | if err := s.startBuildWorkflow(ctx, req.Slug, &ingestParams); err != nil { 157 | log.Errorf("unable to start build workflow: %v", err) 158 | return nil, status.Errorf(codes.Internal, "unable to start build workflow: %v", err) 159 | } 160 | 161 | _, err = qtx.UpdateMiddlewareStatus(ctx, sqlc.UpdateMiddlewareStatusParams{ 162 | Slug: req.Slug, 163 | Status: serverdb.MiddlewareStatusPendingReady, 164 | StatusDetail: sql.NullString{ 165 | String: "build triggered", 166 | Valid: true, 167 | }, 168 | LiveBuildSha: m.LiveBuildSha, 169 | }) 170 | if err != nil { 171 | log.Errorf("failed to update middleware status: %v", err) 172 | return nil, status.Error(codes.Internal, "failed to trigger build") 173 | } 174 | 175 | if err := tx.Commit(); err != nil { 176 | log.Errorf("failed to commit transaction: %v", err) 177 | return nil, status.Error(codes.Internal, "failed to trigger build") 178 | } 179 | 180 | return &emptypb.Empty{}, nil 181 | } 182 | 183 | func (s *MiddlewareService) GetBuildOutput(ctx context.Context, req *middlewarev1.GetBuildOutputRequest) (*middlewarev1.BuildOutput, error) { 184 | log.Infof("GetBuildOutput: %v", req) 185 | 186 | allowedType := map[string]bool{ 187 | "stdout": true, 188 | "stderr": true, 189 | "wasm.out": true, 190 | } 191 | if !allowedType[req.OutputType] { 192 | return nil, status.Errorf(codes.InvalidArgument, "invalid output type: %q (must be one of %v)", req.OutputType, maps.Keys(allowedType)) 193 | } 194 | 195 | b, err := s.db.Queries().GetBuildByMiddlewareSlugAndSha(ctx, sqlc.GetBuildByMiddlewareSlugAndShaParams{ 196 | MiddlewareSlug: req.Slug, 197 | Sha: req.Sha, 198 | }) 199 | if err != nil { 200 | if err == sql.ErrNoRows { 201 | return nil, status.Errorf(codes.NotFound, "build not found for middleware %q", req.Slug) 202 | } 203 | return nil, status.Errorf(codes.Internal, "unable to get build: %v", err) 204 | } 205 | 206 | // TODO(dilyevsky): Make this stuff more streaming. Devs love streaming. 207 | if b.OutputPath.String == "" { 208 | log.Errorf("output path for build %s is empty", b.Sha) 209 | return nil, status.Errorf(codes.NotFound, "output not found for build %s", b.Sha) 210 | } 211 | path := filepath.Join(b.OutputPath.String, req.OutputType) 212 | output, err := ioutil.ReadFile(path) 213 | if err != nil { 214 | if os.IsNotExist(err) { 215 | log.Errorf("output file not found in store for build %s: %s", b.Sha, path) 216 | return nil, status.Errorf(codes.NotFound, "output not found for build %s", b.Sha) 217 | } 218 | log.Errorf("unable to read output: %v", err) 219 | return nil, status.Errorf(codes.Internal, "output not found for build %s", b.Sha) 220 | } 221 | 222 | return &middlewarev1.BuildOutput{ 223 | Build: buildFromRow(&b), 224 | Output: output, 225 | }, nil 226 | } 227 | 228 | func (s *MiddlewareService) GetLiveBuildOutput(ctx context.Context, req *middlewarev1.GetLiveBuildOutputRequest) (*middlewarev1.BuildOutput, error) { 229 | r, err := s.GetLiveBuild(ctx, &middlewarev1.GetLiveBuildRequest{ 230 | Slug: req.Slug, 231 | }) 232 | if err != nil { 233 | return nil, err 234 | } 235 | return s.GetBuildOutput(ctx, &middlewarev1.GetBuildOutputRequest{ 236 | Slug: req.Slug, 237 | Sha: r.Sha, 238 | OutputType: req.OutputType, 239 | }) 240 | } 241 | -------------------------------------------------------------------------------- /server/api/logs.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "time" 8 | 9 | accesslogv3 "github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3" 10 | accesslogservicev3 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3" 11 | "github.com/gogo/status" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/codes" 14 | "google.golang.org/protobuf/encoding/protojson" 15 | 16 | "github.com/apoxy-dev/proximal/core/log" 17 | serverdb "github.com/apoxy-dev/proximal/server/db" 18 | sqlc "github.com/apoxy-dev/proximal/server/db/sql" 19 | 20 | logsv1 "github.com/apoxy-dev/proximal/api/logs/v1" 21 | ) 22 | 23 | // LogsService manages request logs using Envoy's access log and tap services. 24 | type LogsService struct { 25 | db *serverdb.DB 26 | } 27 | 28 | // NewLogsService creates a new LogsService. 29 | func NewLogsService(db *serverdb.DB) *LogsService { 30 | return &LogsService{ 31 | db: db, 32 | } 33 | } 34 | 35 | func logFromRow(row sqlc.AccessLog) (*logsv1.Log, error) { 36 | var entry accesslogv3.HTTPAccessLogEntry 37 | if err := protojson.Unmarshal(row.Entry, &entry); err != nil { 38 | return nil, err 39 | } 40 | 41 | return &logsv1.Log{ 42 | Id: entry.Request.RequestId, 43 | Timestamp: entry.CommonProperties.StartTime, 44 | Http: &entry, 45 | }, nil 46 | } 47 | 48 | // GetLogs returns a stream of logs. 49 | func (s *LogsService) GetLogs(ctx context.Context, req *logsv1.GetLogsRequest) (*logsv1.GetLogsResponse, error) { 50 | var start, end time.Time 51 | if req.Start != nil { 52 | start = req.Start.AsTime() 53 | } 54 | if req.End != nil { 55 | end = req.End.AsTime() 56 | } else { 57 | end = time.Now() 58 | } 59 | 60 | if req.PageToken != "" { 61 | dTs, err := decodeNextPageToken(req.PageToken) 62 | if err != nil { 63 | log.Errorf("failed to decode page token: %v", err) 64 | return nil, status.Error(codes.InvalidArgument, "invalid page token") 65 | } 66 | 67 | log.Debugf("decoded page token: %s", string(dTs)) 68 | 69 | start, err = time.Parse(time.RFC3339Nano, string(dTs)) 70 | if err != nil { 71 | log.Errorf("failed to parse page token: %v", err) 72 | return nil, status.Error(codes.InvalidArgument, "invalid page token") 73 | } 74 | 75 | } 76 | pageSize := req.PageSize 77 | if pageSize == 0 { 78 | pageSize = 100 79 | } 80 | 81 | logs, err := s.db.Queries().GetAccessLogsByStartAndEnd(ctx, sqlc.GetAccessLogsByStartAndEndParams{ 82 | StartTime: start.UnixNano(), 83 | EndTime: end.UnixNano(), 84 | Limit: int64(pageSize), 85 | }) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | logpbs := make([]*logsv1.Log, 0, len(logs)) 91 | for _, l := range logs { 92 | logpb, err := logFromRow(l) 93 | if err != nil { 94 | return nil, err 95 | } 96 | logpbs = append(logpbs, logpb) 97 | } 98 | 99 | var nextPageToken string 100 | if len(logpbs) == int(pageSize) { 101 | nextPageToken = encodeNextPageToken(logpbs[len(logpbs)-1].Timestamp.AsTime().Format(time.RFC3339Nano)) 102 | } 103 | 104 | return &logsv1.GetLogsResponse{ 105 | Logs: logpbs, 106 | NextPageToken: nextPageToken, 107 | }, nil 108 | } 109 | 110 | func (s *LogsService) GetFullLog(ctx context.Context, req *logsv1.GetFullLogRequest) (*logsv1.FullLog, error) { 111 | return nil, status.Error(codes.Unimplemented, "not implemented") 112 | } 113 | 114 | func (s *LogsService) storeHTTPLogEntry(ctx context.Context, entry *accesslogv3.HTTPAccessLogEntry) error { 115 | if entry.Request.RequestId == "" { 116 | return errors.New("missing request ID") 117 | } 118 | entryJson, err := protojson.Marshal(entry) 119 | if err != nil { 120 | return err 121 | } 122 | return s.db.Queries().AddAccessLog(ctx, sqlc.AddAccessLogParams{ 123 | RequestID: entry.Request.RequestId, 124 | StartTime: entry.CommonProperties.StartTime.AsTime().UnixNano(), 125 | EndTime: entry.CommonProperties.StartTime.AsTime().Add(entry.CommonProperties.Duration.AsDuration()).UnixNano(), 126 | Entry: entryJson, 127 | }) 128 | } 129 | 130 | func (s *LogsService) StreamAccessLogs(stream accesslogservicev3.AccessLogService_StreamAccessLogsServer) error { 131 | for { 132 | req, err := stream.Recv() 133 | if errors.Is(err, io.EOF) { 134 | return nil 135 | } 136 | if err != nil { 137 | return err 138 | } 139 | 140 | if req.Identifier != nil { 141 | log.Infof("Initiating access log stream %v: %s/%s", req.Identifier.LogName, req.Identifier.Node.Cluster, req.Identifier.Node.Id) 142 | } 143 | 144 | switch entries := req.LogEntries.(type) { 145 | case *accesslogservicev3.StreamAccessLogsMessage_HttpLogs: 146 | for _, entry := range entries.HttpLogs.LogEntry { 147 | log.Debugf("Received HTTP access log entry: %v", entry) 148 | 149 | if entry == nil { 150 | continue 151 | } 152 | common := entry.CommonProperties 153 | if common == nil { 154 | log.Warnf("Received HTTP access log entry without common properties: %v", entry) 155 | continue 156 | } 157 | req := entry.Request 158 | if req == nil { 159 | log.Warnf("Received HTTP access log entry without request: %v", entry) 160 | continue 161 | } 162 | resp := entry.Response 163 | if resp == nil { 164 | log.Warnf("Received HTTP access log entry without response: %v", entry) 165 | continue 166 | } 167 | 168 | log.Debugf("%s: %v -> %v %s %s %d id=%s", 169 | entry.CommonProperties.StartTime.AsTime().Format(time.RFC3339), 170 | common.DownstreamRemoteAddress, 171 | common.UpstreamRemoteAddress, 172 | req.RequestMethod.String(), 173 | req.Path, 174 | resp.ResponseCode.GetValue(), 175 | req.RequestId, 176 | ) 177 | 178 | if err := s.storeHTTPLogEntry(stream.Context(), entry); err != nil { 179 | log.Errorf("Failed to store log entry: %v", err) 180 | } 181 | } 182 | default: 183 | log.Debugf("Received unknown access log entry: %v", entries) 184 | } 185 | 186 | } 187 | return nil 188 | } 189 | 190 | func (s *LogsService) RegisterALS(srv *grpc.Server) { 191 | accesslogservicev3.RegisterAccessLogServiceServer(srv, s) 192 | } 193 | -------------------------------------------------------------------------------- /server/api/middleware.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | "strings" 9 | "time" 10 | 11 | "github.com/gogo/status" 12 | "github.com/golang/protobuf/ptypes/empty" 13 | tclient "go.temporal.io/sdk/client" 14 | "google.golang.org/grpc/codes" 15 | "google.golang.org/protobuf/encoding/protojson" 16 | "google.golang.org/protobuf/types/known/emptypb" 17 | "google.golang.org/protobuf/types/known/timestamppb" 18 | 19 | "github.com/apoxy-dev/proximal/core/log" 20 | serverdb "github.com/apoxy-dev/proximal/server/db" 21 | sqlc "github.com/apoxy-dev/proximal/server/db/sql" 22 | "github.com/apoxy-dev/proximal/server/watcher" 23 | 24 | middlewarev1 "github.com/apoxy-dev/proximal/api/middleware/v1" 25 | ) 26 | 27 | // MiddlewareService implements the MiddlewareServiceServer gRPC service. 28 | type MiddlewareService struct { 29 | db *serverdb.DB 30 | tc tclient.Client 31 | watcher *watcher.Watcher 32 | } 33 | 34 | // NewMiddlewareService returns a new MiddlewareService. 35 | func NewMiddlewareService(db *serverdb.DB, tc tclient.Client, w *watcher.Watcher) *MiddlewareService { 36 | return &MiddlewareService{ 37 | db: db, 38 | tc: tc, 39 | watcher: w, 40 | } 41 | } 42 | 43 | func (s *MiddlewareService) List(ctx context.Context, req *middlewarev1.ListRequest) (*middlewarev1.ListResponse, error) { 44 | log.Infof("list middlewares: %s", protojson.Format(req)) 45 | 46 | ts := time.Now() 47 | if req.PageToken != "" { 48 | dTs, err := decodeNextPageToken(req.PageToken) 49 | if err != nil { 50 | log.Errorf("failed to decode page token: %v", err) 51 | return nil, status.Error(codes.InvalidArgument, "invalid page token") 52 | } 53 | 54 | log.Debugf("decoded page token: %s", string(dTs)) 55 | 56 | ts, err = time.Parse(time.RFC3339Nano, string(dTs)) 57 | if err != nil { 58 | log.Errorf("failed to parse page token: %v", err) 59 | return nil, status.Error(codes.InvalidArgument, "invalid page token") 60 | } 61 | } 62 | pageSize := req.PageSize 63 | if pageSize == 0 { 64 | pageSize = 100 65 | } 66 | 67 | ms, err := s.db.Queries().ListMiddlewares(ctx, sqlc.ListMiddlewaresParams{ 68 | Datetime: ts, 69 | Limit: int64(pageSize), 70 | }) 71 | if err != nil { 72 | return nil, fmt.Errorf("failed to list middleware: %w", err) 73 | } 74 | 75 | mpbs := make([]*middlewarev1.Middleware, 0, len(ms)) 76 | for _, m := range ms { 77 | mw, err := middlewareFromRow(&m) 78 | if err != nil { 79 | return nil, fmt.Errorf("failed to convert middleware: %w", err) 80 | } 81 | mpbs = append(mpbs, mw) 82 | } 83 | 84 | var nextPageToken string 85 | if len(mpbs) == int(pageSize) { 86 | nextPageToken = encodeNextPageToken(mpbs[len(mpbs)-1].CreatedAt.AsTime().Format(time.RFC3339Nano)) 87 | } 88 | 89 | return &middlewarev1.ListResponse{ 90 | Middlewares: mpbs, 91 | NextPageToken: nextPageToken, 92 | }, nil 93 | 94 | } 95 | 96 | func middlewareFromRow(m *sqlc.Middleware) (*middlewarev1.Middleware, error) { 97 | var ingestParams middlewarev1.MiddlewareIngestParams 98 | if err := protojson.Unmarshal(m.IngestParamsJson, &ingestParams); err != nil { 99 | return nil, fmt.Errorf("unable to unmarshal ingest params: %v", err) 100 | } 101 | var runtimeParams middlewarev1.MiddlewareRuntimeParams 102 | if err := protojson.Unmarshal(m.RuntimeParamsJson, &runtimeParams); err != nil { 103 | return nil, fmt.Errorf("unable to unmarshal runtime params: %v", err) 104 | } 105 | status, ok := middlewarev1.Middleware_MiddlewareStatus_value[strings.ToUpper(m.Status)] 106 | if !ok { 107 | return nil, fmt.Errorf("unknown middleware status: %v", m.Status) 108 | } 109 | return &middlewarev1.Middleware{ 110 | Slug: m.Slug, 111 | IngestParams: &ingestParams, 112 | RuntimeParams: &runtimeParams, 113 | Status: middlewarev1.Middleware_MiddlewareStatus(status), 114 | LiveBuildSha: m.LiveBuildSha.String, 115 | CreatedAt: timestamppb.New(m.CreatedAt.Time), 116 | UpdatedAt: timestamppb.New(m.UpdatedAt.Time), 117 | }, nil 118 | } 119 | 120 | func (s *MiddlewareService) Get(ctx context.Context, req *middlewarev1.GetRequest) (*middlewarev1.Middleware, error) { 121 | m, err := s.db.Queries().GetMiddlewareBySlug(ctx, req.Slug) 122 | if err != nil { 123 | if errors.Is(err, sql.ErrNoRows) { 124 | return nil, status.Error(codes.NotFound, "middleware not found") 125 | } 126 | log.Errorf("unable to get middleware: %v", err) 127 | return nil, status.Error(codes.Internal, "unable to get middleware") 128 | } 129 | 130 | pb, err := middlewareFromRow(&m) 131 | if err != nil { 132 | log.Errorf("unable to create middleware from row: %v", err) 133 | return nil, status.Error(codes.Internal, "unable to create middleware from row") 134 | } 135 | return pb, nil 136 | } 137 | 138 | // Create starts a new ingest workflow for the given middleware. 139 | func (s *MiddlewareService) Create(ctx context.Context, req *middlewarev1.CreateRequest) (*middlewarev1.Middleware, error) { 140 | paramsJson, err := protojson.Marshal(req.Middleware.IngestParams) 141 | if err != nil { 142 | log.Errorf("unable to marshal params: %v", err) 143 | return nil, status.Error(codes.Internal, "unable to marshal params") 144 | } 145 | runParamsJson, err := protojson.Marshal(req.Middleware.RuntimeParams) 146 | if err != nil { 147 | log.Errorf("unable to marshal runtime params: %v", err) 148 | return nil, status.Error(codes.Internal, "unable to marshal runtime params") 149 | } 150 | 151 | _, err = s.db.Queries().GetMiddlewareBySlug(ctx, req.Middleware.Slug) 152 | if err == nil { 153 | return nil, status.Error(codes.AlreadyExists, "middleware already exists") 154 | } else if err != nil && !errors.Is(err, sql.ErrNoRows) { 155 | log.Errorf("unable to get middleware by slug: %v", err) 156 | return nil, status.Error(codes.Internal, "unable to get middleware by slug") 157 | } 158 | 159 | tx, err := s.db.Begin() 160 | if err != nil { 161 | log.Errorf("failed to begin transaction: %v", err) 162 | return nil, status.Error(codes.Internal, "failed to start ingest") 163 | } 164 | defer tx.Rollback() 165 | qtx := s.db.Queries().WithTx(tx) 166 | 167 | m, err := qtx.CreateMiddleware(ctx, sqlc.CreateMiddlewareParams{ 168 | Slug: req.Middleware.Slug, 169 | SourceType: serverdb.SourceTypeGitHub, 170 | IngestParamsJson: paramsJson, 171 | RuntimeParamsJson: runParamsJson, 172 | Status: serverdb.MiddlewareStatusPending, 173 | StatusDetail: sql.NullString{ 174 | String: "pending", 175 | Valid: true, 176 | }, 177 | }) 178 | if err != nil { 179 | log.Errorf("unable to create middleware: %v", err) 180 | return nil, status.Error(codes.Internal, "unable to create middleware") 181 | } 182 | 183 | if err := s.startBuildWorkflow(ctx, req.Middleware.Slug, req.Middleware.IngestParams); err != nil { 184 | log.Errorf("unable to start build workflow: %v", err) 185 | return nil, status.Error(codes.Internal, "unable to start build workflow") 186 | } 187 | 188 | if err := tx.Commit(); err != nil { 189 | log.Errorf("unable to commit transaction: %v", err) 190 | return nil, status.Error(codes.Internal, "unable to commit transaction") 191 | } 192 | 193 | pb, err := middlewareFromRow(&m) 194 | if err != nil { 195 | log.Errorf("unable to create middleware from row: %v", err) 196 | return nil, status.Error(codes.Internal, "unable to create middleware from row") 197 | } 198 | return pb, nil 199 | } 200 | 201 | func (s *MiddlewareService) Update(ctx context.Context, req *middlewarev1.UpdateRequest) (*middlewarev1.Middleware, error) { 202 | m, err := s.db.Queries().GetMiddlewareBySlug(ctx, req.Middleware.Slug) 203 | if err != nil { 204 | if errors.Is(err, sql.ErrNoRows) { 205 | return nil, status.Error(codes.NotFound, "middleware not found") 206 | } 207 | log.Errorf("unable to get middleware: %v", err) 208 | return nil, status.Error(codes.Internal, "unable to get middleware") 209 | } 210 | 211 | mpb, err := middlewareFromRow(&m) 212 | if err != nil { 213 | log.Errorf("unable to create middleware from row: %v", err) 214 | return nil, status.Error(codes.Internal, "unable to create middleware from row") 215 | } 216 | 217 | if mpb.Type != req.Middleware.Type { 218 | return nil, status.Error(codes.InvalidArgument, "cannot change middleware type") 219 | } 220 | if req.Middleware.IngestParams != nil { 221 | if req.Middleware.IngestParams.Type != mpb.IngestParams.Type { 222 | return nil, status.Error(codes.InvalidArgument, "cannot change ingest type") 223 | } 224 | if req.Middleware.IngestParams.Type == middlewarev1.MiddlewareIngestParams_GITHUB { 225 | if req.Middleware.IngestParams.GetGithubRepo() != mpb.IngestParams.GetGithubRepo() { 226 | return nil, status.Error(codes.InvalidArgument, "cannot change github repo") 227 | } 228 | } else if req.Middleware.IngestParams.Type == middlewarev1.MiddlewareIngestParams_DIRECT { 229 | if req.Middleware.IngestParams.GetWatchDir() != mpb.IngestParams.GetWatchDir() { 230 | return nil, status.Error(codes.InvalidArgument, "cannot change watch dir") 231 | } 232 | } 233 | } 234 | 235 | paramsJson, err := protojson.Marshal(req.Middleware.IngestParams) 236 | if err != nil { 237 | log.Errorf("unable to marshal params: %v", err) 238 | return nil, status.Error(codes.Internal, "unable to marshal params") 239 | } 240 | runParamsJson, err := protojson.Marshal(req.Middleware.RuntimeParams) 241 | if err != nil { 242 | log.Errorf("unable to marshal runtime params: %v", err) 243 | return nil, status.Error(codes.Internal, "unable to marshal runtime params") 244 | } 245 | 246 | upd, err := s.db.Queries().UpdateMiddleware(ctx, sqlc.UpdateMiddlewareParams{ 247 | Slug: req.Middleware.Slug, 248 | IngestParamsJson: paramsJson, 249 | RuntimeParamsJson: runParamsJson, 250 | }) 251 | if err != nil { 252 | log.Errorf("unable to update middleware: %v", err) 253 | return nil, status.Error(codes.Internal, "unable to update middleware") 254 | } 255 | 256 | upb, err := middlewareFromRow(&upd) 257 | if err != nil { 258 | log.Errorf("unable to create middleware from row: %v", err) 259 | return nil, status.Error(codes.Internal, "unable to create middleware from row") 260 | } 261 | 262 | return upb, nil 263 | } 264 | 265 | func (s *MiddlewareService) Delete(ctx context.Context, req *middlewarev1.DeleteRequest) (*emptypb.Empty, error) { 266 | log.Infof("deleting middleware %v", req.Slug) 267 | m, err := s.db.Queries().GetMiddlewareBySlug(ctx, req.Slug) 268 | if err != nil { 269 | if errors.Is(err, sql.ErrNoRows) { 270 | return nil, status.Error(codes.NotFound, "middleware not found") 271 | } 272 | log.Errorf("unable to get middleware: %v", err) 273 | return nil, status.Error(codes.Internal, "unable to get middleware") 274 | } 275 | 276 | mpb, err := middlewareFromRow(&m) 277 | if err != nil { 278 | log.Errorf("unable to create middleware from row: %v", err) 279 | return nil, status.Error(codes.Internal, "failed to delete middleware") 280 | } 281 | 282 | if mpb.IngestParams.Type == middlewarev1.MiddlewareIngestParams_DIRECT { 283 | if err := s.watcher.Remove(m.Slug); err != nil { 284 | log.Warnf("unable to remove watch dir: %v", err) 285 | } 286 | } 287 | 288 | if err := s.db.Queries().DeleteMiddleware(ctx, req.Slug); err != nil { 289 | log.Errorf("unable to delete middleware: %v", err) 290 | return nil, status.Error(codes.Internal, "unable to delete middleware") 291 | } 292 | return &emptypb.Empty{}, nil 293 | } 294 | 295 | func (s *MiddlewareService) InternalList(ctx context.Context, _ *empty.Empty) (*middlewarev1.ListResponse, error) { 296 | mws, err := s.db.Queries().ListMiddlewaresAll(ctx) 297 | if err != nil { 298 | log.Errorf("unable to list middleware: %v", err) 299 | return nil, status.Error(codes.Internal, "unable to list middleware") 300 | } 301 | 302 | var resp middlewarev1.ListResponse 303 | for _, m := range mws { 304 | pb, err := middlewareFromRow(&m) 305 | if err != nil { 306 | log.Errorf("unable to create middleware from row: %v", err) 307 | return nil, status.Error(codes.Internal, "unable to create middleware from row") 308 | } 309 | resp.Middlewares = append(resp.Middlewares, pb) 310 | } 311 | return &resp, nil 312 | } 313 | -------------------------------------------------------------------------------- /server/api/utils.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | ) 7 | 8 | func decodeNextPageToken(token string) (string, error) { 9 | dTs := make([]byte, base64.StdEncoding.DecodedLen(len(token))) 10 | n, err := base64.StdEncoding.Decode(dTs, []byte(token)) 11 | if err != nil { 12 | return "", fmt.Errorf("failed to decode page token: %v", err) 13 | 14 | } 15 | dTs = dTs[:n] 16 | return string(dTs), nil 17 | } 18 | 19 | func encodeNextPageToken(ts string) string { 20 | return base64.StdEncoding.EncodeToString([]byte(ts)) 21 | } 22 | -------------------------------------------------------------------------------- /server/build/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "build", 5 | srcs = ["build.go"], 6 | importpath = "github.com/apoxy-dev/proximal/server/build", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /server/build/build.go: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | type Builder interface { 9 | Run(ctx context.Context, cwd, output string, args ...string) (stdout, stderr io.Reader, err error) 10 | Wait(ctx context.Context) error 11 | String() string 12 | } 13 | -------------------------------------------------------------------------------- /server/build/go/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "go", 5 | srcs = ["build.go"], 6 | importpath = "github.com/apoxy-dev/proximal/server/build/go", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /server/build/go/build.go: -------------------------------------------------------------------------------- 1 | package gobuild 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "os/exec" 7 | ) 8 | 9 | type Builder struct { 10 | modCmd, cmd *exec.Cmd 11 | } 12 | 13 | func NewBuilder() *Builder { 14 | return &Builder{} 15 | } 16 | 17 | func (b *Builder) String() string { 18 | return "tinygo" 19 | } 20 | 21 | func (b *Builder) Run(ctx context.Context, cwd, output string, args ...string) (stdout, stderr io.Reader, err error) { 22 | modArgs := []string{ 23 | "mod", 24 | "download", 25 | } 26 | b.modCmd = exec.CommandContext( 27 | ctx, 28 | "go", modArgs..., 29 | ) 30 | b.modCmd.Dir = cwd 31 | modOut, err := b.modCmd.StdoutPipe() 32 | if err != nil { 33 | return nil, nil, err 34 | } 35 | modErr, err := b.modCmd.StderrPipe() 36 | if err != nil { 37 | return nil, nil, err 38 | } 39 | if err := b.modCmd.Start(); err != nil { 40 | return nil, nil, err 41 | } 42 | 43 | buildArgs := []string{ 44 | "build", 45 | "-o", output, 46 | "-target", "wasi", 47 | "-scheduler", "none", 48 | } 49 | b.cmd = exec.CommandContext( 50 | ctx, 51 | "tinygo", append(buildArgs, args...)..., 52 | ) 53 | b.cmd.Dir = cwd 54 | stdout, err = b.cmd.StdoutPipe() 55 | if err != nil { 56 | return nil, nil, err 57 | } 58 | stderr, err = b.cmd.StderrPipe() 59 | if err != nil { 60 | return nil, nil, err 61 | } 62 | 63 | if err := b.cmd.Start(); err != nil { 64 | return nil, nil, err 65 | } 66 | 67 | return io.MultiReader(modOut, stdout), io.MultiReader(modErr, stderr), nil 68 | } 69 | 70 | func (b *Builder) Wait(ctx context.Context) error { 71 | if err := b.modCmd.Wait(); err != nil { 72 | return err 73 | } 74 | return b.cmd.Wait() 75 | } 76 | -------------------------------------------------------------------------------- /server/build/rust/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "rust", 5 | srcs = ["build.go"], 6 | importpath = "github.com/apoxy-dev/proximal/server/build/rust", 7 | visibility = ["//visibility:public"], 8 | deps = ["//core/log"], 9 | ) 10 | -------------------------------------------------------------------------------- /server/build/rust/build.go: -------------------------------------------------------------------------------- 1 | package rustbuild 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | 10 | "github.com/apoxy-dev/proximal/core/log" 11 | ) 12 | 13 | type Builder struct { 14 | cmd *exec.Cmd 15 | output string 16 | } 17 | 18 | func NewBuilder() *Builder { 19 | return &Builder{} 20 | } 21 | 22 | func (b *Builder) String() string { 23 | return "rustc (wasm32-wasi)" 24 | } 25 | 26 | func (b *Builder) Run(ctx context.Context, cwd, output string, args ...string) (stdout, stderr io.Reader, err error) { 27 | defaultArgs := []string{ 28 | "build", 29 | "--target", "wasm32-wasi", 30 | "--target-dir", filepath.Join(cwd, "target"), 31 | "--release", 32 | } 33 | b.cmd = exec.CommandContext( 34 | ctx, 35 | "cargo", append(defaultArgs, args...)..., 36 | ) 37 | b.cmd.Dir = cwd 38 | b.output = output 39 | stdout, err = b.cmd.StdoutPipe() 40 | if err != nil { 41 | return nil, nil, err 42 | } 43 | stderr, err = b.cmd.StderrPipe() 44 | if err != nil { 45 | return nil, nil, err 46 | } 47 | 48 | if err := b.cmd.Start(); err != nil { 49 | return nil, nil, err 50 | } 51 | 52 | return stdout, stderr, nil 53 | } 54 | 55 | func (b *Builder) Wait(ctx context.Context) error { 56 | if err := b.cmd.Wait(); err != nil { 57 | return err 58 | } 59 | 60 | // Move the file from the cargo output directory to the provided output path. 61 | log.Infof("searching in: %v", filepath.Join(b.cmd.Dir, "target/wasm32-wasi/release")) 62 | files, err := os.ReadDir(filepath.Join(b.cmd.Dir, "target/wasm32-wasi/release")) 63 | if err != nil { 64 | return err 65 | } 66 | for _, f := range files { 67 | if filepath.Ext(f.Name()) == ".wasm" { 68 | log.Debugf( 69 | "found output wasm: %v -> %v", 70 | filepath.Join(b.cmd.Dir, "target/wasm32-wasi/release", f.Name()), 71 | b.output, 72 | ) 73 | if err := os.Rename(filepath.Join(b.cmd.Dir, "target/wasm32-wasi/release", f.Name()), b.output); err != nil { 74 | return err 75 | } 76 | break 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /server/db/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "db", 5 | srcs = [ 6 | "conn.go", 7 | "migrations.go", 8 | "types.go", 9 | ], 10 | importpath = "github.com/apoxy-dev/proximal/server/db", 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "//api/middleware/v1:middleware", 14 | "//core/log", 15 | "//server/db/sql:sql_library", 16 | "//server/db/sql/migrations", 17 | "@com_github_golang_migrate_migrate_v4//:migrate", 18 | "@com_github_golang_migrate_migrate_v4//database/sqlite3", 19 | "@com_github_golang_migrate_migrate_v4//source/iofs", 20 | "@com_github_mattn_go_sqlite3//:go-sqlite3", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /server/db/conn.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "flag" 6 | "os" 7 | "path/filepath" 8 | 9 | _ "github.com/mattn/go-sqlite3" 10 | 11 | "github.com/apoxy-dev/proximal/core/log" 12 | sqlc "github.com/apoxy-dev/proximal/server/db/sql" 13 | ) 14 | 15 | var ( 16 | dbPath = flag.String("sqlite3_path", "sqlite3.db", "SQLite3 database path.") 17 | ) 18 | 19 | // DB is a wrapper around sql.DB. 20 | type DB struct { 21 | db *sql.DB 22 | } 23 | 24 | // New returns a new DB. 25 | func New() (*DB, error) { 26 | if _, err := os.Stat(*dbPath); os.IsNotExist(err) { 27 | log.Infof("creating new database at %s", *dbPath) 28 | if err := os.MkdirAll(filepath.Dir(*dbPath), 0755); err != nil { 29 | return nil, err 30 | } 31 | if _, err := os.Create(*dbPath); err != nil { 32 | return nil, err 33 | } 34 | } 35 | 36 | db, err := sql.Open("sqlite3", *dbPath) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return &DB{ 41 | db: db, 42 | }, nil 43 | } 44 | 45 | // Begin starts a transaction. 46 | func (db *DB) Begin() (*sql.Tx, error) { return db.db.Begin() } 47 | 48 | // Query returns a new sqlc.Queries instance. 49 | func (db *DB) Queries() *sqlc.Queries { 50 | return sqlc.New(db.db) 51 | } 52 | 53 | // Close closes the database connection. 54 | func (db *DB) Close() { 55 | db.db.Close() 56 | } 57 | -------------------------------------------------------------------------------- /server/db/migrations.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/golang-migrate/migrate/v4" 8 | _ "github.com/golang-migrate/migrate/v4/database/sqlite3" 9 | "github.com/golang-migrate/migrate/v4/source/iofs" 10 | _ "github.com/mattn/go-sqlite3" 11 | 12 | "github.com/apoxy-dev/proximal/server/db/sql/migrations" 13 | ) 14 | 15 | // DoMigrations performs DB migrations using github.com/golang-migrate/migrate/v4 package. 16 | func DoMigrations() error { 17 | d, err := iofs.New(migrations.SQL, ".") 18 | if err != nil { 19 | return err 20 | } 21 | m, err := migrate.NewWithSourceInstance("iofs", d, fmt.Sprintf("sqlite3://%s", *dbPath)) 22 | if err != nil { 23 | return err 24 | } 25 | if err = m.Up(); err != nil { 26 | if err == migrate.ErrNoChange { 27 | log.Println("Migrations already up-to-date.") 28 | return nil 29 | } 30 | return err 31 | } 32 | log.Println("Migrations complete.") 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /server/db/sql/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@com_plezentek_rules_sqlc//sqlc:def.bzl", "sqlc_package") 2 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 3 | 4 | sqlc_package( 5 | name = "sql", 6 | engine = "sqlite", 7 | package = "sql", 8 | queries = [ 9 | "middlewares.sql", 10 | "logs.sql", 11 | "endpoints.sql", 12 | ], 13 | schema = ["schema.sql"], 14 | visibility = ["//visibility:public"], 15 | ) 16 | 17 | go_library( 18 | name = "sql_library", 19 | srcs = [":sql"], 20 | importpath = "github.com/apoxy-dev/proximal/server/db/sql", 21 | visibility = ["//visibility:public"], 22 | ) 23 | -------------------------------------------------------------------------------- /server/db/sql/endpoints.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateEndpoint :one 2 | INSERT INTO endpoints ( 3 | cluster, 4 | is_domain, 5 | use_tls, 6 | lookup_family 7 | ) VALUES ( 8 | ?, 9 | ?, 10 | ?, 11 | ? 12 | ) 13 | RETURNING *; 14 | 15 | -- name: CountEndpoints :one 16 | SELECT COUNT(*) FROM endpoints; 17 | 18 | -- name: GetEndpointByCluster :one 19 | SELECT * 20 | FROM endpoints 21 | WHERE cluster = ? 22 | LIMIT 1; 23 | 24 | -- name: ListEndpoints :many 25 | SELECT * FROM endpoints; 26 | 27 | -- name: UpdateEndpoint :one 28 | UPDATE endpoints 29 | SET 30 | is_domain = ?, 31 | use_tls = ?, 32 | lookup_family = ?, 33 | updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') 34 | WHERE cluster = ? 35 | RETURNING *; 36 | 37 | -- name: DeleteEndpoint :exec 38 | DELETE FROM endpoints 39 | WHERE cluster = ?; 40 | 41 | -- name: GetDefaultUpstream :one 42 | SELECT * 43 | FROM endpoints 44 | WHERE cluster IN ( 45 | SELECT cluster 46 | FROM endpoint_default 47 | LIMIT 1 48 | ) 49 | LIMIT 1; 50 | 51 | -- name: InitDefaultUpstream :exec 52 | INSERT INTO endpoint_default (cluster) 53 | VALUES (?); 54 | 55 | -- name: SetDefaultUpstream :exec 56 | UPDATE endpoint_default 57 | SET 58 | cluster = ?, 59 | updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'); 60 | 61 | -- name: CreateEndpointAddress :one 62 | INSERT INTO endpoint_addresses ( 63 | cluster, 64 | host, 65 | port 66 | ) VALUES ( 67 | ?, 68 | ?, 69 | ? 70 | ) 71 | RETURNING *; 72 | 73 | -- name: GetEndpointAddressesByCluster :many 74 | SELECT * 75 | FROM endpoint_addresses 76 | WHERE cluster = ?; 77 | 78 | -- name: ListEndpointAddresses :many 79 | SELECT * 80 | FROM endpoint_addresses; 81 | 82 | -- name: DeleteEndpointAddress :exec 83 | DELETE FROM endpoint_addresses 84 | WHERE cluster = ? AND host = ? AND port = ?; 85 | -------------------------------------------------------------------------------- /server/db/sql/logs.sql: -------------------------------------------------------------------------------- 1 | -- name: AddAccessLog :exec 2 | INSERT INTO access_logs (request_id, start_time, end_time, entry) 3 | VALUES (?, ?, ?, ?); 4 | 5 | -- name: GetAccessLogsByStartAndEnd :many 6 | SELECT * 7 | FROM access_logs 8 | WHERE start_time >= ? AND end_time <= ? 9 | ORDER BY start_time DESC 10 | LIMIT ?; 11 | 12 | -- name: GetAccessLogsByRequestID :one 13 | SELECT * 14 | FROM access_logs 15 | WHERE request_id = ?; 16 | 17 | -- name: AddTrace :exec 18 | INSERT INTO traces (request_id, trace) 19 | VALUES (?, ?); 20 | 21 | -- name: GetTraceByRequestID :one 22 | SELECT * 23 | FROM traces 24 | WHERE request_id = ?; 25 | -------------------------------------------------------------------------------- /server/db/sql/middlewares.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateMiddleware :one 2 | INSERT INTO middlewares ( 3 | slug, 4 | source_type, 5 | ingest_params_json, 6 | runtime_params_json, 7 | status, 8 | status_detail 9 | ) VALUES ( 10 | ?, 11 | ?, 12 | ?, 13 | ?, 14 | ?, 15 | ? 16 | ) 17 | RETURNING *; 18 | 19 | -- name: GetMiddlewareBySlug :one 20 | SELECT * 21 | FROM middlewares 22 | WHERE slug = ? 23 | LIMIT 1; 24 | 25 | -- name: UpdateMiddleware :one 26 | UPDATE middlewares 27 | SET 28 | ingest_params_json = ?, 29 | runtime_params_json = ?, 30 | updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') 31 | WHERE slug = ? 32 | RETURNING *; 33 | 34 | -- name: UpdateMiddlewareStatus :one 35 | UPDATE middlewares 36 | SET 37 | status = ?, 38 | status_detail = ?, 39 | live_build_sha = ?, 40 | updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') 41 | WHERE slug = ? 42 | RETURNING *; 43 | 44 | -- name: DeleteMiddleware :exec 45 | DELETE FROM middlewares 46 | WHERE slug = ?; 47 | 48 | -- name: ListMiddlewares :many 49 | SELECT * 50 | FROM middlewares 51 | WHERE datetime(created_at) < datetime(?) 52 | ORDER BY created_at DESC 53 | LIMIT ?; 54 | 55 | -- name: ListMiddlewaresAll :many 56 | SELECT * 57 | FROM middlewares 58 | ORDER BY created_at DESC; 59 | 60 | -- name: ListMiddlewaresBySlugs :many 61 | SELECT * 62 | FROM middlewares 63 | WHERE slug IN (sqlc.slice('slugs')) 64 | ORDER BY created_at DESC; 65 | 66 | -- name: CreateBuild :one 67 | INSERT INTO builds ( 68 | sha, 69 | middleware_slug, 70 | status, 71 | status_detail 72 | ) VALUES ( 73 | ?, 74 | ?, 75 | ?, 76 | ? 77 | ) 78 | RETURNING *; 79 | 80 | -- name: ListBuildsByMiddlewareSlug :many 81 | SELECT * 82 | FROM builds 83 | WHERE middleware_slug = ? AND deleted_at IS NULL AND datetime(started_at) < datetime(?) 84 | ORDER BY started_at DESC 85 | LIMIT ?; 86 | 87 | -- name: GetBuildByMiddlewareSlugAndSha :one 88 | SELECT * 89 | FROM builds 90 | WHERE middleware_slug = ? AND sha = ? AND deleted_at IS NULL 91 | LIMIT 1; 92 | 93 | -- name: GetLiveReadyBuildByMiddlewareSlug :one 94 | SELECT * 95 | FROM builds 96 | WHERE middleware_slug IN ( 97 | SELECT live_build_sha 98 | FROM middlewares 99 | WHERE slug = ? AND deleted_at IS NULL 100 | ) AND status = 'READY' AND deleted_at IS NULL 101 | LIMIT 1; 102 | 103 | -- name: UpdateBuildStatus :one 104 | UPDATE builds 105 | SET 106 | status = ?, 107 | status_detail = ?, 108 | output_path = ?, 109 | updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') 110 | WHERE middleware_slug = ? AND sha = ? 111 | RETURNING *; 112 | 113 | -- name: DeleteBuild :exec 114 | UPDATE builds 115 | SET deleted_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') 116 | WHERE middleware_slug = ? AND sha = ? 117 | -------------------------------------------------------------------------------- /server/db/sql/migrations/00001_init.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE middlewares; 2 | DROP TABLE builds; 3 | -------------------------------------------------------------------------------- /server/db/sql/migrations/00001_init.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE middlewares ( 2 | slug VARCHAR(255) PRIMARY KEY, 3 | source_type TEXT NOT NULL CHECK (source_type IN ('GITHUB', 'DIRECT')), 4 | ingest_params_json BLOB NOT NULL, 5 | runtime_params_json BLOB NOT NULL, 6 | live_build_sha VARCHAR(64), 7 | status TEXT NOT NULL CHECK (status IN ('UNKNOWN', 'PENDING', 'READY', 'PENDING_READY', 'ERRORED')), 8 | status_detail TEXT, 9 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 10 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) 11 | ); 12 | 13 | CREATE TABLE builds ( 14 | sha VARCHAR(64) NOT NULL, 15 | middleware_slug VARCHAR(255) NOT NULL, 16 | status TEXT NOT NULL CHECK (status IN ('UNKNOWN', "PREPARING", 'RUNNING', 'READY', 'ERRORED')), 17 | status_detail TEXT NOT NULL, 18 | output_path VARCHAR(4096), 19 | started_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 20 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 21 | deleted_at DATETIME, 22 | PRIMARY KEY (middleware_slug, sha), 23 | FOREIGN KEY (middleware_slug) REFERENCES middlewares(slug) 24 | ); 25 | -------------------------------------------------------------------------------- /server/db/sql/migrations/00002_proxies.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE proxy_upstreams; 2 | DROP TABLE proxy_middlewares; 3 | DROP TABLE proxies; 4 | -------------------------------------------------------------------------------- /server/db/sql/migrations/00002_proxies.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE proxies ( 2 | key VARCHAR(255) PRIMARY KEY, 3 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 4 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 5 | deleted_at DATETIME 6 | ); 7 | 8 | CREATE TABLE proxy_middlewares ( 9 | proxy_key VARCHAR(255) NOT NULL, 10 | middleware_slug VARCHAR(255) NOT NULL, 11 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 12 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 13 | deleted_at DATETIME, 14 | PRIMARY KEY (proxy_key, middleware_slug), 15 | FOREIGN KEY (proxy_key) REFERENCES proxies(key), 16 | FOREIGN KEY (middleware_slug) REFERENCES middlewares(slug) 17 | ); 18 | 19 | CREATE TABLE proxy_upstreams ( 20 | proxy_key VARCHAR(255) NOT NULL, 21 | upstream BLOB NOT NULL, 22 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 23 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 24 | deleted_at DATETIME, 25 | PRIMARY KEY (proxy_key, upstream), 26 | FOREIGN KEY (proxy_key) REFERENCES proxies(key) 27 | ); 28 | -------------------------------------------------------------------------------- /server/db/sql/migrations/00003_logs.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE traces; 2 | DROP INDEX access_logs_start_time_end_time; 3 | DROP TABLE access_logs; 4 | -------------------------------------------------------------------------------- /server/db/sql/migrations/00003_logs.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE access_logs ( 2 | request_id VARCHAR(255) PRIMARY KEY, 3 | start_time INTEGER NOT NULL, 4 | end_time INTEGER NOT NULL, 5 | entry BLOB NOT NULL 6 | ); 7 | 8 | CREATE INDEX access_logs_start_time_end_time ON access_logs (start_time, end_time); 9 | 10 | CREATE TABLE traces ( 11 | request_id VARCHAR(255) PRIMARY KEY, 12 | trace BLOB NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /server/db/sql/migrations/00004_endpoints.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE endpoint_addresses; 2 | DROP TABLE endpoints; 3 | -------------------------------------------------------------------------------- /server/db/sql/migrations/00004_endpoints.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE endpoints ( 2 | cluster VARCHAR(255) PRIMARY KEY, 3 | is_domain BOOLEAN NOT NULL, 4 | use_tls BOOLEAN DEFAULT FALSE, 5 | lookup_family TEXT NOT NULL CHECK (lookup_family IN ('V4_ONLY', 'V4_FIRST', 'V6_ONLY', 'V6_FIRST')), 6 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 7 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) 8 | ); 9 | 10 | CREATE TABLE endpoint_default ( 11 | cluster VARCHAR(255) PRIMARY KEY, 12 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 13 | FOREIGN KEY (cluster) REFERENCES endpoints(cluster) 14 | ); 15 | 16 | CREATE TABLE endpoint_addresses ( 17 | cluster VARCHAR(255) NOT NULL, 18 | host VARCHAR(255) NOT NULL, 19 | port INTEGER NOT NULL, 20 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 21 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 22 | PRIMARY KEY (cluster, host, port), 23 | FOREIGN KEY (cluster) REFERENCES endpoints(cluster) 24 | ); 25 | -------------------------------------------------------------------------------- /server/db/sql/migrations/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "migrations", 5 | srcs = ["migrations.go"], 6 | embedsrcs = [ 7 | "00001_init.down.sql", 8 | "00001_init.up.sql", 9 | "00002_proxies.down.sql", 10 | "00002_proxies.up.sql", 11 | "00003_logs.up.sql", 12 | "00003_logs.down.sql", 13 | "00004_endpoints.down.sql", 14 | "00004_endpoints.up.sql", 15 | ], 16 | importpath = "github.com/apoxy-dev/proximal/server/db/sql/migrations", 17 | visibility = ["//visibility:public"], 18 | ) 19 | -------------------------------------------------------------------------------- /server/db/sql/migrations/migrations.go: -------------------------------------------------------------------------------- 1 | package migrations 2 | 3 | import "embed" 4 | 5 | //go:embed *.sql 6 | var SQL embed.FS 7 | -------------------------------------------------------------------------------- /server/db/sql/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE middlewares ( 2 | slug VARCHAR(255) PRIMARY KEY, 3 | source_type TEXT NOT NULL CHECK (source_type IN ('GITHUB', 'DIRECT')), 4 | ingest_params_json BLOB NOT NULL, 5 | runtime_params_json BLOB NOT NULL, 6 | live_build_sha VARCHAR(64), 7 | status TEXT NOT NULL CHECK (status IN ('UNKNOWN', 'PENDING', 'READY', 'PENDING_READY', 'ERRORED')), 8 | status_detail TEXT, 9 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 10 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) 11 | ); 12 | 13 | CREATE TABLE builds ( 14 | sha VARCHAR(64) NOT NULL, 15 | middleware_slug VARCHAR(255) NOT NULL, 16 | status TEXT NOT NULL CHECK (status IN ('UNKNOWN', "PREPARING", 'RUNNING', 'READY', 'ERRORED')), 17 | status_detail TEXT NOT NULL, 18 | output_path VARCHAR(4096), 19 | started_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 20 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 21 | deleted_at DATETIME, 22 | PRIMARY KEY (middleware_slug, sha), 23 | FOREIGN KEY (middleware_slug) REFERENCES middlewares(slug) 24 | ); 25 | 26 | CREATE TABLE endpoints ( 27 | cluster VARCHAR(255) PRIMARY KEY, 28 | is_domain BOOLEAN NOT NULL, 29 | use_tls BOOLEAN DEFAULT FALSE, 30 | lookup_family TEXT NOT NULL CHECK (lookup_family IN ('V4_ONLY', 'V4_FIRST', 'V6_ONLY', 'V6_FIRST')), 31 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 32 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) 33 | ); 34 | 35 | CREATE TABLE endpoint_default ( 36 | cluster VARCHAR(255) PRIMARY KEY, 37 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 38 | FOREIGN KEY (cluster) REFERENCES endpoints(cluster) 39 | ); 40 | 41 | CREATE TABLE endpoint_addresses ( 42 | cluster VARCHAR(255) NOT NULL, 43 | host VARCHAR(255) NOT NULL, 44 | port INTEGER NOT NULL, 45 | created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 46 | updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 47 | PRIMARY KEY (cluster, host, port), 48 | FOREIGN KEY (cluster) REFERENCES endpoints(cluster) 49 | ); 50 | 51 | CREATE TABLE access_logs ( 52 | request_id VARCHAR(255) PRIMARY KEY, 53 | start_time INTEGER NOT NULL, 54 | end_time INTEGER NOT NULL, 55 | entry BLOB NOT NULL 56 | ); 57 | 58 | CREATE TABLE traces ( 59 | request_id VARCHAR(255) PRIMARY KEY, 60 | trace BLOB NOT NULL 61 | ); 62 | -------------------------------------------------------------------------------- /server/db/types.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | middlewarev1 "github.com/apoxy-dev/proximal/api/middleware/v1" 5 | ) 6 | 7 | var ( 8 | SourceTypeDirect = middlewarev1.MiddlewareIngestParams_DIRECT.String() 9 | SourceTypeGitHub = middlewarev1.MiddlewareIngestParams_GITHUB.String() 10 | ) 11 | 12 | var ( 13 | MiddlewareStatusUnknown = middlewarev1.Middleware_UNKNOWN.String() 14 | MiddlewareStatusPending = middlewarev1.Middleware_PENDING.String() 15 | MiddlewareStatusPendingReady = middlewarev1.Middleware_PENDING_READY.String() 16 | MiddlewareStatusReady = middlewarev1.Middleware_READY.String() 17 | MiddlewareStatusErrored = middlewarev1.Middleware_ERRORED.String() 18 | ) 19 | -------------------------------------------------------------------------------- /server/envoy/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "envoy", 5 | srcs = ["envoy.go"], 6 | importpath = "github.com/apoxy-dev/proximal/server/envoy", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//api/endpoint/v1:endpoint", 10 | "//api/middleware/v1:middleware", 11 | "//core/log", 12 | "@com_github_envoyproxy_go_control_plane//envoy/config/accesslog/v3:accesslog", 13 | "@com_github_envoyproxy_go_control_plane//envoy/config/cluster/v3:cluster", 14 | "@com_github_envoyproxy_go_control_plane//envoy/config/core/v3:core", 15 | "@com_github_envoyproxy_go_control_plane//envoy/config/endpoint/v3:endpoint", 16 | "@com_github_envoyproxy_go_control_plane//envoy/config/listener/v3:listener", 17 | "@com_github_envoyproxy_go_control_plane//envoy/config/route/v3:route", 18 | "@com_github_envoyproxy_go_control_plane//envoy/extensions/access_loggers/grpc/v3:grpc", 19 | "@com_github_envoyproxy_go_control_plane//envoy/extensions/common/tap/v3:tap", 20 | "@com_github_envoyproxy_go_control_plane//envoy/extensions/filters/http/router/v3:router", 21 | "@com_github_envoyproxy_go_control_plane//envoy/extensions/filters/http/tap/v3:tap", 22 | "@com_github_envoyproxy_go_control_plane//envoy/extensions/filters/http/wasm/v3:wasm", 23 | "@com_github_envoyproxy_go_control_plane//envoy/extensions/filters/network/http_connection_manager/v3:http_connection_manager", 24 | "@com_github_envoyproxy_go_control_plane//envoy/extensions/transport_sockets/tls/v3:tls", 25 | "@com_github_envoyproxy_go_control_plane//envoy/extensions/wasm/v3:wasm", 26 | "@com_github_envoyproxy_go_control_plane//envoy/service/cluster/v3:cluster", 27 | "@com_github_envoyproxy_go_control_plane//envoy/service/discovery/v3:discovery", 28 | "@com_github_envoyproxy_go_control_plane//envoy/service/endpoint/v3:endpoint", 29 | "@com_github_envoyproxy_go_control_plane//envoy/service/listener/v3:listener", 30 | "@com_github_envoyproxy_go_control_plane//envoy/service/route/v3:route", 31 | "@com_github_envoyproxy_go_control_plane//pkg/cache/types", 32 | "@com_github_envoyproxy_go_control_plane//pkg/cache/v3:cache", 33 | "@com_github_envoyproxy_go_control_plane//pkg/resource/v3:resource", 34 | "@com_github_envoyproxy_go_control_plane//pkg/server/v3:server", 35 | "@com_github_envoyproxy_go_control_plane//pkg/wellknown", 36 | "@com_github_gogo_status//:status", 37 | "@org_golang_google_grpc//:go_default_library", 38 | "@org_golang_google_grpc//codes", 39 | "@org_golang_google_protobuf//types/known/anypb", 40 | "@org_golang_google_protobuf//types/known/durationpb", 41 | "@org_golang_google_protobuf//types/known/emptypb", 42 | "@org_golang_google_protobuf//types/known/structpb", 43 | "@org_golang_google_protobuf//types/known/wrapperspb", 44 | ], 45 | ) 46 | -------------------------------------------------------------------------------- /server/ingest/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "ingest", 5 | srcs = ["worker.go"], 6 | importpath = "github.com/apoxy-dev/proximal/server/ingest", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//api/middleware/v1:middleware", 10 | "//server/build", 11 | "//server/build/go", 12 | "//server/build/rust", 13 | "//server/db", 14 | "//server/db/sql:sql_library", 15 | "//server/watcher", 16 | "@com_github_go_git_go_git_v5//:go-git", 17 | "@com_github_go_git_go_git_v5//config", 18 | "@com_github_go_git_go_git_v5//plumbing", 19 | "@com_github_go_git_go_git_v5//storage/memory", 20 | "@io_temporal_go_api//enums/v1:enums", 21 | "@io_temporal_go_sdk//activity", 22 | "@io_temporal_go_sdk//log", 23 | "@io_temporal_go_sdk//temporal", 24 | "@io_temporal_go_sdk//workflow", 25 | "@org_golang_x_mod//sumdb/dirhash", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /server/watcher/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "watcher", 5 | srcs = ["watcher.go"], 6 | importpath = "github.com/apoxy-dev/proximal/server/watcher", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//api/middleware/v1:middleware", 10 | "//core/log", 11 | "@com_github_fsnotify_fsnotify//:fsnotify", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /server/watcher/watcher.go: -------------------------------------------------------------------------------- 1 | package watcher 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "regexp" 10 | "sync" 11 | "time" 12 | 13 | "github.com/fsnotify/fsnotify" 14 | 15 | "github.com/apoxy-dev/proximal/core/log" 16 | 17 | middlewarev1 "github.com/apoxy-dev/proximal/api/middleware/v1" 18 | ) 19 | 20 | // Watcher watches a directory for changes and triggers a build when a change is 21 | // detected. 22 | type Watcher struct { 23 | // IgnoreRegex is a regex of files to ignore. 24 | IgnoreRegex string 25 | 26 | client middlewarev1.MiddlewareServiceClient 27 | wr *fsnotify.Watcher 28 | 29 | mu sync.Mutex 30 | // watches is a map of slugs to watch directories. 31 | watches map[string]string 32 | // subpaths is a map of subpaths to slugs. 33 | subpaths map[string]string 34 | // batches is a map of slugs to whether a build has been triggered. 35 | batches map[string]bool 36 | } 37 | 38 | // NewWatcher creates a new Watcher. 39 | func NewWatcher(c middlewarev1.MiddlewareServiceClient, ignoreRegex string) *Watcher { 40 | return &Watcher{ 41 | IgnoreRegex: ignoreRegex, 42 | client: c, 43 | watches: make(map[string]string), 44 | subpaths: make(map[string]string), 45 | batches: make(map[string]bool), 46 | } 47 | } 48 | 49 | // Run starts the watcher until the context is cancelled. 50 | func (w *Watcher) Run(ctx context.Context) error { 51 | var err error 52 | w.wr, err = fsnotify.NewWatcher() 53 | if err != nil { 54 | return err 55 | } 56 | defer w.wr.Close() 57 | 58 | r, err := regexp.Compile(w.IgnoreRegex) 59 | if err != nil { 60 | return fmt.Errorf("failed to compile ignore regex: %w", err) 61 | } 62 | 63 | errCh := make(chan error, 1) 64 | go func() { 65 | var err error 66 | defer func() { 67 | errCh <- err 68 | }() 69 | for { 70 | select { 71 | case e, ok := <-w.wr.Events: 72 | if !ok { 73 | err = errors.New("watcher closed") 74 | return 75 | } 76 | log.Infof("change detected in %s: %s", e.Name, e.Op) 77 | 78 | if e.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove|fsnotify.Rename) != 0 { 79 | if r.MatchString(filepath.Base(e.Name)) { 80 | log.Debugf("ignoring %s", e.Name) 81 | continue 82 | } 83 | 84 | slug, ok := w.subpaths[filepath.Dir(e.Name)] 85 | if !ok { 86 | log.Errorf("no slug found for %s", e.Name) 87 | continue 88 | } 89 | 90 | log.Infof("batching re-build for slug %s due to %s", slug, e.Name) 91 | 92 | w.mu.Lock() 93 | w.batches[slug] = true 94 | w.mu.Unlock() 95 | } 96 | case err, ok := <-w.wr.Errors: 97 | if !ok { 98 | err = errors.New("watcher closed") 99 | return 100 | } 101 | log.Errorf("watcher error: %v", err) 102 | case <-ctx.Done(): 103 | err = ctx.Err() 104 | return 105 | } 106 | } 107 | }() 108 | 109 | go func() { 110 | for { 111 | select { 112 | case <-time.After(2 * time.Second): 113 | w.mu.Lock() 114 | for slug := range w.batches { 115 | log.Infof("triggering build for %s", slug) 116 | if _, err := w.client.TriggerBuild(ctx, &middlewarev1.TriggerBuildRequest{ 117 | Slug: slug, 118 | }); err != nil { 119 | log.Errorf("failed to trigger build: %v", err) 120 | } 121 | } 122 | w.batches = make(map[string]bool) 123 | w.mu.Unlock() 124 | case <-ctx.Done(): 125 | return 126 | } 127 | } 128 | }() 129 | 130 | return <-errCh 131 | } 132 | 133 | // Add adds a new watch. 134 | func (w *Watcher) Add(slug, dir string) error { 135 | w.mu.Lock() 136 | defer w.mu.Unlock() 137 | 138 | dir, err := filepath.Abs(dir) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | if d, ok := w.watches[slug]; ok && d != dir { 144 | return fmt.Errorf("slug %s already exists with dir %s", slug, d) 145 | } 146 | 147 | w.watches[slug] = dir 148 | 149 | log.Infof("watching %s", dir) 150 | 151 | // Walk dir tree and place watchs for each dir (watches are not recursive). 152 | if err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 153 | if err != nil { 154 | return err 155 | } 156 | if !info.IsDir() || info.Name() == ".git" { 157 | return nil 158 | } 159 | log.Debugf("watching %s", path) 160 | if err := w.wr.Add(path); err != nil { 161 | return err 162 | } 163 | w.subpaths[path] = slug 164 | return nil 165 | }); err != nil { 166 | return err 167 | } 168 | 169 | return nil 170 | } 171 | 172 | // Remove removes a watch. 173 | func (w *Watcher) Remove(slug string) error { 174 | w.mu.Lock() 175 | defer w.mu.Unlock() 176 | 177 | dir, ok := w.watches[slug] 178 | if !ok { 179 | return fmt.Errorf("slug %s does not exist", slug) 180 | } 181 | 182 | // Removes all paths beginning with dir. 183 | for _, path := range w.wr.WatchList() { 184 | if !filepath.HasPrefix(path, dir) { 185 | continue 186 | } 187 | 188 | if err := w.wr.Remove(path); err != nil { 189 | return err 190 | } 191 | 192 | delete(w.subpaths, path) 193 | } 194 | 195 | delete(w.watches, slug) 196 | 197 | return nil 198 | } 199 | -------------------------------------------------------------------------------- /static/github-apoxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/static/github-apoxy.png -------------------------------------------------------------------------------- /static/github-proximal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/static/github-proximal.png -------------------------------------------------------------------------------- /thirdparty/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoxy-dev/proximal/69d1e65aa1a9eec5ed6e3b03ac4c9b7f13a763fe/thirdparty/BUILD.bazel -------------------------------------------------------------------------------- /thirdparty/com_github_cloudflare_circl-amd64-cgo.patch: -------------------------------------------------------------------------------- 1 | diff --git a/dh/x25519/BUILD.bazel b/dh/x25519/BUILD.bazel 2 | index d569bc1..76c14cd 100644 3 | --- a/dh/x25519/BUILD.bazel 4 | +++ b/dh/x25519/BUILD.bazel 5 | @@ -12,7 +12,9 @@ go_library( 6 | "doc.go", 7 | "key.go", 8 | "table.go", 9 | + "//math/fp25519:fp_amd64.h", 10 | ], 11 | + cgo = True, 12 | importpath = "github.com/cloudflare/circl/dh/x25519", 13 | visibility = ["//visibility:public"], 14 | deps = [ 15 | @@ -25,6 +27,8 @@ go_library( 16 | }), 17 | ) 18 | 19 | +exports_files(["curve_amd64.h"], ["//visibility:public"]) 20 | + 21 | alias( 22 | name = "go_default_library", 23 | actual = ":x25519", 24 | diff --git a/dh/x448/BUILD.bazel b/dh/x448/BUILD.bazel 25 | index ed287c6..ec9ef98 100644 26 | --- a/dh/x448/BUILD.bazel 27 | +++ b/dh/x448/BUILD.bazel 28 | @@ -12,7 +12,9 @@ go_library( 29 | "doc.go", 30 | "key.go", 31 | "table.go", 32 | + "//math/fp448:fp_amd64.h", 33 | ], 34 | + cgo = True, 35 | importpath = "github.com/cloudflare/circl/dh/x448", 36 | visibility = ["//visibility:public"], 37 | deps = [ 38 | @@ -25,6 +27,8 @@ go_library( 39 | }), 40 | ) 41 | 42 | +exports_files(["curve_amd64.h"], ["//visibility:public"]) 43 | + 44 | alias( 45 | name = "go_default_library", 46 | actual = ":x448", 47 | diff --git a/math/fp25519/BUILD.bazel b/math/fp25519/BUILD.bazel 48 | index c9973ac..4573937 100644 49 | --- a/math/fp25519/BUILD.bazel 50 | +++ b/math/fp25519/BUILD.bazel 51 | @@ -10,6 +10,7 @@ go_library( 52 | "fp_generic.go", 53 | "fp_noasm.go", 54 | ], 55 | + cgo = True, 56 | importpath = "github.com/cloudflare/circl/math/fp25519", 57 | visibility = ["//visibility:public"], 58 | deps = [ 59 | @@ -22,6 +23,8 @@ go_library( 60 | }), 61 | ) 62 | 63 | +exports_files(["fp_amd64.h"], ["//visibility:public"]) 64 | + 65 | alias( 66 | name = "go_default_library", 67 | actual = ":fp25519", 68 | diff --git a/math/fp448/BUILD.bazel b/math/fp448/BUILD.bazel 69 | index c371ca5..0f5f43d 100644 70 | --- a/math/fp448/BUILD.bazel 71 | +++ b/math/fp448/BUILD.bazel 72 | @@ -10,6 +10,7 @@ go_library( 73 | "fp_generic.go", 74 | "fp_noasm.go", 75 | ], 76 | + cgo = True, 77 | importpath = "github.com/cloudflare/circl/math/fp448", 78 | visibility = ["//visibility:public"], 79 | deps = [ 80 | @@ -22,6 +23,8 @@ go_library( 81 | }), 82 | ) 83 | 84 | +exports_files(["fp_amd64.h"], ["//visibility:public"]) 85 | + 86 | alias( 87 | name = "go_default_library", 88 | actual = ":fp448", 89 | -------------------------------------------------------------------------------- /thirdparty/com_github_googleapis_gax_go_v2-googleapis.patch: -------------------------------------------------------------------------------- 1 | diff --git a/BUILD.bazel b/BUILD.bazel 2 | index 0a5a76d..562debc 100644 3 | --- a/BUILD.bazel 4 | +++ b/BUILD.bazel 5 | @@ -49,8 +49,8 @@ go_test( 6 | "@com_github_google_go_cmp//cmp/cmpopts", 7 | "@org_golang_google_api//googleapi", 8 | "@org_golang_google_genproto//googleapis/longrunning", 9 | - "@org_golang_google_genproto_googleapis_api//serviceconfig", 10 | - "@org_golang_google_genproto_googleapis_rpc//errdetails", 11 | + "@go_googleapis//google/api:serviceconfig_go_proto", 12 | + "@go_googleapis//google/rpc:errdetails_go_proto", 13 | "@org_golang_google_grpc//codes", 14 | "@org_golang_google_grpc//status", 15 | "@org_golang_google_protobuf//encoding/protojson", 16 | diff --git a/apierror/BUILD.bazel b/apierror/BUILD.bazel 17 | index 2b5664c..49ea981 100644 18 | --- a/apierror/BUILD.bazel 19 | +++ b/apierror/BUILD.bazel 20 | @@ -9,7 +9,7 @@ go_library( 21 | deps = [ 22 | "//apierror/internal/proto", 23 | "@org_golang_google_api//googleapi", 24 | - "@org_golang_google_genproto_googleapis_rpc//errdetails", 25 | + "@go_googleapis//google/rpc:errdetails_go_proto", 26 | "@org_golang_google_grpc//codes", 27 | "@org_golang_google_grpc//status", 28 | "@org_golang_google_protobuf//encoding/protojson", 29 | @@ -33,7 +33,7 @@ go_test( 30 | "@com_github_google_go_cmp//cmp", 31 | "@com_github_google_go_cmp//cmp/cmpopts", 32 | "@org_golang_google_api//googleapi", 33 | - "@org_golang_google_genproto_googleapis_rpc//errdetails", 34 | + "@go_googleapis//google/rpc:errdetails_go_proto", 35 | "@org_golang_google_grpc//codes", 36 | "@org_golang_google_grpc//status", 37 | "@org_golang_google_protobuf//encoding/protojson", 38 | diff --git a/apierror/internal/proto/BUILD.bazel b/apierror/internal/proto/BUILD.bazel 39 | index aa284c8..5613132 100644 40 | --- a/apierror/internal/proto/BUILD.bazel 41 | +++ b/apierror/internal/proto/BUILD.bazel 42 | @@ -10,7 +10,7 @@ go_library( 43 | importpath_aliases = ["github.com/googleapis/gax-go/apierror/internal/proto"], 44 | visibility = ["//apierror:__subpackages__"], 45 | deps = [ 46 | - "@org_golang_google_genproto_googleapis_rpc//code", 47 | + "@go_googleapis//google/rpc:code_go_proto", 48 | "@org_golang_google_protobuf//reflect/protoreflect", 49 | "@org_golang_google_protobuf//runtime/protoimpl", 50 | "@org_golang_google_protobuf//types/known/anypb", 51 | -------------------------------------------------------------------------------- /thirdparty/com_github_grpc_ecosystem_grpc_gateway.patch: -------------------------------------------------------------------------------- 1 | diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel 2 | index 58b72b9..13c3eaf 100644 3 | --- a/runtime/BUILD.bazel 4 | +++ b/runtime/BUILD.bazel 5 | @@ -27,16 +27,17 @@ go_library( 6 | deps = [ 7 | "//internal:go_default_library", 8 | "//utilities:go_default_library", 9 | - "@com_github_golang_protobuf//descriptor:go_default_library_gen", 10 | + "@com_github_golang_protobuf//descriptor:descriptor", 11 | "@com_github_golang_protobuf//jsonpb:go_default_library_gen", 12 | "@com_github_golang_protobuf//proto:go_default_library", 13 | + "@com_github_golang_protobuf//protoc-gen-go/descriptor:go_default_library", 14 | + "@com_github_golang_protobuf//ptypes:go_default_library", 15 | + "@com_github_golang_protobuf//ptypes/any:go_default_library", 16 | + "@com_github_golang_protobuf//ptypes/duration:go_default_library", 17 | + "@com_github_golang_protobuf//ptypes/timestamp:go_default_library", 18 | + "@com_github_golang_protobuf//ptypes/wrappers:go_default_library", 19 | "@go_googleapis//google/api:httpbody_go_proto", 20 | - "@io_bazel_rules_go//proto/wkt:any_go_proto", 21 | - "@io_bazel_rules_go//proto/wkt:descriptor_go_proto", 22 | - "@io_bazel_rules_go//proto/wkt:duration_go_proto", 23 | - "@io_bazel_rules_go//proto/wkt:field_mask_go_proto", 24 | - "@io_bazel_rules_go//proto/wkt:timestamp_go_proto", 25 | - "@io_bazel_rules_go//proto/wkt:wrappers_go_proto", 26 | + "@io_bazel_rules_go//proto/wkt:field_mask_go_proto", 27 | "@org_golang_google_grpc//codes:go_default_library", 28 | "@org_golang_google_grpc//grpclog:go_default_library", 29 | "@org_golang_google_grpc//metadata:go_default_library", 30 | -------------------------------------------------------------------------------- /thirdparty/com_github_temporalio_temporalite-new-server-return.patch: -------------------------------------------------------------------------------- 1 | diff --git a/server.go b/server.go 2 | index fa7387d..d0a185c 100644 3 | --- a/server.go 4 | +++ b/server.go 5 | @@ -108,8 +108,12 @@ func NewServer(opts ...ServerOption) (*Server, error) { 6 | serverOpts = append(serverOpts, c.UpstreamOptions...) 7 | } 8 | 9 | + ss, err := temporal.NewServer(serverOpts...) 10 | + if err != nil { 11 | + return nil, err 12 | + } 13 | s := &Server{ 14 | - internal: temporal.NewServer(serverOpts...), 15 | + internal: ss, 16 | ui: c.UIServer, 17 | frontendHostPort: cfg.PublicClient.HostPort, 18 | config: c, 19 | -------------------------------------------------------------------------------- /thirdparty/com_google_cloud_go_storage-go-googleapis.patch: -------------------------------------------------------------------------------- 1 | diff --git a/BUILD.bazel b/BUILD.bazel 2 | index 7c4f9aa..7329fc7 100644 3 | --- a/BUILD.bazel 4 | +++ b/BUILD.bazel 5 | @@ -43,8 +43,8 @@ go_library( 6 | "@org_golang_google_api//storage/v1:storage", 7 | "@org_golang_google_api//transport", 8 | "@org_golang_google_api//transport/http", 9 | - "@org_golang_google_genproto//googleapis/type/date", 10 | - "@org_golang_google_genproto//googleapis/type/expr", 11 | + "@go_googleapis//google/type:date_go_proto", 12 | + "@go_googleapis//google/type:expr_go_proto", 13 | "@org_golang_google_grpc//:grpc", 14 | "@org_golang_google_grpc//codes", 15 | "@org_golang_google_grpc//metadata", 16 | diff --git a/internal/apiv2/stubs/BUILD.bazel b/internal/apiv2/stubs/BUILD.bazel 17 | index 6c13822..452c705 100644 18 | --- a/internal/apiv2/stubs/BUILD.bazel 19 | +++ b/internal/apiv2/stubs/BUILD.bazel 20 | @@ -7,8 +7,8 @@ go_library( 21 | visibility = ["//:__subpackages__"], 22 | deps = [ 23 | "@com_google_cloud_go_iam//apiv1/iampb", 24 | - "@org_golang_google_genproto//googleapis/type/date", 25 | - "@org_golang_google_genproto_googleapis_api//annotations", 26 | + "@go_googleapis//google/type:date_go_proto", 27 | + "@go_googleapis//google/api:annotations_go_proto", 28 | "@org_golang_google_grpc//:grpc", 29 | "@org_golang_google_grpc//codes", 30 | "@org_golang_google_grpc//status", 31 | --------------------------------------------------------------------------------