diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..0e61d5a2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.github +.vscode +benches +temp +tests +renovate.json +temp \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..666fbd32 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,109 @@ +name: build + +on: + push: + branches: + - master + pull_request: + branches: + - master + release: + types: [created] + +jobs: + dockerize: + name: dockerize + runs-on: ubuntu-22.04 + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: configure eqemu + uses: docker/setup-qemu-action@v3 + with: + platforms: "linux/arm64,linux/amd64" + + - name: configure docker buildx + uses: docker/setup-buildx-action@v3 + + - name: login to docker registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: build docker images + timeout-minutes: 15 + id: docker-bake + uses: docker/bake-action@v4 + env: + DOCKER_REGISTRY: ghcr.io/${{ github.repository }}/ + COMMIT_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + PUBLISH_VERSION: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }} + PUBLISH_LATEST: ${{ github.event_name == 'release' && !github.event.release.prerelease && '1' || '' }} + with: + workdir: . + provenance: false + push: true + files: docker/bake.hcl + targets: build + set: | + *.cache-from=type=gha,scope=build + *.cache-to=type=gha,scope=build,mode=max + + - name: docker details pr comment + uses: marocchino/sticky-pull-request-comment@v2 + if: ${{ github.event_name == 'pull_request' }} + with: + message: | + 🐋 This PR was built and pushed to the following [Docker images](https://github.com/the-guild-org/conductor-t2/pkgs/container/conductor-t2%2Fconductor): + +
+ Docker Bake metadata + + ```json + ${{ steps.docker-bake.outputs.metadata }} + ``` +
+ + binary: + name: compile binary (${{ matrix.platform.target }}) + strategy: + matrix: + platform: + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + + - os: ubuntu-22.04 + target: aarch64-unknown-linux-gnu + + runs-on: ${{ matrix.platform.os }} + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: build binary + uses: houseabsolute/actions-rust-cross@v0 + with: + command: build + target: ${{ matrix.platform.target }} + args: "--locked --release" + strip: true + + - uses: actions/upload-artifact@v3 + if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + name: upload binary artifact + with: + name: conductor-${{ matrix.platform.target }} + path: target/${{ matrix.platform.target }}/release/conductor + + - name: upload binaries to release + if: ${{ github.event_name == 'release' }} + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: target/${{ matrix.platform.target }}/release/conductor + asset_name: conductor-${{ matrix.platform.target }} + tag: ${{ github.ref }} + overwrite: true diff --git a/Cargo.lock b/Cargo.lock index 38e3327c..2e73c815 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -535,6 +535,7 @@ dependencies = [ "hyper", "hyper-tls", "mime", + "openssl", "serde", "serde_json", "serde_yaml", @@ -1566,6 +1567,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-src" +version = "111.27.0+1.1.1v" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.91" @@ -1574,6 +1584,7 @@ checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] diff --git a/Cargo.toml b/Cargo.toml index bdc7fa77..3e927533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ url = "2.4.1" graphql-parser = "0.4.0" futures = "0.3.28" axum-test = "12.5.0" +openssl = { version = "0.10", features = ["vendored"] } [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..9233d64e --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,28 @@ +FROM rust:1.72.1 as builder + +WORKDIR /usr/src/conductor + +COPY Cargo.toml Cargo.lock ./ +# This is a trick to get the most out of Docker's caching mechanism in GH Actions. +RUN mkdir src +RUN mkdir benches +RUN echo 'fn main() { println!("Dummy!"); }' > ./src/main.rs +RUN echo 'fn main() { println!("Dummy!"); }' > ./src/lib.rs +RUN echo 'fn main() { println!("Dummy!"); }' > ./benches/bench.rs +# We are only building the dependencies here, with a dummy file, this compiles all dependencies code only. +RUN cargo build --release --bin conductor + +# Now we can remove the dummy code, copy the actual code and compile the user code. +# This ensures that building dependencies and the actual code are cached separately. +RUN rm -rf src +COPY src src +RUN touch src/main.rs src/lib.rs +RUN cargo build --release --bin conductor + +FROM debian:12.1 + +RUN apt-get update -y && apt-get install -y ca-certificates + +COPY --from=builder /usr/src/conductor/target/release/conductor /usr/local/bin/conductor + +CMD conductor diff --git a/docker/bake.hcl b/docker/bake.hcl new file mode 100644 index 00000000..0b34e5fe --- /dev/null +++ b/docker/bake.hcl @@ -0,0 +1,60 @@ +variable "DOCKER_REGISTRY" { + default = "" +} + +variable "COMMIT_SHA" { + default = "local" +} + +variable "PUBLISH_VERSION" { + # Can be "" or the actual version to publish + default = "" +} + +variable "PUBLISH_LATEST" { + # Can be "" or "1" + default = "" +} + +function "maybe_latest_image_tag" { + params = [name] + result = equal("1", PUBLISH_LATEST) ? "${DOCKER_REGISTRY}${name}:latest" : "" +} + +function "maybe_version_tag" { + params = [name] + result = notequal("", PUBLISH_VERSION) ? "${DOCKER_REGISTRY}${name}:${PUBLISH_VERSION}" : "" +} + +function "commit_id_tag" { + params = [name, tag] + result = notequal("", tag) ? "${DOCKER_REGISTRY}${name}:${tag}" : "" +} + +target "conductor" { + context = "./" + dockerfile = "./docker/Dockerfile" + tags = [ + commit_id_tag("conductor", COMMIT_SHA), + maybe_latest_image_tag("conductor"), + maybe_version_tag("conductor"), + ] + labels = { + "org.opencontainers.image.source" = "https://github.com/the-guild-org/conductor-t2", + "org.opencontainers.image.authors": "The Guild ", + "org.opencontainers.image.vendor": "The Guild", + "org.opencontainers.image.url": "https://the-guild.dev/graphql/gateway", + "org.opencontainers.image.docs": "https://the-guild.dev/graphql/gateway", + "org.opencontainers.image.version": PUBLISH_VERSION, + "org.opencontainers.image.revision": COMMIT_SHA, + "org.opencontainers.image.licenses": "MIT", + "org.opencontainers.image.title": "Conductor", + "org.opencontainers.image.description": "Conductor is a robust GraphQL Gateway." + } +} + +group "build" { + targets = [ + "conductor" + ] +} diff --git a/src/endpoint/graphiql.rs b/src/endpoint/graphiql.rs index f64b9e35..55b69bf6 100644 --- a/src/endpoint/graphiql.rs +++ b/src/endpoint/graphiql.rs @@ -13,9 +13,9 @@ pub struct GraphiQLSource { const YOGA_GRAPHIQL_VERSION: &str = "4.1.1"; impl GraphiQLSource { - pub fn new(endpoint: &String) -> GraphiQLSource { + pub fn new(endpoint: &str) -> GraphiQLSource { GraphiQLSource { - endpoint: endpoint.clone(), + endpoint: endpoint.to_string(), query: String::from(""), headers_editor_enabled: true, } diff --git a/src/graphql_utils.rs b/src/graphql_utils.rs index 1db8afd8..cef355c6 100644 --- a/src/graphql_utils.rs +++ b/src/graphql_utils.rs @@ -37,9 +37,9 @@ pub struct GraphQLError { } impl GraphQLError { - pub fn new(message: &String) -> Self { + pub fn new(message: &str) -> Self { GraphQLError { - message: message.clone(), + message: message.to_string(), extensions: None, } } @@ -87,7 +87,7 @@ pub struct GraphQLResponse { } impl GraphQLResponse { - pub fn new_error(error: &String) -> Self { + pub fn new_error(error: &str) -> Self { GraphQLResponse { data: None, errors: Some(vec![GraphQLError::new(error)]), diff --git a/src/lib.rs b/src/lib.rs index bba6c044..02674890 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,10 +89,8 @@ pub async fn http_request_handler( } if flow_ctx.has_failed_extraction() { - return GraphQLResponse::new_error( - &"failed to extract GraphQL request from HTTP request".to_string(), - ) - .into_response(StatusCode::BAD_REQUEST); + return GraphQLResponse::new_error("failed to extract GraphQL request from HTTP request") + .into_response(StatusCode::BAD_REQUEST); } // Execute plugins on the GraphQL request. diff --git a/src/plugins/http_get_plugin.rs b/src/plugins/http_get_plugin.rs index c68107ae..1ec5d20b 100644 --- a/src/plugins/http_get_plugin.rs +++ b/src/plugins/http_get_plugin.rs @@ -45,10 +45,8 @@ impl Plugin for HttpGetPlugin { if let Some(gql_req) = &ctx.downstream_graphql_request { if gql_req.is_mutation() { ctx.short_circuit( - GraphQLResponse::new_error( - &"mutations are not allowed over GET".to_string(), - ) - .into_response(StatusCode::METHOD_NOT_ALLOWED), + GraphQLResponse::new_error("mutations are not allowed over GET") + .into_response(StatusCode::METHOD_NOT_ALLOWED), ); } }