diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ad64b62..5a4f1b4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,6 +1,7 @@ -name: Build pg_idkit +name: build -on: [push] +on: + # push: jobs: ## Build the project diff --git a/.github/workflows/container.yaml b/.github/workflows/container.yaml new file mode 100644 index 0000000..4114bbb --- /dev/null +++ b/.github/workflows/container.yaml @@ -0,0 +1,41 @@ +name: container + +on: + push: + branches: + - feat/infra/add-dockerfiles + - v[0-9]+\.[0-9]+\.[0-9]+ +jobs: + build: + permissions: + contents: read + packages: write + runs-on: ${{ matrix.config.gh.runner }} + strategy: + matrix: + config: + - triple: x86_64-unknown-linux-musl + gh: + runner: ubuntu-22.04 + container: + arch: amd64 + pg_version: 15.5 + os_version: alpine3.18 + steps: + - uses: actions/checkout@v3 + - uses: docker/setup-qemu-action@v3 + - uses: extractions/setup-just@v1 + + - name: registry login + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: build & push container image + run: just build-image push-image + env: + CONTAINER_IMAGE_ARCH: ${{ matrix.config.container.arch }} + POSTGRES_IMAGE_VERSION: ${{ matrix.config.container.pg_version }} + POSTGRES_OS_IMAGE_VERSION: ${{ matrix.config.container.os_version }} diff --git a/Justfile b/Justfile index 535eb30..567f3c7 100644 --- a/Justfile +++ b/Justfile @@ -34,17 +34,25 @@ _check-installed-version tool msg: # Build # ######### -version := `grep 'version' Cargo.toml | head -n 1 | sed -rn 's/version\s*=\s*(.*)/\1/p'` +version := `cargo get package.version` +revision := `git rev-parse --short HEAD` # NOTE: we can't use this as the official version getter until # see: https://github.com/nicolaiunrein/cargo-get/issues/14 @get-version: _check-tool-cargo-get cargo get package.version +@get-revision: _check-tool-cargo-get + echo -n {{revision}} + print-version: #!/usr/bin/env -S bash -euo pipefail echo -n `{{just}} get-version` +print-revision: + #!/usr/bin/env -S bash -euo pipefail + echo -n `{{just}} get-revision` + changelog: {{git}} cliff --unreleased --tag=$(VERSION) --prepend=$(CHANGELOG_FILE_PATH) @@ -57,8 +65,11 @@ build-watch: _check-tool-cargo _check-tool-cargo-watch build-test-watch: _check-tool-cargo _check-tool-cargo-watch {{cargo_watch}} -x "test $(CARGO_BUILD_FLAGS)" --watch src +pkg_pg_version := env_var_or_default("PKG_PG_VERSION", "15.5") +pkg_pg_config_path := env_var_or_default("PKG_PG_CONFIG_PATH", "~/.pgrx/" + pkg_pg_version + "/pgrx-install/bin/pg_config") + package: - {{cargo}} pgrx package + {{cargo}} pgrx package --pg-config {{pkg_pg_config_path}} test: {{cargo}} test @@ -68,24 +79,23 @@ test: # Docker # ########## -pg_image_version := env_var_or_default("POSTGRES_IMAGE_VERSION", "15.4") -pg_image_tag := env_var_or_default("POSGRES_IMAGE_VERSION", pg_image_version + "-alpine3.18") +container_img_arch := env_var_or_default("CONTAINER_IMAGE_ARCH", "amd64") -pgkit_image_name := env_var_or_default("PGKIT_IMAGE_NAME", "postgres") -pgkit_image_tag := env_var_or_default("PGKIT_IMAGE_TAG", pg_image_version + "-pg_idkit=" + version) -pgkit_image_name_full := env_var_or_default("PGKIT_IMAGE_NAME_FULL", pgkit_image_name + ":" + pgkit_image_tag) -pgkit_dockerfile_path := env_var_or_default("PGKIT_DOCKERFILE_PATH", "infra" / "docker" / pgkit_image_tag + ".Dockerfile") +pg_image_version := env_var_or_default("POSTGRES_IMAGE_VERSION", "15.5") +pg_os_image_version := env_var_or_default("POSTGRES_OS_IMAGE_VERSION", "alpine3.18") -ci_dockerfile_path := env_var_or_default("CI_DOCKERFILE_PATH", "infra" / "docker" / "ci.Dockerfile") -ci_image_name := env_var_or_default("CI_IMAGE_NAME", "ghcr.io/vadosware/pg_idkit/builder") -ci_image_tag := env_var_or_default("CI_IMAGE_TAG", "0.x.x") -ci_image_name_full := env_var_or_default("CI_IMAGE_NAME_FULL", ci_image_name + ":" + ci_image_tag) +pgidkit_image_name := env_var_or_default("PGIDKIT_IMAGE_NAME", "ghcr.io/vadosware/pg_idkit") +pgidkit_image_tag := env_var_or_default("POSGRES_IMAGE_VERSION", version + "-" + "pg" + pg_image_version + "-" + pg_os_image_version + "-" + container_img_arch) +pgidkit_image_name_full := env_var_or_default("PGIDKIT_IMAGE_NAME_FULL", pgidkit_image_name + ":" + pgidkit_image_tag) +pgidkit_dockerfile_path := env_var_or_default("PGIDKIT_DOCKERFILE_PATH", "infra" / "docker" / pgidkit_image_tag + ".Dockerfile") docker_password_path := env_var_or_default("DOCKER_PASSWORD_PATH", "secrets/docker/password.secret") docker_username_path := env_var_or_default("DOCKER_USERNAME_PATH", "secrets/docker/username.secret") docker_image_registry := env_var_or_default("DOCKER_IMAGE_REGISTRY", "ghcr.io/vadosware/pg_idkit") docker_config_dir := env_var_or_default("DOCKER_CONFIG", "secrets/docker") +img_dockerfile_path := "infra" / "docker" / "pg" + pg_image_version + "-" + pg_os_image_version + "-" + container_img_arch + ".Dockerfile" + # Ensure that that a given file is present _ensure-file file: #!/usr/bin/env -S bash -euo pipefail @@ -101,9 +111,21 @@ docker-login: cat {{docker_password_path}} | {{docker}} login {{docker_image_registry}} -u `cat {{docker_username_path}}` --password-stdin cp {{docker_config_dir}}/config.json {{docker_config_dir}}/.dockerconfigjson +docker_platform_arg := env_var_or_default("DOCKER_PLATFORM_ARG", "") +docker_progress_arg := env_var_or_default("DOCKER_PROGRESS_ARG", "") + # Build the docker image for pg_idkit -image: - {{docker}} build -f {{pgkit_dockerfile_path}} -t {{pgkit_image_name_full}} +build-image: + {{docker}} build {{docker_platform_arg}} {{docker_progress_arg}} -f {{img_dockerfile_path}} -t {{pgidkit_image_name_full}} --build-arg PGIDKIT_REVISION={{revision}} . + +# Push the docker image for pg_idkit +push-image: + {{docker}} push {{pgidkit_image_name_full}} + +ci_dockerfile_path := env_var_or_default("CI_DOCKERFILE_PATH", "infra" / "docker" / "ci.Dockerfile") +ci_image_name := env_var_or_default("CI_IMAGE_NAME", "ghcr.io/vadosware/pg_idkit/builder") +ci_image_tag := env_var_or_default("CI_IMAGE_TAG", "0.x.x") +ci_image_name_full := env_var_or_default("CI_IMAGE_NAME_FULL", ci_image_name + ":" + ci_image_tag) # Build the docker image used in CI build-ci-image: @@ -112,3 +134,22 @@ build-ci-image: # Push the docker image used in CI (to GitHub Container Registry) push-ci-image: {{docker}} push {{ci_image_name_full}} + +# Determine the Dockerfile to use when building the packaging utility base image +base_pkg_dockerfile_path := if pg_os_image_version == "alpine3.18" { + "infra/docker/base-pkg-alpine3.18.Dockerfile" +} else if pg_os_image_version == "bookworm" { + "infra/docker/base-pkg-bookworm.Dockerfile" +} else { + error("invalid pg_os_image_version") +} +base_pkg_image_name := env_var_or_default("PKG_IMAGE_NAME", "ghcr.io/vadosware/pg_idkit/base-pkg") +base_pkg_image_name_full := env_var_or_default("PKG_IMAGE_NAME_FULL", base_pkg_image_name + ":" + pgidkit_image_tag) + +# Build the base image for packaging +build-base-pkg-image: + {{docker}} build -f {{base_pkg_dockerfile_path}} . -t {{base_pkg_image_name_full}}; + +# Push the base image for packaging +push-base-pkg-image: + {{docker}} push {{base_pkg_image_name_full}} diff --git a/README.md b/README.md index 0f9a973..7ebe53a 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@ `pg_idkit` is a [Postgres][postgres] extension for generating IDs. It aims to be have just about every ID you'd normally think of using: -| Methodology | function | Crate | Description | -|------------------------|----------------------------|---------------------------------------------------------|----------------------------------------------------------| -| [UUID v6][uuidv6] | `idkit_uuidv6_generate` | [`uuidv6`](https://crates.io/crates/uuidv6) | UUID v6 ([RFC 4122][rfc-4122-update]) | -| [UUID v7][uuidv7] | `idkit_uuidv7_generate` | [`uuid7`](https://crates.io/crates/uuid7) | UUID v7 ([RFC 4122][rfc-4122-update]) | -| [nanoid][nanoid] | `idkit_nanoid_generate` | [`nanoid`](https://crates.io/crates/nanoid) | NanoID, developed by [Andrey Sitnik][github-ai] | -| [ksuid][ksuid] | `idkit_ksuid_generate` | [`ksuid`](https://crates.io/crates/ksuid) | developed by [Segment][segment] | -| [ulid][ulid] | `idkit_ulid_generate` | [`ulid`](https://crates.io/crates/ulid) | unique, lexicographically sortable identifiers | -| [Timeflake][timeflake] | `idkit_timeflake_generate` | [`timeflake-rs`](https://crates.io/crates/timeflake-rs) | Twitter's Snowflake + Instagram's ID + Firebase's PushID | -| [PushID][pushid] | `idkit_pushid_generate` | [`pushid`](https://crates.io/crates/pushid) | Google Firebase's PushID | -| [xid][xid] | `idkit_xid_generate` | [`xid`](https://crates.io/crates/xid) | XID | -| [cuid][cuid] (deprecated) | `idkit_cuid_generate` | [`cuid`](https://crates.io/crates/cuid) | CUID | -| [cuid2][cuid2] | `idkit_cuid2_generate` | [`cuid2`](https://crates.io/crates/cuid2) | CUID | +| Methodology | function | Crate | Description | +|---------------------------|----------------------------|---------------------------------------------------------|----------------------------------------------------------| +| [UUID v6][uuidv6] | `idkit_uuidv6_generate` | [`uuidv6`](https://crates.io/crates/uuidv6) | UUID v6 ([RFC 4122][rfc-4122-update]) | +| [UUID v7][uuidv7] | `idkit_uuidv7_generate` | [`uuid7`](https://crates.io/crates/uuid7) | UUID v7 ([RFC 4122][rfc-4122-update]) | +| [nanoid][nanoid] | `idkit_nanoid_generate` | [`nanoid`](https://crates.io/crates/nanoid) | NanoID, developed by [Andrey Sitnik][github-ai] | +| [ksuid][ksuid] | `idkit_ksuid_generate` | [`ksuid`](https://crates.io/crates/ksuid) | developed by [Segment][segment] | +| [ulid][ulid] | `idkit_ulid_generate` | [`ulid`](https://crates.io/crates/ulid) | unique, lexicographically sortable identifiers | +| [Timeflake][timeflake] | `idkit_timeflake_generate` | [`timeflake-rs`](https://crates.io/crates/timeflake-rs) | Twitter's Snowflake + Instagram's ID + Firebase's PushID | +| [PushID][pushid] | `idkit_pushid_generate` | [`pushid`](https://crates.io/crates/pushid) | Google Firebase's PushID | +| [xid][xid] | `idkit_xid_generate` | [`xid`](https://crates.io/crates/xid) | XID | +| [cuid][cuid] (deprecated) | `idkit_cuid_generate` | [`cuid`](https://crates.io/crates/cuid) | CUID | +| [cuid2][cuid2] | `idkit_cuid2_generate` | [`cuid2`](https://crates.io/crates/cuid2) | CUID | This Postgres extension is made possible thanks to [`pgrx`][pgrx]. @@ -44,12 +44,40 @@ shared_preload_libraries = '/etc/postgresql/extensions/pg_idkit-vX.X.X.so' ### Dockerfile -To build `pg_idkit` into a Postgres instance you can use a `Dockerfile` like the following: +To use `pg_idkit` easily from a containerized environment, you can use the `pg_idkit` image, built from [`postgres`][docker-postgres]: -```dockerfile -TODO +```console +docker run \ + --rm \ + -e POSTGRES_PASSWORD=replace_this \ + -p 5432 \ + --name pg_idkit \ + ghcr.io/vadosware/pg_idkit:0.1.0-pg15.5-alpine3.18-amd64 ``` +> [!WARNING] +> Right now, only amd64 (x86_64) images are present/supported. +> +> Work to support more platforms is described in [issue #30](https://github.com/VADOSWARE/pg_idkit/issues/30) + +From another window, you can exec into the `pg_idkit` container and enable `pg_idkit`: + +```console +➜ docker exec -it pg_idkit psql -U postgres +psql (15.5) +Type "help" for help. + +postgres=# CREATE EXTENSION pg_idkit; +CREATE EXTENSION +postgres=# SELECT idkit_uuidv7_generate(); + idkit_uuidv7_generate +-------------------------------------- + 018c106f-9304-79bb-b5be-4483b92b036c +(1 row) +``` + +[docker-postgres]: https://hub.docker.com/_/postgres + ## Local Development Here's how to get started working on `pg_idkit` locally. @@ -76,7 +104,7 @@ cargo install --locked just #### `cargo-pgrx` -Installing +Installing ```console cargo install --locked cargo-pgrx @@ -179,6 +207,7 @@ export DOCKER_CONFIG=$(realpath secrets/docker) [google]: https://google.com [instagram]: instagram-engineering.com/ [it-cabrera]: https://darkghosthunter.medium.com/ +[just]: https://github.com/casey/just [ksuid]: https://github.com/segmentio/ksuid [mongodb]: https://www.mongodb.com/blog/channel/engineering-blog [nanoid]: https://www.npmjs.com/package/nanoid diff --git a/infra/docker/14.4-alpine.Dockerfile b/infra/docker/14.4-alpine.Dockerfile deleted file mode 100644 index 582aff2..0000000 --- a/infra/docker/14.4-alpine.Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# rust:1.69.0-alpine3.17 as of 2023/05/18 -FROM rust:1.69.0-alpine3.17@sha256:3dd0bb6f134635fe40dd9c18bd9603f9d90ce3538ac25ae3e69b9b127137acf2 AS build - -WORKDIR /app -COPY . . - -RUN make build - -# postgres:14.4-alpine as of 2022/07/31 -FROM sha256:7d6403121b12c9d29c13c9873bc0f76da8ff51f6c89fae10b99dc890807e27ae - -# SHAREDIR should be /usr/local/share/postgresql (pg_config --sharedir) -COPY --from=build /app/target/pg_idkit.so /usr/local/share/postgresql/extension/pg_idkit--$(VERSION).so -COPY --from=build /app/infra/pg/pg_idkit.control /usr/local/share/postgresql/extension/ - -RUN cat <> /var/lib/postgresql/data/postgresql.com \ -shared_preload_libraries=' diff --git a/infra/docker/base-pkg-alpine3.18.Dockerfile b/infra/docker/base-pkg-alpine3.18.Dockerfile new file mode 100644 index 0000000..89a7a2f --- /dev/null +++ b/infra/docker/base-pkg-alpine3.18.Dockerfile @@ -0,0 +1,35 @@ +FROM postgres:15.5-alpine3.18@sha256:a57387207806d947c842f1be9f358e37b05442bf8b5ed19b1a69af281be930e7 AS builder + +ARG RUST_TOOLCHAIN_VERSION=1.74 +ARG PGRX_PG_VERSION=pg15 + +# Install OS deps +RUN apk add --no-cache \ + alpine-sdk \ + clang \ + clang-dev \ + clang-libs \ + musl-dev \ + openssl-dev \ + rustup + +# Install Rust & related deps +RUN rustup-init -y --profile minimal --default-toolchain $RUST_TOOLCHAIN_VERSION \ + && source "$HOME/.cargo/env" \ + && cargo install cargo-binstall +ENV PATH="/root/.cargo/bin:${PATH}" +RUN cargo binstall -y just cargo-get + +# Install pgrx +# (disabling the static C runtime is required since pgrx requires dynamic linking w/ libssl and libcrypto) +RUN RUSTFLAGS="-Ctarget-feature=-crt-static" cargo install cargo-pgrx + +# Copy in pg_idkit code +WORKDIR /pg_idkit +COPY . . + +# Perform the build and packaging of pg_idkit +ENV PGRX_IGNORE_RUST_VERSIONS=y +ENV PKG_PG_CONFIG_PATH=/usr/local/bin/pg_config +RUN cargo pgrx init --pg15=$PKG_PG_CONFIG_PATH +RUN RUSTFLAGS="-Ctarget-feature=-crt-static" just build package diff --git a/infra/docker/pg15.5-alpine3.18-amd64.Dockerfile b/infra/docker/pg15.5-alpine3.18-amd64.Dockerfile new file mode 100644 index 0000000..12e3e2e --- /dev/null +++ b/infra/docker/pg15.5-alpine3.18-amd64.Dockerfile @@ -0,0 +1,36 @@ +# +# NOTE: you must have the base packaging layer built for this image to work +# you can build this from scratch with `just build-base-pkg-image` +# +FROM ghcr.io/vadosware/pg_idkit/base-pkg:pg15.5-alpine3.18-amd64 AS builder + +# Re-run the build with the latest code +WORKDIR /pg_idkit +COPY . . +RUN RUSTFLAGS="-Ctarget-feature=-crt-static" just build package + +FROM postgres:15.5-alpine3.18@sha256:a57387207806d947c842f1be9f358e37b05442bf8b5ed19b1a69af281be930e7 + +# NOTE: PGRX_PG_VERSION is defined via base-pkg:pg15.5-alpine3.18-amd64 +ARG PGRX_PG_VERSION=pg15 +ARG PGIDKIT_REVISION + +# Copy relevant built-files +COPY --from=builder /pg_idkit/target/release/pg_idkit-${PGRX_PG_VERSION}/usr /usr + +# expected @ /usr/local/share/postgresql/extension +# actually @ ./local/share/postgresql/extension/pg_idkit.control + +ARG PGIDKIT_VERSION +ARG PGIDKIT_REVISION + +LABEL org.opencontainers.image.authors="Victor Adossi " +LABEL org.opencontainers.image.description="A distribution of the base postgres image, with pg_idkit pre-installed." +LABEL org.opencontainers.image.documentation="https://github.com/VADOSWARE/pg_idkit#readme" +LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL org.opencontainers.image.revision=$PGIDKIT_REVISION +LABEL org.opencontainers.image.source="https://github.com/VADOSWARE/pg_idkit" +LABEL org.opencontainers.image.title="Postgres + pg_idkit" +LABEL org.opencontainers.image.url="https://github.com/VADOSWARE/pg_idkit" +LABEL org.opencontainers.image.vendor="VADOSWARE" +LABEL org.opencontainers.image.version=v${PGIDKIT_VERSION}